diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index b937b383a..acc500fc1 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -162,9 +162,6 @@ jobs: [.nvmrc, .tool-versions, .tool-versions-node, package.json] steps: - uses: actions/checkout@v4 - - name: Remove volta from package.json - shell: bash - run: cat <<< "$(jq 'del(.volta)' ./__tests__/data/package.json)" > ./__tests__/data/package.json - name: Setup node from node version file uses: ./ with: @@ -183,7 +180,22 @@ jobs: - name: Setup node from node version file uses: ./ with: - node-version-file: '__tests__/data/package.json' + node-version-file: '__tests__/data/package-volta.json' + - name: Verify node + run: __tests__/verify-node.sh 16 + + version-file-volta-extends: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + - name: Setup node from node version file + uses: ./ + with: + node-version-file: '__tests__/data/package-volta-extends.json' - name: Verify node run: __tests__/verify-node.sh 16 diff --git a/__tests__/data/package-volta-extends.json b/__tests__/data/package-volta-extends.json new file mode 100644 index 000000000..ee6d7451c --- /dev/null +++ b/__tests__/data/package-volta-extends.json @@ -0,0 +1,5 @@ +{ + "volta": { + "extends": "./package-volta.json" + } +} diff --git a/__tests__/data/package-volta.json b/__tests__/data/package-volta.json new file mode 100644 index 000000000..ebee7dfc7 --- /dev/null +++ b/__tests__/data/package-volta.json @@ -0,0 +1,8 @@ +{ + "engines": { + "node": "^14.0.0" + }, + "volta": { + "node": "16.0.0" + } +} diff --git a/__tests__/data/package.json b/__tests__/data/package.json index ebee7dfc7..b201009d6 100644 --- a/__tests__/data/package.json +++ b/__tests__/data/package.json @@ -1,8 +1,5 @@ { "engines": { "node": "^14.0.0" - }, - "volta": { - "node": "16.0.0" } } diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 36024e650..501741a6b 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -2,6 +2,7 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; import * as cache from '@actions/cache'; +import * as io from '@actions/io'; import fs from 'fs'; import path from 'path'; @@ -24,11 +25,13 @@ describe('main tests', () => { let startGroupSpy: jest.SpyInstance; let endGroupSpy: jest.SpyInstance; + let whichSpy: jest.SpyInstance; + let existsSpy: jest.SpyInstance; let getExecOutputSpy: jest.SpyInstance; - let parseNodeVersionSpy: jest.SpyInstance; + let getNodeVersionFromFileSpy: jest.SpyInstance; let cnSpy: jest.SpyInstance; let findSpy: jest.SpyInstance; let isCacheActionAvailable: jest.SpyInstance; @@ -41,6 +44,7 @@ describe('main tests', () => { // node os = {}; console.log('::stop-commands::stoptoken'); + process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out infoSpy = jest.spyOn(core, 'info'); @@ -56,18 +60,18 @@ describe('main tests', () => { inSpy = jest.spyOn(core, 'getInput'); inSpy.mockImplementation(name => inputs[name]); + whichSpy = jest.spyOn(io, 'which'); + getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); findSpy = jest.spyOn(tc, 'find'); isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable'); - existsSpy = jest.spyOn(fs, 'existsSync'); - cnSpy = jest.spyOn(process.stdout, 'write'); cnSpy.mockImplementation(line => { // uncomment to debug - // process.stderr.write('write:' + line + '\n'); + process.stderr.write('write:' + line + '\n'); }); setupNodeJsSpy = jest.spyOn(OfficialBuilds.prototype, 'setupNodeJs'); @@ -85,7 +89,7 @@ describe('main tests', () => { jest.restoreAllMocks(); }, 100000); - describe('parseNodeVersionFile', () => { + describe('getNodeVersionFromFile', () => { each` contents | expected ${'12'} | ${'12'} @@ -100,10 +104,27 @@ describe('main tests', () => { ${'unknown format'} | ${'unknown format'} ${' 14.1.0 '} | ${'14.1.0'} ${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'} + ${'{"volta": {"extends": "./package.json"}}'}| ${'18.0.0'} ${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'} ${'{}'} | ${null} `.it('parses "$contents"', ({contents, expected}) => { - expect(util.parseNodeVersionFile(contents)).toBe(expected); + const existsSpy = jest.spyOn(fs, 'existsSync'); + existsSpy.mockImplementation(() => true); + + const readFileSpy = jest.spyOn(fs, 'readFileSync'); + readFileSpy.mockImplementation(filePath => { + if ( + typeof filePath === 'string' && + path.basename(filePath) === 'package.json' + ) { + // Special case for volta.extends + return '{"volta": {"node": "18.0.0"}}'; + } + + return contents; + }); + + expect(util.getNodeVersionFromFile('file')).toBe(expected); }); }); @@ -126,6 +147,10 @@ describe('main tests', () => { return {stdout: obj[command], stderr: '', exitCode: 0}; }); + whichSpy.mockImplementation(cmd => { + return `some/${cmd}/path`; + }); + await util.printEnvDetailsAndSetOutput(); expect(setOutputSpy).toHaveBeenCalledWith('node-version', obj['node']); @@ -142,10 +167,17 @@ describe('main tests', () => { describe('node-version-file flag', () => { beforeEach(() => { - parseNodeVersionSpy = jest.spyOn(util, 'parseNodeVersionFile'); + delete inputs['node-version']; + inputs['node-version-file'] = '.nvmrc'; + + getNodeVersionFromFileSpy = jest.spyOn(util, 'getNodeVersionFromFile'); + }); + + afterEach(() => { + getNodeVersionFromFileSpy.mockRestore(); }); - it('not used if node-version is provided', async () => { + it('does not read node-version-file if node-version is provided', async () => { // Arrange inputs['node-version'] = '12'; @@ -153,107 +185,54 @@ describe('main tests', () => { await main.run(); // Assert - expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); - }, 10000); - - it('not used if node-version-file not provided', async () => { - // Act - await main.run(); - - // Assert - expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); + expect(inputs['node-version']).toBeDefined(); + expect(inputs['node-version-file']).toBeDefined(); + expect(getNodeVersionFromFileSpy).not.toHaveBeenCalled(); + expect(warningSpy).toHaveBeenCalledWith( + 'Both node-version and node-version-file inputs are specified, only node-version will be used' + ); }); - it('reads node-version-file if provided', async () => { + it('does not read node-version-file if node-version-file is not provided', async () => { // Arrange - const versionSpec = 'v14'; - const versionFile = '.nvmrc'; - const expectedVersionSpec = '14'; - process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); - inputs['node-version-file'] = versionFile; - - parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec); - existsSpy.mockImplementationOnce( - input => input === path.join(__dirname, 'data', versionFile) - ); + delete inputs['node-version-file']; // Act await main.run(); // Assert - expect(existsSpy).toHaveBeenCalledTimes(1); - expect(existsSpy).toHaveReturnedWith(true); - expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec); - expect(infoSpy).toHaveBeenCalledWith( - `Resolved ${versionFile} as ${expectedVersionSpec}` - ); - }, 10000); + expect(getNodeVersionFromFileSpy).not.toHaveBeenCalled(); + }); - it('reads package.json as node-version-file if provided', async () => { + it('reads node-version-file', async () => { // Arrange - const versionSpec = fs.readFileSync( - path.join(__dirname, 'data/package.json'), - 'utf-8' - ); - const versionFile = 'package.json'; const expectedVersionSpec = '14'; - process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); - inputs['node-version-file'] = versionFile; + getNodeVersionFromFileSpy.mockImplementation(() => expectedVersionSpec); - parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec); - existsSpy.mockImplementationOnce( - input => input === path.join(__dirname, 'data', versionFile) - ); // Act await main.run(); // Assert - expect(existsSpy).toHaveBeenCalledTimes(1); - expect(existsSpy).toHaveReturnedWith(true); - expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec); + expect(getNodeVersionFromFileSpy).toHaveBeenCalled(); expect(infoSpy).toHaveBeenCalledWith( - `Resolved ${versionFile} as ${expectedVersionSpec}` + `Resolved ${inputs['node-version-file']} as ${expectedVersionSpec}` ); }, 10000); - it('both node-version-file and node-version are provided', async () => { - inputs['node-version'] = '12'; - const versionSpec = 'v14'; - const versionFile = '.nvmrc'; - const expectedVersionSpec = '14'; - process.env['GITHUB_WORKSPACE'] = path.join(__dirname, '..'); - inputs['node-version-file'] = versionFile; - - parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec); - - // Act - await main.run(); - - // Assert - expect(existsSpy).toHaveBeenCalledTimes(0); - expect(parseNodeVersionSpy).not.toHaveBeenCalled(); - expect(warningSpy).toHaveBeenCalledWith( - 'Both node-version and node-version-file inputs are specified, only node-version will be used' - ); - }); - - it('should throw an error if node-version-file is not found', async () => { - const versionFile = '.nvmrc'; - const versionFilePath = path.join(__dirname, '..', versionFile); - inputs['node-version-file'] = versionFile; - - inSpy.mockImplementation(name => inputs[name]); - existsSpy.mockImplementationOnce( - input => input === path.join(__dirname, 'data', versionFile) + it('should throw an error if node-version-file is not accessible', async () => { + // Arrange + inputs['node-version-file'] = 'non-existing-file'; + const versionFilePath = path.join( + __dirname, + 'data', + inputs['node-version-file'] ); // Act await main.run(); // Assert - expect(existsSpy).toHaveBeenCalled(); - expect(existsSpy).toHaveReturnedWith(false); - expect(parseNodeVersionSpy).not.toHaveBeenCalled(); + expect(getNodeVersionFromFileSpy).toHaveBeenCalled(); expect(cnSpy).toHaveBeenCalledWith( `::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}` ); diff --git a/__tests__/official-installer.test.ts b/__tests__/official-installer.test.ts index 2d36c19c7..2d8f17cfa 100644 --- a/__tests__/official-installer.test.ts +++ b/__tests__/official-installer.test.ts @@ -248,6 +248,9 @@ describe('setup-node', () => { const toolPath = path.normalize('/cache/node/12.16.2/x64'); exSpy.mockImplementation(async () => '/some/other/temp/path'); cacheSpy.mockImplementation(async () => toolPath); + whichSpy.mockImplementation(cmd => { + return `some/${cmd}/path`; + }); await main.run(); diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index ece5ae39a..3ced1ad3f 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -83329,54 +83329,67 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.unique = exports.printEnvDetailsAndSetOutput = exports.parseNodeVersionFile = void 0; +exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); -function parseNodeVersionFile(contents) { - var _a, _b, _c; - let nodeVersion; +const io = __importStar(__nccwpck_require__(7436)); +const fs_1 = __importDefault(__nccwpck_require__(7147)); +const path_1 = __importDefault(__nccwpck_require__(1017)); +function getNodeVersionFromFile(versionFilePath) { + var _a, _b, _c, _d, _e; + if (!fs_1.default.existsSync(versionFilePath)) { + throw new Error(`The specified node version file at: ${versionFilePath} does not exist`); + } + const contents = fs_1.default.readFileSync(versionFilePath, 'utf8'); // Try parsing the file as an NPM `package.json` file. try { const manifest = JSON.parse(contents); - // JSON can parse numbers, but that's handled later - if (typeof manifest === 'object') { - nodeVersion = (_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node; - if (!nodeVersion) - nodeVersion = (_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node; - // if contents are an object, we parsed JSON + // Presume package.json file. + if (typeof manifest === 'object' && !!manifest) { + // Support Volta. + // See https://docs.volta.sh/guide/understanding#managing-your-project + if ((_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node) { + return manifest.volta.node; + } + if ((_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node) { + return manifest.engines.node; + } + // Support Volta workspaces. + // See https://docs.volta.sh/advanced/workspaces + if ((_c = manifest.volta) === null || _c === void 0 ? void 0 : _c.extends) { + const extendedFilePath = path_1.default.resolve(path_1.default.dirname(versionFilePath), manifest.volta.extends); + core.info('Resolving node version from ' + extendedFilePath); + return getNodeVersionFromFile(extendedFilePath); + } + // If contents are an object, we parsed JSON // this can happen if node-version-file is a package.json // yet contains no volta.node or engines.node // - // if node-version file is _not_ json, control flow + // If node-version file is _not_ JSON, control flow // will not have reached these lines. // // And because we've reached here, we know the contents // *are* JSON, so no further string parsing makes sense. - if (!nodeVersion) { - return null; - } + return null; } } - catch (_d) { + catch (_f) { core.info('Node version file is not JSON file'); } - if (!nodeVersion) { - const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); - nodeVersion = (_c = found === null || found === void 0 ? void 0 : found.groups) === null || _c === void 0 ? void 0 : _c.version; - } - // In the case of an unknown format, - // return as is and evaluate the version separately. - if (!nodeVersion) - nodeVersion = contents.trim(); - return nodeVersion; + const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); + return (_e = (_d = found === null || found === void 0 ? void 0 : found.groups) === null || _d === void 0 ? void 0 : _d.version) !== null && _e !== void 0 ? _e : contents.trim(); } -exports.parseNodeVersionFile = parseNodeVersionFile; +exports.getNodeVersionFromFile = getNodeVersionFromFile; function printEnvDetailsAndSetOutput() { return __awaiter(this, void 0, void 0, function* () { core.startGroup('Environment details'); const promises = ['node', 'npm', 'yarn'].map((tool) => __awaiter(this, void 0, void 0, function* () { - const output = yield getToolVersion(tool, ['--version']); + const pathTool = yield io.which(tool, false); + const output = pathTool ? yield getToolVersion(tool, ['--version']) : ''; return { tool, output }; })); const tools = yield Promise.all(promises); diff --git a/dist/setup/index.js b/dist/setup/index.js index c4b448b1d..90558a8ea 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -93110,7 +93110,11 @@ class BaseDistribution { const fileName = this.osPlat == 'win32' ? `node-v${version}-win-${osArch}` : `node-v${version}-${this.osPlat}-${osArch}`; - const urlFileName = this.osPlat == 'win32' ? `${fileName}.7z` : `${fileName}.tar.gz`; + const urlFileName = this.osPlat == 'win32' + ? this.nodeInfo.arch === 'arm64' + ? `${fileName}.zip` + : `${fileName}.7z` + : `${fileName}.tar.gz`; const initialUrl = this.getDistributionUrl(); const url = `${initialUrl}/v${version}/${urlFileName}`; return { @@ -93194,10 +93198,23 @@ class BaseDistribution { let extPath; info = info || {}; // satisfy compiler, never null when reaches here if (this.osPlat == 'win32') { - const _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe'); - extPath = yield tc.extract7z(downloadPath, undefined, _7zPath); + const extension = this.nodeInfo.arch === 'arm64' ? '.zip' : '.7z'; + // Rename archive to add extension because after downloading + // archive does not contain extension type and it leads to some issues + // on Windows runners without PowerShell Core. + // + // For default PowerShell Windows it should contain extension type to unpack it. + if (extension === '.zip') { + const renamedArchive = `${downloadPath}.zip`; + fs_1.default.renameSync(downloadPath, renamedArchive); + extPath = yield tc.extractZip(renamedArchive); + } + else { + const _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe'); + extPath = yield tc.extract7z(downloadPath, undefined, _7zPath); + } // 7z extracts to folder matching file name - const nestedPath = path.join(extPath, path.basename(info.fileName, '.7z')); + const nestedPath = path.join(extPath, path.basename(info.fileName, extension)); if (fs_1.default.existsSync(nestedPath)) { extPath = nestedPath; } @@ -93229,7 +93246,12 @@ class BaseDistribution { dataFileName = `osx-${osArch}-tar`; break; case 'win32': - dataFileName = `win-${osArch}-exe`; + if (this.nodeInfo.arch === 'arm64') { + dataFileName = `win-${osArch}-zip`; + } + else { + dataFileName = `win-${osArch}-exe`; + } break; default: throw new Error(`Unexpected OS '${this.osPlat}'`); @@ -93650,7 +93672,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.run = void 0; const core = __importStar(__nccwpck_require__(2186)); -const fs_1 = __importDefault(__nccwpck_require__(7147)); const os_1 = __importDefault(__nccwpck_require__(2037)); const auth = __importStar(__nccwpck_require__(7573)); const path = __importStar(__nccwpck_require__(1017)); @@ -93725,10 +93746,7 @@ function resolveVersionInput() { } if (versionFileInput) { const versionFilePath = path.join(process.env.GITHUB_WORKSPACE, versionFileInput); - if (!fs_1.default.existsSync(versionFilePath)) { - throw new Error(`The specified node version file at: ${versionFilePath} does not exist`); - } - const parsedVersion = (0, util_1.parseNodeVersionFile)(fs_1.default.readFileSync(versionFilePath, 'utf8')); + const parsedVersion = (0, util_1.getNodeVersionFromFile)(versionFilePath); if (parsedVersion) { version = parsedVersion; } @@ -93780,54 +93798,67 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.unique = exports.printEnvDetailsAndSetOutput = exports.parseNodeVersionFile = void 0; +exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); -function parseNodeVersionFile(contents) { - var _a, _b, _c; - let nodeVersion; +const io = __importStar(__nccwpck_require__(7436)); +const fs_1 = __importDefault(__nccwpck_require__(7147)); +const path_1 = __importDefault(__nccwpck_require__(1017)); +function getNodeVersionFromFile(versionFilePath) { + var _a, _b, _c, _d, _e; + if (!fs_1.default.existsSync(versionFilePath)) { + throw new Error(`The specified node version file at: ${versionFilePath} does not exist`); + } + const contents = fs_1.default.readFileSync(versionFilePath, 'utf8'); // Try parsing the file as an NPM `package.json` file. try { const manifest = JSON.parse(contents); - // JSON can parse numbers, but that's handled later - if (typeof manifest === 'object') { - nodeVersion = (_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node; - if (!nodeVersion) - nodeVersion = (_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node; - // if contents are an object, we parsed JSON + // Presume package.json file. + if (typeof manifest === 'object' && !!manifest) { + // Support Volta. + // See https://docs.volta.sh/guide/understanding#managing-your-project + if ((_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node) { + return manifest.volta.node; + } + if ((_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node) { + return manifest.engines.node; + } + // Support Volta workspaces. + // See https://docs.volta.sh/advanced/workspaces + if ((_c = manifest.volta) === null || _c === void 0 ? void 0 : _c.extends) { + const extendedFilePath = path_1.default.resolve(path_1.default.dirname(versionFilePath), manifest.volta.extends); + core.info('Resolving node version from ' + extendedFilePath); + return getNodeVersionFromFile(extendedFilePath); + } + // If contents are an object, we parsed JSON // this can happen if node-version-file is a package.json // yet contains no volta.node or engines.node // - // if node-version file is _not_ json, control flow + // If node-version file is _not_ JSON, control flow // will not have reached these lines. // // And because we've reached here, we know the contents // *are* JSON, so no further string parsing makes sense. - if (!nodeVersion) { - return null; - } + return null; } } - catch (_d) { + catch (_f) { core.info('Node version file is not JSON file'); } - if (!nodeVersion) { - const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); - nodeVersion = (_c = found === null || found === void 0 ? void 0 : found.groups) === null || _c === void 0 ? void 0 : _c.version; - } - // In the case of an unknown format, - // return as is and evaluate the version separately. - if (!nodeVersion) - nodeVersion = contents.trim(); - return nodeVersion; + const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); + return (_e = (_d = found === null || found === void 0 ? void 0 : found.groups) === null || _d === void 0 ? void 0 : _d.version) !== null && _e !== void 0 ? _e : contents.trim(); } -exports.parseNodeVersionFile = parseNodeVersionFile; +exports.getNodeVersionFromFile = getNodeVersionFromFile; function printEnvDetailsAndSetOutput() { return __awaiter(this, void 0, void 0, function* () { core.startGroup('Environment details'); const promises = ['node', 'npm', 'yarn'].map((tool) => __awaiter(this, void 0, void 0, function* () { - const output = yield getToolVersion(tool, ['--version']); + const pathTool = yield io.which(tool, false); + const output = pathTool ? yield getToolVersion(tool, ['--version']) : ''; return { tool, output }; })); const tools = yield Promise.all(promises); diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 079b8bfac..bf62e0713 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -84,6 +84,8 @@ When using the `package.json` input, the action will look for `volta.node` first } ``` +Otherwise, when [`volta.extends`](https://docs.volta.sh/advanced/workspaces) is defined, then it will resolve the corresponding file and look for `volta.node` or `engines.node` recursively. + ## Architecture You can use any of the [supported operating systems](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners), and the compatible `architecture` can be selected using `architecture`. Values are `x86`, `x64`, `arm64`, `armv6l`, `armv7l`, `ppc64le`, `s390x` (not all of the architectures are available on all platforms). diff --git a/src/distributions/base-distribution.ts b/src/distributions/base-distribution.ts index edac6b9b1..cf5bb5449 100644 --- a/src/distributions/base-distribution.ts +++ b/src/distributions/base-distribution.ts @@ -112,7 +112,11 @@ export default abstract class BaseDistribution { ? `node-v${version}-win-${osArch}` : `node-v${version}-${this.osPlat}-${osArch}`; const urlFileName: string = - this.osPlat == 'win32' ? `${fileName}.7z` : `${fileName}.tar.gz`; + this.osPlat == 'win32' + ? this.nodeInfo.arch === 'arm64' + ? `${fileName}.zip` + : `${fileName}.7z` + : `${fileName}.tar.gz`; const initialUrl = this.getDistributionUrl(); const url = `${initialUrl}/v${version}/${urlFileName}`; @@ -215,12 +219,24 @@ export default abstract class BaseDistribution { let extPath: string; info = info || ({} as INodeVersionInfo); // satisfy compiler, never null when reaches here if (this.osPlat == 'win32') { - const _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe'); - extPath = await tc.extract7z(downloadPath, undefined, _7zPath); + const extension = this.nodeInfo.arch === 'arm64' ? '.zip' : '.7z'; + // Rename archive to add extension because after downloading + // archive does not contain extension type and it leads to some issues + // on Windows runners without PowerShell Core. + // + // For default PowerShell Windows it should contain extension type to unpack it. + if (extension === '.zip') { + const renamedArchive = `${downloadPath}.zip`; + fs.renameSync(downloadPath, renamedArchive); + extPath = await tc.extractZip(renamedArchive); + } else { + const _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe'); + extPath = await tc.extract7z(downloadPath, undefined, _7zPath); + } // 7z extracts to folder matching file name const nestedPath = path.join( extPath, - path.basename(info.fileName, '.7z') + path.basename(info.fileName, extension) ); if (fs.existsSync(nestedPath)) { extPath = nestedPath; @@ -260,7 +276,11 @@ export default abstract class BaseDistribution { dataFileName = `osx-${osArch}-tar`; break; case 'win32': - dataFileName = `win-${osArch}-exe`; + if (this.nodeInfo.arch === 'arm64') { + dataFileName = `win-${osArch}-zip`; + } else { + dataFileName = `win-${osArch}-exe`; + } break; default: throw new Error(`Unexpected OS '${this.osPlat}'`); diff --git a/src/main.ts b/src/main.ts index 34f943104..c55c3b005 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ import * as core from '@actions/core'; -import fs from 'fs'; import os from 'os'; import * as auth from './authutil'; @@ -8,7 +7,7 @@ import * as path from 'path'; import {restoreCache} from './cache-restore'; import {isCacheFeatureAvailable} from './cache-utils'; import {getNodejsDistribution} from './distributions/installer-factory'; -import {parseNodeVersionFile, printEnvDetailsAndSetOutput} from './util'; +import {getNodeVersionFromFile, printEnvDetailsAndSetOutput} from './util'; import {State} from './constants'; export async function run() { @@ -99,15 +98,7 @@ function resolveVersionInput(): string { versionFileInput ); - if (!fs.existsSync(versionFilePath)) { - throw new Error( - `The specified node version file at: ${versionFilePath} does not exist` - ); - } - - const parsedVersion = parseNodeVersionFile( - fs.readFileSync(versionFilePath, 'utf8') - ); + const parsedVersion = getNodeVersionFromFile(versionFilePath); if (parsedVersion) { version = parsedVersion; diff --git a/src/util.ts b/src/util.ts index 0b2b14906..bbe25ddf0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,52 +1,70 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; +import * as io from '@actions/io'; -export function parseNodeVersionFile(contents: string): string | null { - let nodeVersion: string | undefined; +import fs from 'fs'; +import path from 'path'; + +export function getNodeVersionFromFile(versionFilePath: string): string | null { + if (!fs.existsSync(versionFilePath)) { + throw new Error( + `The specified node version file at: ${versionFilePath} does not exist` + ); + } + + const contents = fs.readFileSync(versionFilePath, 'utf8'); // Try parsing the file as an NPM `package.json` file. try { const manifest = JSON.parse(contents); - // JSON can parse numbers, but that's handled later - if (typeof manifest === 'object') { - nodeVersion = manifest.volta?.node; - if (!nodeVersion) nodeVersion = manifest.engines?.node; + // Presume package.json file. + if (typeof manifest === 'object' && !!manifest) { + // Support Volta. + // See https://docs.volta.sh/guide/understanding#managing-your-project + if (manifest.volta?.node) { + return manifest.volta.node; + } - // if contents are an object, we parsed JSON + if (manifest.engines?.node) { + return manifest.engines.node; + } + + // Support Volta workspaces. + // See https://docs.volta.sh/advanced/workspaces + if (manifest.volta?.extends) { + const extendedFilePath = path.resolve( + path.dirname(versionFilePath), + manifest.volta.extends + ); + core.info('Resolving node version from ' + extendedFilePath); + return getNodeVersionFromFile(extendedFilePath); + } + + // If contents are an object, we parsed JSON // this can happen if node-version-file is a package.json // yet contains no volta.node or engines.node // - // if node-version file is _not_ json, control flow + // If node-version file is _not_ JSON, control flow // will not have reached these lines. // // And because we've reached here, we know the contents // *are* JSON, so no further string parsing makes sense. - if (!nodeVersion) { - return null; - } + return null; } } catch { core.info('Node version file is not JSON file'); } - if (!nodeVersion) { - const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); - nodeVersion = found?.groups?.version; - } - - // In the case of an unknown format, - // return as is and evaluate the version separately. - if (!nodeVersion) nodeVersion = contents.trim(); - - return nodeVersion as string; + const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); + return found?.groups?.version ?? contents.trim(); } export async function printEnvDetailsAndSetOutput() { core.startGroup('Environment details'); - const promises = ['node', 'npm', 'yarn'].map(async tool => { - const output = await getToolVersion(tool, ['--version']); + const pathTool = await io.which(tool, false); + const output = pathTool ? await getToolVersion(tool, ['--version']) : ''; return {tool, output}; }); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy