diff --git a/docs/checks/nodejs.md b/docs/checks/nodejs.md index 7308dcadc50..62b2958cd95 100644 --- a/docs/checks/nodejs.md +++ b/docs/checks/nodejs.md @@ -4,9 +4,9 @@ Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server. -The runner carries its own copy of node.js executable under `/externals/node20/`. +The runner carries its own copies of node.js executables under `/externals/node20/` and `/externals/node24/`. -All javascript base Actions will get executed by the built-in `node` at `/externals/node20/`. +All javascript base Actions will get executed by the built-in `node` at either `/externals/node20/` or `/externals/node24/` depending on the version specified in the action's metadata. > Not the `node` from `$PATH` diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh index c6c893a0be6..7f8baf4c8b1 100755 --- a/src/Misc/externals.sh +++ b/src/Misc/externals.sh @@ -7,6 +7,7 @@ NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download # When you update Node versions you must also create a new release of alpine_nodejs at that updated version. # Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started NODE20_VERSION="20.19.3" +NODE24_VERSION="24.4.0" get_abs_path() { # exploits the fact that pwd will print abs path when no args @@ -139,6 +140,8 @@ function acquireExternalTool() { if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin if [[ "$PRECACHE" != "" ]]; then acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere fi @@ -149,6 +152,8 @@ if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then # todo: replace these with official release when available acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin if [[ "$PRECACHE" != "" ]]; then acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere fi @@ -157,21 +162,26 @@ fi # Download the external tools only for OSX. if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-x64.tar.gz" node24 fix_nested_dir fi if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then # node.js v12 doesn't support macOS on arm64. acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-arm64.tar.gz" node24 fix_nested_dir fi # Download the external tools for Linux PACKAGERUNTIMEs. if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir acquireExternalTool "$NODE_ALPINE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_alpine + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-x64.tar.gz" node24 fix_nested_dir + acquireExternalTool "$NODE_ALPINE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-alpine-x64.tar.gz" node24_alpine fi if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-arm64.tar.gz" node24 fix_nested_dir fi if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then diff --git a/src/Misc/layoutbin/update.sh.template b/src/Misc/layoutbin/update.sh.template index 832385edd5f..476ae11e57d 100755 --- a/src/Misc/layoutbin/update.sh.template +++ b/src/Misc/layoutbin/update.sh.template @@ -135,12 +135,17 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then then # inspect the open file handles to find the node process # we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks - nodever="node20" + # Try finding node24 first, then fallback to earlier versions if needed + nodever="node24" path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-) - if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16 + if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node20 then - nodever="node16" + nodever="node20" path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-) + if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16 + then + nodever="node16" + path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-) if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12 then nodever="node12" diff --git a/src/Runner.Common/Util/NodeUtil.cs b/src/Runner.Common/Util/NodeUtil.cs index 1a9252cde0f..65a3278fb02 100644 --- a/src/Runner.Common/Util/NodeUtil.cs +++ b/src/Runner.Common/Util/NodeUtil.cs @@ -18,5 +18,22 @@ public static string GetInternalNodeVersion() } return _defaultNodeVersion; } + + /// + /// Checks if Node24 is requested but running on ARM32 Linux, and determines if fallback is needed. + /// + /// The preferred Node version + /// A tuple containing the adjusted node version and an optional warning message + public static (string nodeVersion, string warningMessage) CheckNodeVersionForLinuxArm32(string preferredVersion) + { + if (string.Equals(preferredVersion, "node24", StringComparison.OrdinalIgnoreCase) && + Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) && + Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux)) + { + return ("node20", "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20."); + } + + return (preferredVersion, null); + } } } diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 351c2427daf..c731b3d5d5c 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -450,7 +450,8 @@ private ActionExecutionData ConvertRuns( } else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) || string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase) || - string.Equals(usingToken.Value, "node20", StringComparison.OrdinalIgnoreCase)) + string.Equals(usingToken.Value, "node20", StringComparison.OrdinalIgnoreCase) || + string.Equals(usingToken.Value, "node24", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrEmpty(mainToken?.Value)) { @@ -490,7 +491,7 @@ private ActionExecutionData ConvertRuns( } else { - throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead."); + throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12', 'node16', 'node20' or 'node24' instead."); } } else if (pluginToken != null) @@ -501,7 +502,7 @@ private ActionExecutionData ConvertRuns( }; } - throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'."); + throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16', 'node20' or 'node24'."); } private void ConvertInputs( diff --git a/src/Runner.Worker/Handlers/StepHost.cs b/src/Runner.Worker/Handlers/StepHost.cs index 1270dd90e6f..211009658e4 100644 --- a/src/Runner.Worker/Handlers/StepHost.cs +++ b/src/Runner.Worker/Handlers/StepHost.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using GitHub.DistributedTask.Pipelines.ContextData; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -9,7 +8,6 @@ using GitHub.Runner.Sdk; using System.Linq; using GitHub.Runner.Worker.Container.ContainerHooks; -using System.IO; using System.Threading.Channels; namespace GitHub.Runner.Worker.Handlers @@ -60,7 +58,14 @@ public string ResolvePathForStepHost(IExecutionContext executionContext, string public Task DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion) { - return Task.FromResult(preferredVersion); + // Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux + var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion); + if (!string.IsNullOrEmpty(warningMessage)) + { + executionContext.Warning(warningMessage); + } + + return Task.FromResult(nodeVersion); } public async Task ExecuteAsync(IExecutionContext context, @@ -137,8 +142,12 @@ public string ResolvePathForStepHost(IExecutionContext executionContext, string public async Task DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion) { - // Optimistically use the default - string nodeExternal = preferredVersion; + // Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux + var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion); + if (!string.IsNullOrEmpty(warningMessage)) + { + executionContext.Warning(warningMessage); + } if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables)) { @@ -264,7 +273,14 @@ await containerHookManager.RunScriptStepAsync(context, private string CheckPlatformForAlpineContainer(IExecutionContext executionContext, string preferredVersion) { - string nodeExternal = preferredVersion; + // Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux + var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion); + if (!string.IsNullOrEmpty(warningMessage)) + { + executionContext.Warning(warningMessage); + } + + // Check for Alpine container compatibility if (!Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.X64)) { var os = Constants.Runner.Platform.ToString(); diff --git a/src/Test/L0/Worker/ActionManagerL0.cs b/src/Test/L0/Worker/ActionManagerL0.cs index 50d5b99d106..328c5b5f61b 100644 --- a/src/Test/L0/Worker/ActionManagerL0.cs +++ b/src/Test/L0/Worker/ActionManagerL0.cs @@ -1659,6 +1659,76 @@ public void LoadsNode20ActionDefinition() Teardown(); } } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void LoadsNode24ActionDefinition() + { + try + { + // Arrange. + Setup(); + const string Content = @" +# Container action +name: 'Hello World' +description: 'Greet the world and record the time' +author: 'GitHub' +inputs: + greeting: # id of input + description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' + required: true + default: 'Hello' + entryPoint: # id of input + description: 'optional docker entrypoint overwrite.' + required: false +outputs: + time: # id of output + description: 'The time we did the greeting' +icon: 'hello.svg' # vector art to display in the GitHub Marketplace +color: 'green' # optional, decorates the entry in the GitHub Marketplace +runs: + using: 'node24' + main: 'task.js' +"; + Pipelines.ActionStep instance; + string directory; + CreateAction(yamlContent: Content, instance: out instance, directory: out directory); + + // Act. + Definition definition = _actionManager.LoadAction(_ec.Object, instance); + + // Assert. + Assert.NotNull(definition); + Assert.Equal(directory, definition.Directory); + Assert.NotNull(definition.Data); + Assert.NotNull(definition.Data.Inputs); // inputs + Dictionary inputDefaults = new(StringComparer.OrdinalIgnoreCase); + foreach (var input in definition.Data.Inputs) + { + var name = input.Key.AssertString("key").Value; + var value = input.Value.AssertScalar("value").ToString(); + + _hc.GetTrace().Info($"Default: {name} = {value}"); + inputDefaults[name] = value; + } + + Assert.Equal(2, inputDefaults.Count); + Assert.True(inputDefaults.ContainsKey("greeting")); + Assert.Equal("Hello", inputDefaults["greeting"]); + Assert.True(string.IsNullOrEmpty(inputDefaults["entryPoint"])); + Assert.NotNull(definition.Data.Execution); // execution + + Assert.NotNull(definition.Data.Execution as NodeJSActionExecutionData); + Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script); + Assert.Equal("node24", (definition.Data.Execution as NodeJSActionExecutionData).NodeVersion); + } + finally + { + Teardown(); + } + } + [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] diff --git a/src/Test/L0/Worker/ActionManifestManagerL0.cs b/src/Test/L0/Worker/ActionManifestManagerL0.cs index 91f604c0645..dae75c8f600 100644 --- a/src/Test/L0/Worker/ActionManifestManagerL0.cs +++ b/src/Test/L0/Worker/ActionManifestManagerL0.cs @@ -502,6 +502,49 @@ public void Load_Node20Action() } } + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void Load_Node24Action() + { + try + { + //Arrange + Setup(); + + var actionManifest = new ActionManifestManager(); + actionManifest.Initialize(_hc); + + //Act + var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "node24action.yml")); + + //Assert + Assert.Equal("Hello World", result.Name); + Assert.Equal("Greet the world and record the time", result.Description); + Assert.Equal(2, result.Inputs.Count); + Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value); + Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value); + Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value); + Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value); + Assert.Equal(1, result.Deprecated.Count); + + Assert.True(result.Deprecated.ContainsKey("greeting")); + result.Deprecated.TryGetValue("greeting", out string value); + Assert.Equal("This property has been deprecated", value); + + Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType); + + var nodeAction = result.Execution as NodeJSActionExecutionData; + + Assert.Equal("main.js", nodeAction.Script); + Assert.Equal("node24", nodeAction.NodeVersion); + } + finally + { + Teardown(); + } + } + [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] @@ -758,7 +801,7 @@ public void Load_CompositeActionNoUsing() //Assert var err = Assert.Throws(() => actionManifest.Load(_ec.Object, action_path)); Assert.Contains($"Failed to load {action_path}", err.Message); - _ec.Verify(x => x.AddIssue(It.Is(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.")), It.IsAny()), Times.Once); + _ec.Verify(x => x.AddIssue(It.Is(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16', 'node20' or 'node24'.")), It.IsAny()), Times.Once); } finally { diff --git a/src/Test/L0/Worker/HandlerFactoryL0.cs b/src/Test/L0/Worker/HandlerFactoryL0.cs index 5f88d5211d0..37981e46aa9 100644 --- a/src/Test/L0/Worker/HandlerFactoryL0.cs +++ b/src/Test/L0/Worker/HandlerFactoryL0.cs @@ -33,6 +33,7 @@ private TestHostContext CreateTestContext([CallerMemberName] string testName = " [InlineData("node12", "node20")] [InlineData("node16", "node20")] [InlineData("node20", "node20")] + [InlineData("node24", "node24")] public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion) { using (TestHostContext hc = CreateTestContext()) @@ -41,7 +42,7 @@ public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion) var hf = new HandlerFactory(); hf.Initialize(hc); - // Server Feature Flag + // Setup variables var variables = new Dictionary(); Variables serverVariables = new(hc, variables); @@ -72,5 +73,48 @@ public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion) Assert.Equal(expectedVersion, handler.Data.NodeVersion); } } + + + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void Node24ExplicitlyRequested_HonoredByDefault() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + // Basic variables setup + var variables = new Dictionary(); + Variables serverVariables = new(hc, variables); + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = new Dictionary() + }); + + // Act - Node 24 explicitly requested in action.yml + var data = new NodeJSActionExecutionData(); + data.NodeVersion = "node24"; + var handler = hf.Create( + _ec.Object, + new ScriptReference(), + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), + "", + new List() + ) as INodeScriptActionHandler; + + // Assert - should be node24 as requested + Assert.Equal("node24", handler.Data.NodeVersion); + } + } } } diff --git a/src/Test/L0/Worker/Handlers/NodeHandlerL0.cs b/src/Test/L0/Worker/Handlers/NodeHandlerL0.cs new file mode 100644 index 00000000000..78c4053f174 --- /dev/null +++ b/src/Test/L0/Worker/Handlers/NodeHandlerL0.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Sdk; +using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Handlers; +using Moq; +using Xunit; + +namespace GitHub.Runner.Common.Tests.Worker.Handlers +{ + public sealed class NodeHandlerL0 + { + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void NodeJSActionExecutionDataSupportsNode24() + { + // Create NodeJSActionExecutionData with node24 + var nodeJSData = new NodeJSActionExecutionData + { + NodeVersion = "node24", + Script = "test.js" + }; + + // Act & Assert + Assert.Equal("node24", nodeJSData.NodeVersion); + Assert.Equal(ActionExecutionType.NodeJS, nodeJSData.ExecutionType); + } + } +} diff --git a/src/Test/L0/Worker/StepHostL0.cs b/src/Test/L0/Worker/StepHostL0.cs index 47a5d3344da..bac7d41d90a 100644 --- a/src/Test/L0/Worker/StepHostL0.cs +++ b/src/Test/L0/Worker/StepHostL0.cs @@ -162,6 +162,60 @@ public async Task DetermineNode20RuntimeVersionInUnknowContainerAsync() Assert.Equal("node20", nodeVersion); } } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task DetermineNode24RuntimeVersionInAlpineContainerAsync() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var sh = new ContainerStepHost(); + sh.Initialize(hc); + sh.Container = new ContainerInfo() { ContainerId = "1234abcd" }; + + _dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback((IExecutionContext ec, string id, string options, string command, List output) => + { + output.Add("alpine"); + }) + .ReturnsAsync(0); + + // Act. + var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24"); + + // Assert. + Assert.Equal("node24_alpine", nodeVersion); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task DetermineNode24RuntimeVersionInUnknownContainerAsync() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var sh = new ContainerStepHost(); + sh.Initialize(hc); + sh.Container = new ContainerInfo() { ContainerId = "1234abcd" }; + + _dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback((IExecutionContext ec, string id, string options, string command, List output) => + { + output.Add("github"); + }) + .ReturnsAsync(0); + + // Act. + var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24"); + + // Assert. + Assert.Equal("node24", nodeVersion); + } + } #endif } } diff --git a/src/Test/L0/Worker/StepHostNodeVersionL0.cs b/src/Test/L0/Worker/StepHostNodeVersionL0.cs new file mode 100644 index 00000000000..6ba8c9fa4e7 --- /dev/null +++ b/src/Test/L0/Worker/StepHostNodeVersionL0.cs @@ -0,0 +1,63 @@ +using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Handlers; +using Moq; +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace GitHub.Runner.Common.Tests.Worker +{ + public sealed class StepHostNodeVersionL0 + { + private Mock _ec; + private DefaultStepHost _defaultStepHost; + + public StepHostNodeVersionL0() + { + _ec = new Mock(); + _defaultStepHost = new DefaultStepHost(); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void CheckNodeVersionForArm32_Node24OnArm32Linux() + { + // Test via NodeUtil directly + string preferredVersion = "node24"; + var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion); + + // On ARM32 Linux, we should fall back to node20 + bool isArm32 = RuntimeInformation.ProcessArchitecture == Architecture.Arm || + Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")?.Contains("ARM") == true; + bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + if (isArm32 && isLinux) + { + // Should downgrade to node20 on ARM32 Linux + Assert.Equal("node20", nodeVersion); + Assert.NotNull(warningMessage); + Assert.Contains("Node 24 is not supported on Linux ARM32 platforms", warningMessage); + } + else + { + // On non-ARM32 platforms, should pass through the version unmodified + Assert.Equal("node24", nodeVersion); + Assert.Null(warningMessage); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void CheckNodeVersionForArm32_PassThroughNonNode24Versions() + { + string preferredVersion = "node20"; + var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion); + + // Should never modify the version for non-node24 inputs + Assert.Equal("node20", nodeVersion); + Assert.Null(warningMessage); + } + } +} diff --git a/src/Test/TestData/node24action.yml b/src/Test/TestData/node24action.yml new file mode 100644 index 00000000000..653e558a0c0 --- /dev/null +++ b/src/Test/TestData/node24action.yml @@ -0,0 +1,20 @@ +name: 'Hello World' +description: 'Greet the world and record the time' +author: 'Test Corporation' +inputs: + greeting: # id of input + description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout' + required: true + default: 'Hello' + deprecationMessage: 'This property has been deprecated' + entryPoint: # id of input + description: 'optional docker entrypoint overwrite.' + required: false +outputs: + time: # id of output + description: 'The time we did the greeting' +icon: 'hello.svg' # vector art to display in the GitHub Marketplace +color: 'green' # optional, decorates the entry in the GitHub Marketplace +runs: + using: 'node24' + main: 'main.js' \ No newline at end of file