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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +19 -0
M internal/executor/actions_node.go +5 -0
| @@ -7,6 +7,8 @@ import ( | |||
| 7 | 7 | "os" | |
| 8 | 8 | "path/filepath" | |
| 9 | 9 | "strings" | |
| 10 | + | ||
| 11 | + | "bnema.dev/ermite-actions/action" | |
| 10 | 12 | ) | |
| 11 | 13 | ||
| 12 | 14 | const ( | |
| @@ -129,6 +131,9 @@ func appendUnique(l []string, v string) []string { | |||
| 129 | 131 | ||
| 130 | 132 | // actionName is HandlerFactory.GetActionName: "Name[/Path]@Ref". | |
| 131 | 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 | 137 | return registeredRef(n.uses) | |
| 133 | 138 | } | |
| 134 | 139 | ||
| @@ -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) { | |||
| 223 | 223 | if got := run("node20", " FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n", nil); !strings.Contains(got, "[node24]") { | |
| 224 | 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 | 230 | by24 := func(c *Config) { c.Actions.Node24ByDefault = true } | |
| 227 | 231 | got := run("node20", "", by24) | |
| 228 | 232 | 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 ( | |||
| 8 | 8 | "fmt" | |
| 9 | 9 | "os" | |
| 10 | 10 | "path/filepath" | |
| 11 | + | "slices" | |
| 11 | 12 | "strings" | |
| 12 | 13 | "sync" | |
| 13 | 14 | "sync/atomic" | |
| @@ -615,6 +616,15 @@ func (j *job) completeJob(num int) map[string]string { | |||
| 615 | 616 | j.out(num, "Cleaning up orphan processes") | |
| 616 | 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 | 628 | rep.StepCompleted(num, CompleteJobName, result.conclusion(), j.now()) | |
| 619 | 629 | return outputs | |
| 620 | 630 | } | |
| @@ -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 outputs | 629 | return outputs | ||
| 620 | } | 630 | } | ||