From d0cafc58c4acf5de61d0764811daa08936161c0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:40:06 +0000 Subject: [PATCH] Avoid action metadata parser backtracking --- __tests__/main.test.ts | 1 + dist/cache-save/index.js | 30 +++++++++++++++++++++++++++--- dist/setup/index.js | 30 +++++++++++++++++++++++++++--- src/util.ts | 37 ++++++++++++++++++++++++++++++++----- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index c1f52b72..9b18cea6 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -198,6 +198,7 @@ describe('main tests', () => { ${'{"devEngines": {"runtime": {"name": "node", "version": "22.0.0"}}}'} | ${'22.0.0'} ${'{"devEngines": {"runtime": [{"name": "bun"}, {"name": "node", "version": "22.0.0"}]}}'} | ${'22.0.0'} ${"name: 'My Action'\nruns:\n using: 'node24'\n main: 'dist/index.js'\n"} | ${'24'} + ${'runs:\n using: node24 # action runtime\n'} | ${'24'} `.it('parses "$contents"', ({contents, expected}: any) => { const existsSpy = jest.spyOn(fs, 'existsSync'); existsSpy.mockImplementation(() => true); diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index fd680b98..054361b7 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -92709,9 +92709,33 @@ function getNodeVersionFromFile(versionFilePath) { catch { core.info('Node version file is not JSON file'); } - const actionRuntime = contents.match(/^runs\s*:\s*$(?:\r?\n(?:[ \t]+[^\r\n]*|[ \t]*))*?\r?\n[ \t]+using:\s*['"]?node(?\d+)['"]?\s*(?:#.*)?$/m); - if (actionRuntime?.groups?.version) { - return actionRuntime.groups.version; + let inRunsSection = false; + for (const line of contents.split(/\r?\n/)) { + const trimmedLine = line.trim(); + if (!inRunsSection) { + inRunsSection = + trimmedLine === 'runs:' || trimmedLine.startsWith('runs: #'); + continue; + } + if (!trimmedLine || trimmedLine.startsWith('#')) { + continue; + } + if (!line.match(/^\s/)) { + break; + } + const separatorIndex = trimmedLine.indexOf(':'); + if (trimmedLine.slice(0, separatorIndex) !== 'using') { + continue; + } + let runtime = trimmedLine.slice(separatorIndex + 1).trim(); + runtime = runtime.split('#', 1)[0].trim(); + if ((runtime.startsWith("'") && runtime.endsWith("'")) || + (runtime.startsWith('"') && runtime.endsWith('"'))) { + runtime = runtime.slice(1, -1); + } + if (runtime.startsWith('node') && /^\d+$/.test(runtime.slice(4))) { + return runtime.slice(4); + } } const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); return found?.groups?.version ?? contents.trim(); diff --git a/dist/setup/index.js b/dist/setup/index.js index 8307c1d0..55eab84d 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -98103,9 +98103,33 @@ function getNodeVersionFromFile(versionFilePath) { catch { core_info('Node version file is not JSON file'); } - const actionRuntime = contents.match(/^runs\s*:\s*$(?:\r?\n(?:[ \t]+[^\r\n]*|[ \t]*))*?\r?\n[ \t]+using:\s*['"]?node(?\d+)['"]?\s*(?:#.*)?$/m); - if (actionRuntime?.groups?.version) { - return actionRuntime.groups.version; + let inRunsSection = false; + for (const line of contents.split(/\r?\n/)) { + const trimmedLine = line.trim(); + if (!inRunsSection) { + inRunsSection = + trimmedLine === 'runs:' || trimmedLine.startsWith('runs: #'); + continue; + } + if (!trimmedLine || trimmedLine.startsWith('#')) { + continue; + } + if (!line.match(/^\s/)) { + break; + } + const separatorIndex = trimmedLine.indexOf(':'); + if (trimmedLine.slice(0, separatorIndex) !== 'using') { + continue; + } + let runtime = trimmedLine.slice(separatorIndex + 1).trim(); + runtime = runtime.split('#', 1)[0].trim(); + if ((runtime.startsWith("'") && runtime.endsWith("'")) || + (runtime.startsWith('"') && runtime.endsWith('"'))) { + runtime = runtime.slice(1, -1); + } + if (runtime.startsWith('node') && /^\d+$/.test(runtime.slice(4))) { + return runtime.slice(4); + } } const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); return found?.groups?.version ?? contents.trim(); diff --git a/src/util.ts b/src/util.ts index d9816fd5..938bb861 100644 --- a/src/util.ts +++ b/src/util.ts @@ -68,11 +68,38 @@ export function getNodeVersionFromFile(versionFilePath: string): string | null { core.info('Node version file is not JSON file'); } - const actionRuntime = contents.match( - /^runs\s*:\s*$(?:\r?\n(?:[ \t]+[^\r\n]*|[ \t]*))*?\r?\n[ \t]+using:\s*['"]?node(?\d+)['"]?\s*(?:#.*)?$/m - ); - if (actionRuntime?.groups?.version) { - return actionRuntime.groups.version; + let inRunsSection = false; + for (const line of contents.split(/\r?\n/)) { + const trimmedLine = line.trim(); + if (!inRunsSection) { + inRunsSection = + trimmedLine === 'runs:' || trimmedLine.startsWith('runs: #'); + continue; + } + + if (!trimmedLine || trimmedLine.startsWith('#')) { + continue; + } + if (!line.match(/^\s/)) { + break; + } + + const separatorIndex = trimmedLine.indexOf(':'); + if (trimmedLine.slice(0, separatorIndex) !== 'using') { + continue; + } + + let runtime = trimmedLine.slice(separatorIndex + 1).trim(); + runtime = runtime.split('#', 1)[0].trim(); + if ( + (runtime.startsWith("'") && runtime.endsWith("'")) || + (runtime.startsWith('"') && runtime.endsWith('"')) + ) { + runtime = runtime.slice(1, -1); + } + if (runtime.startsWith('node') && /^\d+$/.test(runtime.slice(4))) { + return runtime.slice(4); + } } const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m);