diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 3f963491..1af0c125 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -92,12 +92,13 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] + node-version-file: [.nvmrc, .tool-versions] steps: - uses: actions/checkout@v3 - name: Setup node from node version file uses: ./ with: - node-version-file: '__tests__/data/.nvmrc' + node-version-file: '__tests__/data/${{ matrix.node-version-file }}' - name: Verify node run: __tests__/verify-node.sh 14 diff --git a/README.md b/README.md index 3b9ea0a0..6f44fdc6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 - run: npm ci - run: npm test ``` @@ -39,8 +39,8 @@ The `node-version` input supports the Semantic Versioning Specification, for mor Examples: - - Major versions: `12`, `14`, `16` - - More specific versions: `10.15`, `14.2.0`, `16.3.0` + - Major versions: `14`, `16`, `18` + - More specific versions: `10.15`, `16.15.1` , `18.4.0` - NVM LTS syntax: `lts/erbium`, `lts/fermium`, `lts/*`, `lts/-n` - Latest release: `*` or `latest`/`current`/`node` @@ -58,7 +58,7 @@ It's **always** recommended to commit the lockfile of your package manager for s The action has a built-in functionality for caching and restoring dependencies. It uses [actions/cache](https://github.com/actions/cache) under the hood for caching global packages data but requires less configuration settings. Supported package managers are `npm`, `yarn`, `pnpm` (v6.10+). The `cache` input is optional, and caching is turned off by default. -The action defaults to search for the dependency file (`package-lock.json` or `yarn.lock`) in the repository root, and uses its hash as a part of the cache key. Use `cache-dependency-path` for cases when multiple dependency files are used, or they are located in different subdirectories. +The action defaults to search for the dependency file (`package-lock.json`, `npm-shrinkwrap.json` or `yarn.lock`) in the repository root, and uses its hash as a part of the cache key. Use `cache-dependency-path` for cases when multiple dependency files are used, or they are located in different subdirectories. **Note:** The action does not cache `node_modules` @@ -71,7 +71,7 @@ steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 cache: 'npm' - run: npm ci - run: npm test @@ -84,7 +84,7 @@ steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 cache: 'npm' cache-dependency-path: subdir/package-lock.json - run: npm ci @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [ 12, 14, 16 ] + node: [ 14, 16, 18 ] name: Node ${{ matrix.node }} sample steps: - uses: actions/checkout@v3 diff --git a/__tests__/data/.tool-versions b/__tests__/data/.tool-versions new file mode 100644 index 00000000..317343f3 --- /dev/null +++ b/__tests__/data/.tool-versions @@ -0,0 +1 @@ +nodejs 14.0.0 diff --git a/__tests__/data/package.json b/__tests__/data/package.json index 9fdc65e0..e537e200 100644 --- a/__tests__/data/package.json +++ b/__tests__/data/package.json @@ -1,11 +1,5 @@ { - "name": "test", - "version": "1.0.0", - "private": true, - "scripts": { - "test": "echo test" - }, - "volta": { - "node": "16.15.1" + "engines": { + "node": ">=14.0.0" } } diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 80e1d85b..cb6bdbe2 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -1,12 +1,14 @@ import * as core from '@actions/core'; import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; +import * as exec from '@actions/exec'; import * as im from '../src/installer'; import * as cache from '@actions/cache'; import fs from 'fs'; import cp from 'child_process'; import osm = require('os'); import path from 'path'; +import each from 'jest-each'; import * as main from '../src/main'; import * as auth from '../src/authutil'; @@ -38,6 +40,7 @@ describe('setup-node', () => { let authSpy: jest.SpyInstance; let parseNodeVersionSpy: jest.SpyInstance; let isCacheActionAvailable: jest.SpyInstance; + let getExecOutputSpy: jest.SpyInstance; beforeEach(() => { // @actions/core @@ -103,6 +106,10 @@ describe('setup-node', () => { // uncomment to debug // process.stderr.write('log:' + line + '\n'); }); + + // @actions/exec + getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); + getExecOutputSpy.mockImplementation(() => 'v16.15.0'); }); afterEach(() => { @@ -613,6 +620,33 @@ describe('setup-node', () => { ); }); + it('reads package.json as node-version-file if provided', 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; + + 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(logSpy).toHaveBeenCalledWith( + `Resolved ${versionFile} as ${expectedVersionSpec}` + ); + }); + it('both node-version-file and node-version are provided', async () => { inputs['node-version'] = '12'; const versionSpec = 'v14'; @@ -927,3 +961,23 @@ describe('setup-node', () => { ); }); }); + +describe('helper methods', () => { + describe('parseNodeVersionFile', () => { + each` + contents | expected + ${'12'} | ${'12'} + ${'12.3'} | ${'12.3'} + ${'12.3.4'} | ${'12.3.4'} + ${'v12.3.4'} | ${'12.3.4'} + ${'lts/erbium'} | ${'lts/erbium'} + ${'lts/*'} | ${'lts/*'} + ${'nodejs 12.3.4'} | ${'12.3.4'} + ${'ruby 2.3.4\nnodejs 12.3.4\npython 3.4.5'} | ${'12.3.4'} + ${''} | ${''} + ${'unknown format'} | ${'unknown format'} + `.it('parses "$contents"', ({contents, expected}) => { + expect(im.parseNodeVersionFile(contents)).toBe(expected); + }); + }); +}); diff --git a/action.yml b/action.yml index 7bed73dc..ae2e8243 100644 --- a/action.yml +++ b/action.yml @@ -8,7 +8,7 @@ inputs: node-version: description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.' node-version-file: - description: 'File containing the version Spec of the version to use. Examples: .nvmrc, .node-version.' + description: 'File containing the version Spec of the version to use. Examples: .nvmrc, .node-version, .tool-versions.' architecture: description: 'Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.' check-latest: @@ -30,6 +30,8 @@ inputs: outputs: cache-hit: description: 'A boolean value to indicate if a cache was hit.' + node-version: + description: 'The installed node version.' runs: using: 'node16' main: 'dist/setup/index.js' diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index a55d2327..a79693b2 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -59926,12 +59926,12 @@ const exec = __importStar(__nccwpck_require__(1514)); const cache = __importStar(__nccwpck_require__(7799)); exports.supportedPackageManagers = { npm: { - lockFilePatterns: ['package-lock.json', 'yarn.lock'], + lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'], getCacheFolderCommand: 'npm config get cache' }, pnpm: { lockFilePatterns: ['pnpm-lock.yaml'], - getCacheFolderCommand: 'pnpm store path' + getCacheFolderCommand: 'pnpm store path --silent' }, yarn1: { lockFilePatterns: ['yarn.lock'], @@ -59986,7 +59986,7 @@ exports.getCacheDirectoryPath = (packageManagerInfo, packageManager) => __awaite throw new Error(`Could not get cache folder path for ${packageManager}`); } core.debug(`${packageManager} path is ${stdOut}`); - return stdOut; + return stdOut.trim(); }); function isGhes() { const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); diff --git a/dist/setup/index.js b/dist/setup/index.js index 67c1d96d..0a0c0bad 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -71272,12 +71272,12 @@ const exec = __importStar(__nccwpck_require__(1514)); const cache = __importStar(__nccwpck_require__(7799)); exports.supportedPackageManagers = { npm: { - lockFilePatterns: ['package-lock.json', 'yarn.lock'], + lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'], getCacheFolderCommand: 'npm config get cache' }, pnpm: { lockFilePatterns: ['pnpm-lock.yaml'], - getCacheFolderCommand: 'pnpm store path' + getCacheFolderCommand: 'pnpm store path --silent' }, yarn1: { lockFilePatterns: ['yarn.lock'], @@ -71332,7 +71332,7 @@ exports.getCacheDirectoryPath = (packageManagerInfo, packageManager) => __awaite throw new Error(`Could not get cache folder path for ${packageManager}`); } core.debug(`${packageManager} path is ${stdOut}`); - return stdOut; + return stdOut.trim(); }); function isGhes() { const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); @@ -71768,12 +71768,23 @@ function translateArchToDistUrl(arch) { } } function parseNodeVersionFile(contents) { - let nodeVersion = contents.trim(); - if (contents.includes('volta')) { - nodeVersion = JSON.parse(contents).volta.node; - } - if (/^v\d/.test(nodeVersion)) { - nodeVersion = nodeVersion.substring(1); + var _a, _b; + let nodeVersion; + const found = contents.match(/^(?:nodejs\s+)?v?(?[^\s]+)$/m); + nodeVersion = (_a = found === null || found === void 0 ? void 0 : found.groups) === null || _a === void 0 ? void 0 : _a.version; + if (!nodeVersion) { + try { + // Try parsing the file as an NPM `package.json` + // file. + nodeVersion = (_b = JSON.parse(contents).engines) === null || _b === void 0 ? void 0 : _b.node; + if (!nodeVersion) + throw new Error(); + } + catch (err) { + // In the case of an unknown format, + // return as is and evaluate the version separately. + nodeVersion = contents.trim(); + } } return nodeVersion; } @@ -71811,6 +71822,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); +const exec = __importStar(__nccwpck_require__(1514)); const installer = __importStar(__nccwpck_require__(2574)); const fs_1 = __importDefault(__nccwpck_require__(7147)); const auth = __importStar(__nccwpck_require__(7573)); @@ -71843,6 +71855,14 @@ function run() { const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE'; yield installer.getNode(version, stable, checkLatest, auth, arch); } + // Output version of node is being used + try { + const { stdout: installedVersion } = yield exec.getExecOutput('node', ['--version'], { ignoreReturnCode: true, silent: true }); + core.setOutput('node-version', installedVersion.trim()); + } + catch (err) { + core.setOutput('node-version', ''); + } const registryUrl = core.getInput('registry-url'); const alwaysAuth = core.getInput('always-auth'); if (registryUrl) { diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 17b46370..f795d215 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -56,8 +56,7 @@ steps: ## Node version file -The `node-version-file` input accepts a path to a file containing the version of Node.js to be used by a project, for example `.nvmrc` or `.node-version`. If both the `node-version` and the `node-version-file` inputs are provided then the `node-version` input is used. In the special case of using `package.json`, the action will look for keys like `volta.node` key to receive the version. - +The `node-version-file` input accepts a path to a file containing the version of Node.js to be used by a project, for example `.nvmrc`, `.node-version` or `.tool-versions`. If both the `node-version` and the `node-version-file` inputs are provided then the `node-version` input is used. See [supported version syntax](https://github.com/actions/setup-node#supported-version-syntax) > The action will search for the node version file relative to the repository root. @@ -249,3 +248,5 @@ steps: # `npm rebuild` will run all those post-install scripts for us. - run: npm rebuild && npm run prepare --if-present ``` + +NOTE: As per https://github.com/actions/setup-node/issues/49 you cannot use `secrets.GITHUB_TOKEN` to access private GitHub Packages within the same organisation but in a different repository. diff --git a/src/cache-utils.ts b/src/cache-utils.ts index b71bcc8a..ccd4e987 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -13,12 +13,12 @@ export interface PackageManagerInfo { export const supportedPackageManagers: SupportedPackageManagers = { npm: { - lockFilePatterns: ['package-lock.json', 'yarn.lock'], + lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'], getCacheFolderCommand: 'npm config get cache' }, pnpm: { lockFilePatterns: ['pnpm-lock.yaml'], - getCacheFolderCommand: 'pnpm store path' + getCacheFolderCommand: 'pnpm store path --silent' }, yarn1: { lockFilePatterns: ['yarn.lock'], @@ -94,7 +94,7 @@ export const getCacheDirectoryPath = async ( core.debug(`${packageManager} path is ${stdOut}`); - return stdOut; + return stdOut.trim(); }; export function isGhes(): boolean { diff --git a/src/installer.ts b/src/installer.ts index a56b20a4..193ff16a 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -495,17 +495,26 @@ function translateArchToDistUrl(arch: string): string { } export function parseNodeVersionFile(contents: string): string { - let nodeVersion = contents.trim(); + let nodeVersion: string | undefined; - if (contents.includes('volta')) { - nodeVersion = JSON.parse(contents).volta.node; + const found = contents.match(/^(?:nodejs\s+)?v?(?[^\s]+)$/m); + nodeVersion = found?.groups?.version; + + if (!nodeVersion) { + try { + // Try parsing the file as an NPM `package.json` + // file. + nodeVersion = JSON.parse(contents).engines?.node; + + if (!nodeVersion) throw new Error(); + } catch (err) { + // In the case of an unknown format, + // return as is and evaluate the version separately. + nodeVersion = contents.trim(); + } } - if (/^v\d/.test(nodeVersion)) { - nodeVersion = nodeVersion.substring(1); - } - - return nodeVersion; + return nodeVersion as string; } function isLatestSyntax(versionSpec): boolean { diff --git a/src/main.ts b/src/main.ts index 42576375..d43614e6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,5 @@ import * as core from '@actions/core'; +import * as exec from '@actions/exec'; import * as installer from './installer'; import fs from 'fs'; import * as auth from './authutil'; @@ -39,6 +40,18 @@ export async function run() { await installer.getNode(version, stable, checkLatest, auth, arch); } + // Output version of node is being used + try { + const {stdout: installedVersion} = await exec.getExecOutput( + 'node', + ['--version'], + {ignoreReturnCode: true, silent: true} + ); + core.setOutput('node-version', installedVersion.trim()); + } catch (err) { + core.setOutput('node-version', ''); + } + const registryUrl: string = core.getInput('registry-url'); const alwaysAuth: string = core.getInput('always-auth'); if (registryUrl) {