-
Notifications
You must be signed in to change notification settings - Fork 527
feat: update command always updates core actions (actions/*) to latest major version #18692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
4fea869
feff364
c2984c7
20419c3
fb5a192
32b407a
70a90fc
24c18c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ import ( | |||||
| "os" | ||||||
| "os/exec" | ||||||
| "path/filepath" | ||||||
| "regexp" | ||||||
| "sort" | ||||||
| "strings" | ||||||
|
|
||||||
|
|
@@ -28,6 +29,12 @@ func extractBaseRepo(actionPath string) string { | |||||
| return actionPath | ||||||
| } | ||||||
|
|
||||||
| // isCoreAction returns true if the repo is a GitHub-maintained core action (actions/* org). | ||||||
| // Core actions are always updated to the latest major version without requiring --major. | ||||||
| func isCoreAction(repo string) bool { | ||||||
| return strings.HasPrefix(repo, "actions/") | ||||||
| } | ||||||
|
|
||||||
| // UpdateActions updates GitHub Actions versions in .github/aw/actions-lock.json | ||||||
| // It checks each action for newer releases and updates the SHA if a newer version is found | ||||||
| func UpdateActions(allowMajor, verbose bool) error { | ||||||
|
|
@@ -70,8 +77,11 @@ func UpdateActions(allowMajor, verbose bool) error { | |||||
| for key, entry := range actionsLock.Entries { | ||||||
| updateLog.Printf("Checking action: %s@%s", entry.Repo, entry.Version) | ||||||
|
|
||||||
| // Core actions (actions/*) always update to the latest major version | ||||||
| effectiveAllowMajor := allowMajor || isCoreAction(entry.Repo) | ||||||
|
|
||||||
| // Check for latest release | ||||||
| latestVersion, latestSHA, err := getLatestActionRelease(entry.Repo, entry.Version, allowMajor, verbose) | ||||||
| latestVersion, latestSHA, err := getLatestActionRelease(entry.Repo, entry.Version, effectiveAllowMajor, verbose) | ||||||
| if err != nil { | ||||||
| if verbose { | ||||||
| fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to check %s: %v", entry.Repo, err))) | ||||||
|
|
@@ -456,3 +466,150 @@ func marshalActionsLockSorted(actionsLock *actionsLockFile) ([]byte, error) { | |||||
| buf.WriteString(" }\n}") | ||||||
| return []byte(buf.String()), nil | ||||||
| } | ||||||
|
|
||||||
| // actionRefPattern matches "uses: actions/repo@SHA-or-tag" in workflow files. | ||||||
| // Captures: (1) indentation+uses prefix, (2) repo path, (3) SHA or version tag, | ||||||
| // (4) optional version comment (e.g., "v6.0.2" from "# v6.0.2"). | ||||||
|
||||||
| // (4) optional version comment (e.g., "v6.0.2" from "# v6.0.2"). | |
| // (4) optional version comment (e.g., "v6.0.2" from "# v6.0.2"), (5) trailing whitespace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 32b407a — comment now reads: (4) optional version comment (e.g., "v6.0.2" from "# v6.0.2"), (5) trailing whitespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex captures SHA hashes (40 hex chars) and version tags correctly. The [^\s#\n]+? lazy match for version tags looks good. Worth noting: the $ anchor with a multiline regex could be tricky — make sure the regexp.MustCompile is used with line-by-line processing (which it is, based on the scanning logic in UpdateActionsInWorkflowFiles).
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UpdateActionsInWorkflowFiles only iterates over os.ReadDir(workflowsDir) and skips subdirectories. In this repo there are workflow source files under nested paths (e.g. .github/workflows/shared/mcp-debug.md still references actions/checkout@v5), so these won’t be updated. Consider walking workflowsDir recursively (filepath.WalkDir) and applying the same update+recompile logic to all *.md files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 32b407a — UpdateActionsInWorkflowFiles now uses filepath.WalkDir to recurse into all subdirectories (including .github/workflows/shared/), applying the same update+recompile logic to all *.md files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: os.WriteFile with hardcoded 0644 will change the file's permissions if they were different (e.g., 0664 or 0600).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot apply comment and leave a note as a comment in the go code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a .md file has a bare SHA with no version comment (e.g., uses: actions/checkout@11bd719 like in hourly-ci-cleaner.md), currentVersion is set to "". Does getLatestActionRelease handle an empty version string gracefully? If it needs a version to determine the current major, passing "" might cause it to skip the update or error. Might be worth falling back to resolving the SHA to a tag via the API, or just defaulting to "v0" so it always picks up the latest.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateActionRefsInContent calls getLatestActionRelease (GitHub API / git ls-remote) for every matching line. With many workflows and repeated actions, this can result in a large number of redundant network calls and slow/flake the update command. Cache results per repo (and possibly per currentVersion/allowMajor) within a single run, and reuse the latestVersion/latestSHA across lines/files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 32b407a — added a map[string]latestReleaseResult cache (keyed by repo|currentVersion) passed through updateActionRefsInContent. Each unique repo/version is resolved only once per UpdateActionsInWorkflowFiles call, regardless of how many files or lines reference it.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -249,3 +249,62 @@ func TestMajorVersionPreference(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestIsCoreAction(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| repo string | ||
| want bool | ||
| }{ | ||
| {"actions/checkout is core", "actions/checkout", true}, | ||
| {"actions/setup-go is core", "actions/setup-go", true}, | ||
| {"actions/cache/restore is core", "actions/cache/restore", true}, | ||
| {"github/codeql-action is not core", "github/codeql-action", false}, | ||
| {"docker/login-action is not core", "docker/login-action", false}, | ||
| {"super-linter/super-linter is not core", "super-linter/super-linter", false}, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got := isCoreAction(tt.repo) | ||
| if got != tt.want { | ||
| t.Errorf("isCoreAction(%q) = %v, want %v", tt.repo, got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestUpdateActionRefsInContent_NonCoreActionsUnchanged(t *testing.T) { | ||
| // Non-actions/* org references should not be modified by updateActionRefsInContent | ||
| // since it only processes "uses: actions/" prefixed references. | ||
| input := `steps: | ||
| - uses: docker/login-action@v3 | ||
| - uses: github/codeql-action/upload-sarif@v3 | ||
| - run: echo hello` | ||
|
|
||
| changed, newContent, err := updateActionRefsInContent(input, false) | ||
| if err != nil { | ||
| t.Fatalf("updateActionRefsInContent() error = %v", err) | ||
| } | ||
| if changed { | ||
| t.Errorf("updateActionRefsInContent() changed = true, want false for non-actions/* refs") | ||
| } | ||
| if newContent != input { | ||
| t.Errorf("updateActionRefsInContent() modified content for non-actions/* refs\nGot: %s\nWant: %s", newContent, input) | ||
| } | ||
| } | ||
|
|
||
| func TestUpdateActionRefsInContent_NoActionRefs(t *testing.T) { | ||
| input := `description: Test workflow | ||
| steps: | ||
| - run: echo hello | ||
| - run: echo world` | ||
|
|
||
| changed, _, err := updateActionRefsInContent(input, false) | ||
| if err != nil { | ||
| t.Fatalf("updateActionRefsInContent() error = %v", err) | ||
| } | ||
| if changed { | ||
| t.Errorf("updateActionRefsInContent() changed = true, want false for content with no action refs") | ||
| } | ||
| } | ||
|
Comment on lines
+277
to
+312
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice clean helper function! The
HasPrefixcheck is simple and effective. One thought: if other "trusted" orgs need similar treatment in the future (e.g.,github/), this could be extended to a slice of trusted prefixes. For now,actions/*-only is the right scope.