Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 17 additions & 9 deletions pkg/workflow/action_sha_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ func ExtractActionsFromLockFile(lockFilePath string) ([]ActionUsage, error) {
return nil, fmt.Errorf("failed to parse lock file YAML: %w", err)
}

// Regular expression to match uses: owner/repo@sha
// This matches: owner/repo@40-char-hex-sha or owner/repo/subpath@40-char-hex-sha
usesPattern := regexp.MustCompile(`([a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+(?:/[a-zA-Z0-9_.-]+)*)@([0-9a-f]{40})`)
// Regular expression to match uses: owner/repo@sha with optional version comment
// This matches: owner/repo@40-char-hex-sha # version
// Captures: (1) repo, (2) sha, (3) version (optional)
usesPattern := regexp.MustCompile(`([a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+(?:/[a-zA-Z0-9_.-]+)*)@([0-9a-f]{40})(?:\s*#\s*([^\s]+))?`)

actions := make(map[string]ActionUsage) // Use map to deduplicate

Expand All @@ -62,12 +63,19 @@ func ExtractActionsFromLockFile(lockFilePath string) ([]ActionUsage, error) {
continue
}

actionSHACheckerLog.Printf("Found action: %s@%s", repo, sha)

// Try to determine the version tag from action_pins.json
// Extract version from comment if present (match[3])
version := ""
if pin, found := GetActionPinByRepo(repo); found {
version = pin.Version
if len(match) >= 4 && match[3] != "" {
version = match[3]
actionSHACheckerLog.Printf("Found action: %s@%s (version: %s)", repo, sha, version)
} else {
// Fallback: try to determine the version tag from action_pins.json
if pin, found := GetActionPinByRepo(repo); found {
version = pin.Version
actionSHACheckerLog.Printf("Found action: %s@%s (version from pins: %s)", repo, sha, version)
} else {
actionSHACheckerLog.Printf("Found action: %s@%s (no version)", repo, sha)
}
}

actions[repo+"@"+sha] = ActionUsage{
Expand Down Expand Up @@ -187,7 +195,7 @@ func ValidateActionSHAsInLockFile(lockFilePath string, cache *ActionCache, verbo
actionSHACheckerLog.Print("Saved updated action cache")
}
// Provide suggestion to fix the issue
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To update action SHAs, run: gh aw compile --validate"))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To apply updated action SHAs, recompile with: gh aw compile"))
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d action(s) with available updates", updateCount)))
}
Expand Down
63 changes: 63 additions & 0 deletions pkg/workflow/action_sha_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,66 @@ func TestExtractActionsFromLockFileInvalidFile(t *testing.T) {
t.Error("Expected error when reading non-existent file, got nil")
}
}

func TestExtractActionsFromLockFileWithVersionComments(t *testing.T) {
// Create a temporary lock file with version comments
tmpDir := testutil.TempDir(t, "test-*")
lockFile := filepath.Join(tmpDir, "test.lock.yml")

lockContent := `
name: Test Workflow
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
- name: Run tests
run: npm test
`

if err := os.WriteFile(lockFile, []byte(lockContent), 0644); err != nil {
t.Fatalf("Failed to create test lock file: %v", err)
}

// Extract actions
actions, err := ExtractActionsFromLockFile(lockFile)
if err != nil {
t.Fatalf("ExtractActionsFromLockFile failed: %v", err)
}

// Verify we extracted the expected actions with versions
if len(actions) != 3 {
t.Errorf("Expected 3 actions, got %d", len(actions))
}

// Create a map to easily look up actions by repo
actionMap := make(map[string]ActionUsage)
for _, action := range actions {
actionMap[action.Repo] = action
}

// Verify versions were extracted correctly from comments
tests := []struct {
repo string
expectedVersion string
}{
{"actions/checkout", "v5"},
{"actions/setup-node", "v6"},
{"actions/github-script", "v7.0.1"},
}

for _, tt := range tests {
action, found := actionMap[tt.repo]
if !found {
t.Errorf("Expected to find action %s, but it was not extracted", tt.repo)
continue
}

if action.Version != tt.expectedVersion {
t.Errorf("For %s: expected version %s, got %s", tt.repo, tt.expectedVersion, action.Version)
}
}
}