detached 92205ff

feat(executor): warn about actions upgraded to Node.js 24 in Complete job

commit
92205ff3b150e5e82f32c1790467617cff7d50c1
author
brice · · 2026-10-13 16:36 UTC
parent
76038ad
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +19 -0
M internal/executor/actions_node.go +5 -0
@@ -7,6 +7,8 @@ import (
77 "os"
88 "path/filepath"
99 "strings"
10+
11+ "bnema.dev/ermite-actions/action"
1012 )
1113
1214 const (
@@ -129,6 +131,9 @@ func appendUnique(l []string, v string) []string {
129131
130132 // actionName is HandlerFactory.GetActionName: "Name[/Path]@Ref".
131133 func actionName(n *node) string {
134+ if n.uses.Kind == action.UsesLocal {
135+ return n.uses.Path // no name: HandlerFactory.cs:200-208 uses the path alone
136+ }
132137 return registeredRef(n.uses)
133138 }
134139
@@ -7,6 +7,8 @@ import (
7 "os"7 "os"
8 "path/filepath"8 "path/filepath"
9 "strings"9 "strings"
10+
11+ "bnema.dev/ermite-actions/action"
10 )12 )
11 13
12 const (14 const (
@@ -129,6 +131,9 @@ func appendUnique(l []string, v string) []string {
129 131
130 // actionName is HandlerFactory.GetActionName: "Name[/Path]@Ref".132 // actionName is HandlerFactory.GetActionName: "Name[/Path]@Ref".
131 func actionName(n *node) string {133 func actionName(n *node) string {
134+ if n.uses.Kind == action.UsesLocal {
135+ return n.uses.Path // no name: HandlerFactory.cs:200-208 uses the path alone
136+ }
132 return registeredRef(n.uses)137 return registeredRef(n.uses)
133 }138 }
134 139
M internal/executor/actions_test.go +4 -0
@@ -223,6 +223,10 @@ func TestNodeVersionRules(t *testing.T) {
223223 if got := run("node20", " FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n", nil); !strings.Contains(got, "[node24]") {
224224 t.Errorf("force: %s", got)
225225 }
226+ rec, _ := actionRun(t, act("node20"), " - uses: ./a\n env:\n FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n", nil)
227+ if got := logOf(rec.steps[len(rec.steps)-1]); !strings.Contains(got, "##[warning]Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: ./a. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/") {
228+ t.Errorf("complete job:\n%s", got)
229+ }
226230 by24 := func(c *Config) { c.Actions.Node24ByDefault = true }
227231 got := run("node20", "", by24)
228232 if !strings.Contains(got, "[node24]") || !strings.Contains(got, "Node 20 is being deprecated. This workflow is running with Node 24 by default.") {
@@ -223,6 +223,10 @@ func TestNodeVersionRules(t *testing.T) {
223 if got := run("node20", " FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n", nil); !strings.Contains(got, "[node24]") {223 if got := run("node20", " FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n", nil); !strings.Contains(got, "[node24]") {
224 t.Errorf("force: %s", got)224 t.Errorf("force: %s", got)
225 }225 }
226+ rec, _ := actionRun(t, act("node20"), " - uses: ./a\n env:\n FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n", nil)
227+ if got := logOf(rec.steps[len(rec.steps)-1]); !strings.Contains(got, "##[warning]Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: ./a. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/") {
228+ t.Errorf("complete job:\n%s", got)
229+ }
226 by24 := func(c *Config) { c.Actions.Node24ByDefault = true }230 by24 := func(c *Config) { c.Actions.Node24ByDefault = true }
227 got := run("node20", "", by24)231 got := run("node20", "", by24)
228 if !strings.Contains(got, "[node24]") || !strings.Contains(got, "Node 20 is being deprecated. This workflow is running with Node 24 by default.") {232 if !strings.Contains(got, "[node24]") || !strings.Contains(got, "Node 20 is being deprecated. This workflow is running with Node 24 by default.") {
M internal/executor/job.go +10 -0
@@ -8,6 +8,7 @@ import (
88 "fmt"
99 "os"
1010 "path/filepath"
11+ "slices"
1112 "strings"
1213 "sync"
1314 "sync/atomic"
@@ -615,6 +616,15 @@ func (j *job) completeJob(num int) map[string]string {
615616 j.out(num, "Cleaning up orphan processes")
616617 killOrphans(j.trackID, j.before, func(l string) { j.out(num, l) })
617618
619+ // The actions that asked for node20 and got node24 (JobExtension.cs:1043-1049).
620+ j.mu.Lock()
621+ up := slices.SortedFunc(slices.Values(j.upgradedToNode24), func(a, b string) int { return strings.Compare(strings.ToLower(a), strings.ToLower(b)) })
622+ j.mu.Unlock()
623+ if len(up) > 0 {
624+ j.tagged(num, "##[warning]", "Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: "+
625+ strings.Join(up, ", ")+". For more information see: "+node20DeprecationURL)
626+ }
627+
618628 rep.StepCompleted(num, CompleteJobName, result.conclusion(), j.now())
619629 return outputs
620630 }
@@ -8,6 +8,7 @@ import (
8 "fmt"8 "fmt"
9 "os"9 "os"
10 "path/filepath"10 "path/filepath"
11+ "slices"
11 "strings"12 "strings"
12 "sync"13 "sync"
13 "sync/atomic"14 "sync/atomic"
@@ -615,6 +616,15 @@ func (j *job) completeJob(num int) map[string]string {
615 j.out(num, "Cleaning up orphan processes")616 j.out(num, "Cleaning up orphan processes")
616 killOrphans(j.trackID, j.before, func(l string) { j.out(num, l) })617 killOrphans(j.trackID, j.before, func(l string) { j.out(num, l) })
617 618
619+ // The actions that asked for node20 and got node24 (JobExtension.cs:1043-1049).
620+ j.mu.Lock()
621+ up := slices.SortedFunc(slices.Values(j.upgradedToNode24), func(a, b string) int { return strings.Compare(strings.ToLower(a), strings.ToLower(b)) })
622+ j.mu.Unlock()
623+ if len(up) > 0 {
624+ j.tagged(num, "##[warning]", "Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: "+
625+ strings.Join(up, ", ")+". For more information see: "+node20DeprecationURL)
626+ }
627+
618 rep.StepCompleted(num, CompleteJobName, result.conclusion(), j.now())628 rep.StepCompleted(num, CompleteJobName, result.conclusion(), j.now())
619 return outputs629 return outputs
620 }630 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close