diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 88a1b0f2c..1482ae289 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -51,6 +51,26 @@ jobs: __tests__/verify-node.sh "${BASH_REMATCH[1]}" shell: bash + v8-canary-syntax: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + node-version: ['20-v8-canary', '20.0.0-v8-canary','20.0.0-v8-canary20221103f7e2421e91'] + steps: + - uses: actions/checkout@v3 + - name: Setup Node + uses: ./ + with: + node-version: ${{ matrix.node-version }} + - name: Verify node and npm + run: | + canaryVersion="${{ matrix.node-version }}" + majorVersion=$(echo $canaryVersion | cut -d- -f1) + __tests__/verify-node.sh "$majorVersion" + shell: bash + nightly-syntax: runs-on: ${{ matrix.os }} strategy: diff --git a/README.md b/README.md index f2fbd3d99..f075d0cfa 100644 --- a/README.md +++ b/README.md @@ -128,16 +128,17 @@ If the runner is not able to access github.com, any Nodejs versions requested du ## Advanced usage -1. [Check latest version](docs/advanced-usage.md#check-latest-version) -2. [Using a node version file](docs/advanced-usage.md#node-version-file) -3. [Using different architectures](docs/advanced-usage.md#architecture) -4. [Using nigthly versions](docs/advanced-usage.md#nightly-versions) -5. [Using rc versions](docs/advanced-usage.md#rc-versions) -6. [Caching packages data](docs/advanced-usage.md#caching-packages-data) -7. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures) -8. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm) -9. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn) -10. [Using private packages](docs/advanced-usage.md#use-private-packages) + - [Check latest version](docs/advanced-usage.md#check-latest-version) + - [Using a node version file](docs/advanced-usage.md#node-version-file) + - [Using different architectures](docs/advanced-usage.md#architecture) + - [Using v8 canary versions](docs/advanced-usage.md#v8-canary-versions) + - [Using nigthly versions](docs/advanced-usage.md#nightly-versions) + - [Using rc versions](docs/advanced-usage.md#rc-versions) + - [Caching packages data](docs/advanced-usage.md#caching-packages-data) + - [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures) + - [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm) + - [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn) + - [Using private packages](docs/advanced-usage.md#use-private-packages) ## License diff --git a/__tests__/data/v8-canary-dist-index.json b/__tests__/data/v8-canary-dist-index.json new file mode 100644 index 000000000..2c06a072e --- /dev/null +++ b/__tests__/data/v8-canary-dist-index.json @@ -0,0 +1,537 @@ +[ + { + "version": "v20.0.0-v8-canary20221103f7e2421e91", + "date": "2022-11-03", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.138.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary202211026bf85d0fb4", + "date": "2022-11-02", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.130.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221101e50e45c9f8", + "date": "2022-11-01", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.129.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary202210311b1e675ad0", + "date": "2022-10-31", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.125.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221030fefe1c0879", + "date": "2022-10-30", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.125.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary202210293881e51ba2", + "date": "2022-10-29", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.122.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary202210286fe49d2a49", + "date": "2022-10-28", + "files": [ + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.112.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221027c470b3108c", + "date": "2022-10-27", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.101.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221026c24f7d1e4a", + "date": "2022-10-26", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.88.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221025b063237e20", + "date": "2022-10-25", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.73.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary2022102454996f930f", + "date": "2022-10-24", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.61.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary2022102310ff1e5a8d", + "date": "2022-10-23", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.61.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221022e83bcb6c41", + "date": "2022-10-22", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip", + "win-x86-7z", + "win-x86-exe", + "win-x86-msi", + "win-x86-zip" + ], + "npm": "8.19.2", + "v8": "10.9.60.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221021f6d5f347fa", + "date": "2022-10-21", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip" + ], + "npm": "8.19.2", + "v8": "10.9.48.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221020f78c149307", + "date": "2022-10-20", + "files": [ + "headers", + "linux-arm64", + "linux-armv7l", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip" + ], + "npm": "8.19.2", + "v8": "10.9.38.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v20.0.0-v8-canary20221019d52c76f76e", + "date": "2022-10-19", + "files": [ + "aix-ppc64", + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-x64", + "osx-arm64-tar", + "osx-x64-pkg", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip" + ], + "npm": "8.19.2", + "v8": "10.9.27.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v19.0.0-v8-canary202210187d6960f23f", + "date": "2022-10-18", + "files": [ + "aix-ppc64", + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip" + ], + "npm": "8.19.2", + "v8": "10.9.12.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + }, + { + "version": "v19.0.0-v8-canary202210172ec229fc56", + "date": "2022-10-17", + "files": [ + "aix-ppc64", + "headers", + "linux-arm64", + "linux-armv7l", + "linux-ppc64le", + "linux-s390x", + "linux-x64", + "osx-arm64-tar", + "osx-x64-tar", + "src", + "win-x64-7z", + "win-x64-exe", + "win-x64-msi", + "win-x64-zip" + ], + "npm": "8.19.2", + "v8": "10.9.6.0", + "uv": "1.43.0", + "zlib": "1.2.11", + "openssl": "3.0.5+quic", + "modules": "112", + "lts": false, + "security": false + } +] \ No newline at end of file diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 3c3105e2c..3ec837c22 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -1,10 +1,10 @@ import * as core from '@actions/core'; import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; +import * as httpm from '@actions/http-client'; import * as exec from '@actions/exec'; import * as im from '../src/installer'; import * as cache from '@actions/cache'; -import * as httpm from '@actions/http-client'; import fs from 'fs'; import cp from 'child_process'; import osm from 'os'; @@ -17,6 +17,7 @@ const nodeTestManifest = require('./data/versions-manifest.json'); const nodeTestDist = require('./data/node-dist-index.json'); const nodeTestDistNightly = require('./data/node-nightly-index.json'); const nodeTestDistRc = require('./data/node-rc-index.json'); +const nodeV8CanaryTestDist = require('./data/v8-canary-dist-index.json'); describe('setup-node', () => { let inputs = {} as any; @@ -137,7 +138,7 @@ describe('setup-node', () => { }); warningSpy.mockImplementation(msg => { // uncomment to debug - // process.stderr.write('log:' + line + '\n'); + // process.stderr.write('log:' + msg + '\n'); }); // @actions/exec @@ -998,7 +999,17 @@ describe('setup-node', () => { 'finds the %s version in the hostedToolcache', async (input, expectedVersion) => { const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - findSpy.mockReturnValue(toolPath); + findSpy.mockImplementation((_, version) => + path.normalize(`/cache/node/${version}/x64`) + ); + findAllVersionsSpy.mockReturnValue([ + '2.2.2-rc.2', + '1.1.1-rc.1', + '99.1.1', + expectedVersion, + '88.1.1', + '3.3.3-rc.3' + ]); inputs['node-version'] = input; os['arch'] = 'x64'; @@ -1252,9 +1263,127 @@ describe('setup-node', () => { } ); }); + + describe('setup-node v8 canary tests', () => { + // @actions/http-client + let getDistIndexJsonSpy: jest.SpyInstance; + let findAllVersionSpy: jest.SpyInstance; + + beforeEach(() => { + // @actions/http-client + getDistIndexJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson'); + getDistIndexJsonSpy.mockImplementation(() => ({ + result: nodeV8CanaryTestDist + })); + + // @actions/tool-cache + findAllVersionSpy = jest.spyOn(tc, 'findAllVersions'); + }); + + it('v8 canary setup node flow without cached', async () => { + let versionSpec = 'v20-v8-canary'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + os.platform = 'linux'; + os.arch = 'x64'; + + findAllVersionSpy.mockImplementation(() => []); + + findSpy.mockImplementation(() => ''); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + let toolPath = path.normalize('/cache/node/12.16.2/x64'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + await main.run(); + + expect(dbgSpy.mock.calls[0][0]).toBe('evaluating 0 versions'); + expect(dbgSpy.mock.calls[1][0]).toBe('match not found'); + expect(logSpy.mock.calls[0][0]).toBe( + `Attempting to download ${versionSpec}...` + ); + expect(dbgSpy.mock.calls[2][0]).toBe('No manifest cached'); + expect(dbgSpy.mock.calls[3][0]).toBe( + 'Getting manifest from actions/node-versions@main' + ); + expect(dbgSpy.mock.calls[4][0].slice(0, 6)).toBe('check '); + expect(dbgSpy.mock.calls[10][0].slice(0, 6)).toBe('check '); + expect(logSpy.mock.calls[1][0]).toBe( + 'Not found in manifest. Falling back to download directly from Node' + ); + expect(dbgSpy.mock.calls[12][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[13][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[2][0]).toBe( + 'Acquiring 20.0.0-v8-canary20221103f7e2421e91 - x64 from https://nodejs.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz' + ); + + expect(dlSpy).toHaveBeenCalledTimes(1); + expect(exSpy).toHaveBeenCalledTimes(1); + expect(cacheSpy).toHaveBeenCalledTimes(1); + }); + + it('v8 canary setup node flow with cached', async () => { + let versionSpec = 'v20-v8-canary'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + os.platform = 'linux'; + os.arch = 'x64'; + + const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91'; + findAllVersionSpy.mockImplementation(() => [versionExpected]); + + const toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); + findSpy.mockImplementation(version => toolPath); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${toolPath}${path.sep}bin${osm.EOL}` + ); + + expect(dlSpy).not.toHaveBeenCalled(); + expect(exSpy).not.toHaveBeenCalled(); + expect(cacheSpy).not.toHaveBeenCalled(); + }); + }); }); describe('helper methods', () => { + it('is not LTS alias', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + const isLtsAlias = im.isLtsAlias(versionSpec); + expect(isLtsAlias).toBeFalsy(); + }); + + it('is not isLatestSyntax', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + const isLatestSyntax = im.isLatestSyntax(versionSpec); + expect(isLatestSyntax).toBeFalsy(); + }); + + describe('getNodejsDistUrl', () => { + it('dist url to be https://nodejs.org/download/v8-canary for input versionSpec', () => { + const versionSpec = 'v99.0.0-v8-canary'; + const url = im.getNodejsDistUrl(versionSpec); + expect(url).toBe('https://nodejs.org/download/v8-canary'); + }); + + it('dist url to be https://nodejs.org/download/v8-canary for full versionSpec', () => { + const versionSpec = 'v20.0.0-v8-canary20221103f7e2421e91'; + const url = im.getNodejsDistUrl(versionSpec); + expect(url).toBe('https://nodejs.org/download/v8-canary'); + }); + }); + describe('parseNodeVersionFile', () => { each` contents | expected diff --git a/__tests__/installer.unit.test.ts b/__tests__/installer.unit.test.ts new file mode 100644 index 000000000..11d4b1bb0 --- /dev/null +++ b/__tests__/installer.unit.test.ts @@ -0,0 +1,362 @@ +import semver from 'semver'; +import { + canaryExactVersionMatcherFactory, + canaryRangeVersionMatcherFactory, + distributionOf, + Distributions, + evaluateVersions, + getNodejsDistUrl, + nightlyExactVersionMatcherFactory, + nightlyRangeVersionMatcherFactory, + semverVersionMatcherFactory, + splitVersionSpec, + versionMatcherFactory +} from '../src/installer'; + +describe('setup-node unit tests', () => { + describe('splitVersionSpec', () => { + it('splitVersionSpec correctly splits version spec without dashes', () => { + const [raw, prerelease] = splitVersionSpec('1.1.1'); + expect(raw).toBe('1.1.1'); + expect(prerelease).toBeUndefined(); + }); + it('splitVersionSpec correctly splits version spec with one dash', () => { + const [raw, prerelease] = splitVersionSpec('1.1.1-nightly12345678'); + expect(raw).toBe('1.1.1'); + expect(prerelease).toBe('nightly12345678'); + }); + it('splitVersionSpec correctly splits version spec with 2 dashes', () => { + const [raw, prerelease] = splitVersionSpec('1.1.1-v8-canary12345678'); + expect(raw).toBe('1.1.1'); + expect(prerelease).toBe('v8-canary12345678'); + }); + }); + + describe('distributionOf', () => { + it('1.1.1-v8-canary should be CANARY', () => { + expect(distributionOf('1.1.1-v8-canary')).toBe(Distributions.CANARY); + }); + it('1.1.1-v8-canary20221103f7e2421e91 should be CANARY', () => { + expect(distributionOf('1.1.1-v8-canary20221103f7e2421e91')).toBe( + Distributions.CANARY + ); + }); + it('1.1.1-nightly should be NIGHTLY', () => { + expect(distributionOf('1.1.1-nightly')).toBe(Distributions.NIGHTLY); + }); + it('1.1.1-nightly20221103f7e2421e91 should be NIGHTLY', () => { + expect(distributionOf('1.1.1-nightly20221103f7e2421e91')).toBe( + Distributions.NIGHTLY + ); + }); + it('1.1.1-rc.0 should be RC', () => { + expect(distributionOf('1.1.1-rc.0')).toBe(Distributions.RC); + }); + }); + + describe('versionMatcherFactory', () => { + it('1.1.1 should be handled by semverVersionMatcherFactory', () => { + expect(versionMatcherFactory('1.1.1').factory).toBe( + semverVersionMatcherFactory + ); + }); + it('v1.1.1 should be handled by semverVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1').factory).toBe( + semverVersionMatcherFactory + ); + }); + it('v1.1.1-v8-canary should be handled by canaryRangeVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1-v8-canary').factory).toBe( + canaryRangeVersionMatcherFactory + ); + }); + it('v1.1.1-v8-canary123 should be handled by canaryExactVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1-v8-canary123').factory).toBe( + canaryExactVersionMatcherFactory + ); + }); + it('v1.1.1-nightly should be handled by nightlyRangeVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1-nightly').factory).toBe( + nightlyRangeVersionMatcherFactory + ); + }); + it('v1.1.1-nigthly123 should be handled by nightlyExactVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1-nightly123').factory).toBe( + nightlyExactVersionMatcherFactory + ); + }); + it('v1.1.1-rc should be handled by semverVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1-rc').factory).toBe( + semverVersionMatcherFactory + ); + }); + it('v1.1.1-rc.1 should be handled by semverVersionMatcherFactory', () => { + expect(versionMatcherFactory('v1.1.1-rc.1').factory).toBe( + semverVersionMatcherFactory + ); + }); + }); + + describe('Version spec matchers', () => { + describe('semverVersionMatcher', () => { + it('semverVersionMatcher should always work as semver.satisfies does', () => { + const rangePlain = '1.1.1'; + const matcherPlain = semverVersionMatcherFactory(rangePlain); + expect(matcherPlain('1.1.1')).toBe( + semver.satisfies('1.1.1', rangePlain) + ); + expect(matcherPlain('1.1.2')).toBe( + semver.satisfies('1.1.2', rangePlain) + ); + + const rangeEq = '=1.1.1'; + const matcherEq = semverVersionMatcherFactory(rangeEq); + expect(matcherEq('1.1.1')).toBe(semver.satisfies('1.1.1', rangeEq)); + expect(matcherEq('1.1.2')).toBe(semver.satisfies('1.1.2', rangeEq)); + + // TODO: add for discovered issues if any + }); + + it("semverVersionMatcher should match release candidate as semver.satisfies does'", () => { + const rangePlain = 'v19.0.0-rc.2'; + const matcherPlain = semverVersionMatcherFactory(rangePlain); + expect(matcherPlain('v19.0.0-rc.2')).toBe( + semver.satisfies('v19.0.0-rc.2', rangePlain) + ); + expect(matcherPlain('v19.0.1-rc.2')).toBe( + semver.satisfies('v19.0.01rc.2', rangePlain) + ); + + const rangeEq = '=1.1.1'; + const matcherEq = semverVersionMatcherFactory(rangeEq); + expect(matcherPlain('v19.0.0-rc.2')).toBe( + semver.satisfies('v19.0.0-rc.2', rangePlain) + ); + expect(matcherPlain('v19.0.1-rc.2')).toBe( + semver.satisfies('v19.0.1-rc.2', rangePlain) + ); + }); + }); + + describe('canaryExactVersionMatcher', () => { + it('canaryExactVersionMatcher should match v20.0.0-v8-canary20221103f7e2421e91 only v20.0.0-v8-canary20221103f7e2421e91', () => { + const version = semver.coerce('v20.0.0')!.version; + const matcher = canaryExactVersionMatcherFactory( + version, + 'v8-canary20221103f7e2421e91' + ); + expect(matcher('v20.0.0-v8-canary20221103f7e2421e91')).toBeTruthy(); + // see https://github.com/actions/setup-node/blob/00e1b6691b40cce14b5078cb411dd1ec7dab07f7/__tests__/verify-node.sh#L10 + expect(matcher('v20.0.0-v8-canary202211026bf85d0fb4')).toBeFalsy(); + }); + }); + + describe('canaryRangeVersionMatcherFactory', () => { + it('canaryRangeVersionMatcherFactory should match v20-v8-canary to any v20.x.x', () => { + const version = semver.coerce('v20')!.version; + const matcher = canaryRangeVersionMatcherFactory(version); + expect(matcher('v20.0.0-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.0.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.0.0-v8-canary202211026bf85d0fb4')).toBeTruthy(); + }); + + it('canaryRangeVersionMatcherFactory should not match v20-v8-canary to v21.x & v19.x', () => { + const version = semver.coerce('v20')!.version; + const matcher = canaryRangeVersionMatcherFactory(version); + expect(matcher('v21.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.1.1-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.1.-v8-canary20221103f7e2421e91')).toBeFalsy(); + }); + + it('canaryRangeVersionMatcherFactory should match v20.1-v8-canary to any v20.1.x patch version and minor above or eq v20.1', () => { + const version = semver.coerce('v20.1')!.version; + const matcher = canaryRangeVersionMatcherFactory(version); + expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.0-v8-canary202211026bf85d0fb4')).toBeTruthy(); + expect(matcher('v20.2.0-v8-canary20221103f7e2421e91')).toBeTruthy(); + }); + + it('canaryRangeVersionMatcherFactory should not match v20.2-v8-canary to v21.x, v19.x, and v20 minor less than v20.2', () => { + const version = semver.coerce('v20.2')!.version; + const matcher = canaryRangeVersionMatcherFactory(version); + expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + }); + + it('canaryRangeVersionMatcherFactory should match v20.1.1-v8-canary to v20.1.x patch versions above or eq v20.1.1', () => { + const version = semver.coerce('v20.1.1')!.version; + const matcher = canaryRangeVersionMatcherFactory('v20.1.1-v8-canary'); + expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.2-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.2.0-v8-canary20221103f7e2421e91')).toBeTruthy(); + }); + + it('canaryRangeVersionMatcherFactory should not match v20.1.1-v8-canary to any other minor versions and patch versions below v20.1.1', () => { + const version = semver.coerce('v20.1.1')!.version; + const matcher = canaryRangeVersionMatcherFactory(version); + expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); + }); + + it('canaryRangeVersionMatcherFactory should match v20.1.1-v8-canary to patch versions with any canary timestamp', () => { + const version = semver.coerce('v20.1.1')!.version; + const matcher = canaryRangeVersionMatcherFactory(version); + expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-v8-canary202211026bf85d0fb4')).toBeTruthy(); + }); + }); + + describe('nightlyRangeVersionMatcherFactory', () => { + it('nightlyRangeVersionMatcherFactory should match v20-nightly to any v20.x.x', () => { + const version = semver.coerce('v20')!.version; + const matcher = nightlyRangeVersionMatcherFactory(version); + expect(matcher('v20.0.0-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.0.1-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.0.0-nightly202211026bf85d0fb4')).toBeTruthy(); + }); + + it('nightlyRangeVersionMatcherFactory should not match v20-nightly to v21.x & v19.x', () => { + const version = semver.coerce('v20')!.version; + const matcher = nightlyRangeVersionMatcherFactory(version); + expect(matcher('v21.0.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.1.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.1.1-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.0.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.1.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.1.-nightly20221103f7e2421e91')).toBeFalsy(); + }); + + it('nightlyRangeVersionMatcherFactory should match v20.1-nightly to any v20.1.x patch version and minor above or eq v20.1', () => { + const version = semver.coerce('v20.1')!.version; + const matcher = nightlyRangeVersionMatcherFactory(version); + expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.0-nightly202211026bf85d0fb4')).toBeTruthy(); + expect(matcher('v20.2.0-nightly20221103f7e2421e91')).toBeTruthy(); + }); + + it('nightlyRangeVersionMatcherFactory should not match v20.2-nightly to v21.x, v19.x, and v20 minor less v20.2', () => { + const version = semver.coerce('v20.2')!.version; + const matcher = nightlyRangeVersionMatcherFactory(version); + expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.0.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.0.0-nightly20221103f7e2421e91')).toBeFalsy(); + }); + + it('nightlyRangeVersionMatcherFactory should match v20.1.1-nightly to v20.1.x patch versions above or eq v20.1.1', () => { + const version = semver.coerce('v20.1.1')!.version; + const matcher = nightlyRangeVersionMatcherFactory('v20.1.1-nightly'); + expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.2-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.2.0-nightly20221103f7e2421e91')).toBeTruthy(); + }); + + it('nightlyRangeVersionMatcherFactory should not match v20.1.1-nightly to any other minor versions and patch versions below v20.1.1', () => { + const version = semver.coerce('v20.1.1')!.version; + const matcher = nightlyRangeVersionMatcherFactory(version); + expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v21.0.0-nightly20221103f7e2421e91')).toBeFalsy(); + expect(matcher('v19.0.0-nightly20221103f7e2421e91')).toBeFalsy(); + }); + + it('nightlyRangeVersionMatcherFactory should match v20.1.1-nightly to patch versions with any timestamp', () => { + const version = semver.coerce('v20.1.1')!.version; + const matcher = nightlyRangeVersionMatcherFactory(version); + expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-nightly202211026bf85d0fb4')).toBeTruthy(); + }); + }); + }); + + describe('evaluateVersions', () => { + it('evaluateVersions should handle v8-canary version spec without timestamp', () => { + const versions = [ + 'v20.0.0-v8-canary20221103f7e2421e91', + 'v20.0.1-v8-canary20221103f7e2421e91', + 'v20.1.0-v8-canary20221103f7e2421e91', + 'v20.1.1-v8-canary20221103f7e2421e91', + 'v21.1.0-v8-canary20221103f7e2421e91', + 'v19.1.0-v8-canary20221103f7e2421e91' + ]; + const version = evaluateVersions(versions, 'v20-v8-canary'); + expect(version).toBe('v20.1.1-v8-canary20221103f7e2421e91'); + }); + + it('evaluateVersions should handle v8-canary version spec with timestamp', () => { + const versions = [ + 'v20.0.0-v8-canary20221103f7e2421e91', + 'v20.0.1-v8-canary20221103f7e2421e91', + 'v20.0.1-v8-canary20221103f7e2421e92', + 'v20.0.1-v8-canary20221103f7e2421e93', + 'v20.0.2-v8-canary20221103f7e2421e91' + ]; + const version = evaluateVersions( + versions, + 'v20.0.1-v8-canary20221103f7e2421e92' + ); + expect(version).toBe('v20.0.1-v8-canary20221103f7e2421e92'); + }); + }); + + describe('getNodejsDistUrl', () => { + it('getNodejsDistUrl should handle v8 canary version spec', async () => { + expect(getNodejsDistUrl('1.1.1-v8-canary')).toBe( + 'https://nodejs.org/download/v8-canary' + ); + expect(getNodejsDistUrl('1.1.1-v8-canary123')).toBe( + 'https://nodejs.org/download/v8-canary' + ); + expect(getNodejsDistUrl('v1.1.1-v8-canary')).toBe( + 'https://nodejs.org/download/v8-canary' + ); + expect(getNodejsDistUrl('v1.1.1-v8-canary123')).toBe( + 'https://nodejs.org/download/v8-canary' + ); + }); + + it('getNodejsDistUrl should handle nightly version spec', async () => { + expect(getNodejsDistUrl('1.1.1-nightly')).toBe( + 'https://nodejs.org/download/nightly' + ); + expect(getNodejsDistUrl('v1.1.1-nightly')).toBe( + 'https://nodejs.org/download/nightly' + ); + expect(getNodejsDistUrl('1.1.1-nightly123')).toBe( + 'https://nodejs.org/download/nightly' + ); + expect(getNodejsDistUrl('v1.1.1-nightly123')).toBe( + 'https://nodejs.org/download/nightly' + ); + }); + + it('getNodejsDistUrl should handle rc version spec', async () => { + expect(getNodejsDistUrl('1.1.1-rc')).toBe( + 'https://nodejs.org/download/rc' + ); + expect(getNodejsDistUrl('v1.1.1-rc')).toBe( + 'https://nodejs.org/download/rc' + ); + expect(getNodejsDistUrl('1.1.1-rc.0')).toBe( + 'https://nodejs.org/download/rc' + ); + expect(getNodejsDistUrl('v1.1.1-rc.0')).toBe( + 'https://nodejs.org/download/rc' + ); + }); + + it('getNodejsDistUrl should handle unspecific version spec', async () => { + expect(getNodejsDistUrl('1.1.1')).toBe('https://nodejs.org/dist'); + expect(getNodejsDistUrl('v1.1.1')).toBe('https://nodejs.org/dist'); + }); + }); +}); diff --git a/dist/setup/index.js b/dist/setup/index.js index aa4f708af..2d4f882fb 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73216,14 +73216,106 @@ const tc = __importStar(__nccwpck_require__(7784)); const path = __importStar(__nccwpck_require__(1017)); const semver = __importStar(__nccwpck_require__(5911)); const fs_1 = __importDefault(__nccwpck_require__(7147)); +var Distributions; +(function (Distributions) { + Distributions["DEFAULT"] = "default"; + Distributions["CANARY"] = "v8-canary"; + Distributions["NIGHTLY"] = "nightly"; + Distributions["RC"] = "rc"; +})(Distributions = exports.Distributions || (exports.Distributions = {})); +exports.distributionOf = (versionSpec) => { + if (versionSpec.includes('-v8-canary')) + return Distributions.CANARY; + if (versionSpec.includes('nightly')) + return Distributions.NIGHTLY; + if (semver.prerelease(versionSpec)) + return Distributions.RC; + return Distributions.DEFAULT; +}; +exports.semverVersionMatcherFactory = (range) => { + const matcher = (potential) => semver.satisfies(potential, range); + matcher.factory = exports.semverVersionMatcherFactory; + return matcher; +}; +exports.canaryRangeVersionMatcherFactory = (version) => { + const range = semver.validRange(`^${version}`); + const matcher = (potential) => semver.satisfies(potential.replace('-v8-canary', '+v8-canary.'), range); + matcher.factory = exports.canaryRangeVersionMatcherFactory; + return matcher; +}; +exports.canaryExactVersionMatcherFactory = (version, timestamp) => { + const range = `${version}-${timestamp}`; + const matcher = (potential) => semver.satisfies(potential, range); + matcher.factory = exports.canaryExactVersionMatcherFactory; + return matcher; +}; +exports.nightlyRangeVersionMatcherFactory = (version) => { + const range = semver.validRange(`^${version}`); + // TODO: this makes v20.1.1-nightly to do not match v20.1.1-nightly20221103f7e2421e91 + // const range = `${semver.validRange(`^${version}-0`)}-0`; + const matcher = (potential) => exports.distributionOf(potential) === Distributions.NIGHTLY && + // TODO: dmitry's variant was potential.replace('-nightly', '-nightly.') that made + // all unit tests to fail + semver.satisfies(potential.replace('-nightly', '+nightly.'), range /*, { + // TODO: what is for? + includePrerelease: true + }*/); + matcher.factory = exports.nightlyRangeVersionMatcherFactory; + return matcher; +}; +exports.nightlyExactVersionMatcherFactory = (version, timestamp) => { + const range = `${version}-${timestamp.replace('nightly', 'nightly.')}`; + const matcher = (potential) => exports.distributionOf(potential) === Distributions.NIGHTLY && + semver.satisfies(potential.replace('-nightly', '-nightly.'), range /*, { + // TODO: what is for? + includePrerelease: true + }*/); + matcher.factory = exports.nightlyExactVersionMatcherFactory; + return matcher; +}; +const alwaysFalseVersionMatcherFactory = () => { + const matcher = () => false; + matcher.factory = alwaysFalseVersionMatcherFactory; + return matcher; +}; +const alwaysFalseVersionMatcher = alwaysFalseVersionMatcherFactory(); +// [raw, prerelease] +exports.splitVersionSpec = (versionSpec) => versionSpec.split(/-(.*)/s); +function versionMatcherFactory(versionSpec) { + var _a; + const [raw, prerelease] = exports.splitVersionSpec(versionSpec); + const validVersion = semver.valid(raw) ? raw : (_a = semver.coerce(raw)) === null || _a === void 0 ? void 0 : _a.version; + if (validVersion) { + switch (exports.distributionOf(versionSpec)) { + case Distributions.CANARY: + return prerelease === 'v8-canary' // this means versionSpec does not have timestamp + ? exports.canaryRangeVersionMatcherFactory(validVersion) + : exports.canaryExactVersionMatcherFactory(validVersion, prerelease); + case Distributions.NIGHTLY: + return prerelease === 'nightly' // this means versionSpec does not have prerelease tag + ? exports.nightlyRangeVersionMatcherFactory(validVersion) + : exports.nightlyExactVersionMatcherFactory(validVersion, prerelease); + case Distributions.RC: + case Distributions.DEFAULT: + return exports.semverVersionMatcherFactory(versionSpec); + } + } + else { + // TODO: i prefer to have implicit exception for the malformed input + throw Error(`Invalid version input "${versionSpec}"`); + // TODO: but it is possible to silently fail + // return alwaysFalseVersionMatcher + } +} +exports.versionMatcherFactory = versionMatcherFactory; function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arch()) { return __awaiter(this, void 0, void 0, function* () { // Store manifest data to avoid multiple calls let manifest; let nodeVersions; - let isNightly = versionSpec.includes('nightly'); - let osPlat = os_1.default.platform(); - let osArch = translateArchToDistUrl(arch); + const osPlat = os_1.default.platform(); + const osArch = translateArchToDistUrl(arch); + const distribution = exports.distributionOf(versionSpec); if (isLtsAlias(versionSpec)) { core.info('Attempt to resolve LTS alias from manifest...'); // No try-catch since it's not possible to resolve LTS alias without manifest @@ -73233,13 +73325,17 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc if (isLatestSyntax(versionSpec)) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); - core.info(`getting latest node version...`); + core.info(`getting latest node version ${versionSpec}...`); } - if (isNightly && checkLatest) { + if ((distribution === Distributions.NIGHTLY || + distribution === Distributions.CANARY) && + checkLatest) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); } - if (checkLatest && !isNightly) { + if (checkLatest && + distribution !== Distributions.NIGHTLY && + distribution !== Distributions.CANARY) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = yield resolveVersionFromManifest(versionSpec, stable, auth, osArch, manifest); if (resolvedVersion) { @@ -73252,12 +73348,13 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc } // check cache let toolPath; - if (isNightly) { - const nightlyVersion = findNightlyVersionInHostedToolcache(versionSpec, osArch); - toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); + if (distribution === Distributions.DEFAULT) { + toolPath = tc.find('node', versionSpec, osArch); } else { - toolPath = tc.find('node', versionSpec, osArch); + const localVersionPaths = tc.findAllVersions('node', osArch); + const localVersion = evaluateVersions(localVersionPaths, versionSpec); + toolPath = localVersion && tc.find('node', localVersion, osArch); } // If not found in cache, download if (toolPath) { @@ -73354,14 +73451,10 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc }); } exports.getNode = getNode; -function findNightlyVersionInHostedToolcache(versionsSpec, osArch) { - const foundAllVersions = tc.findAllVersions('node', osArch); - const version = evaluateVersions(foundAllVersions, versionsSpec); - return version; -} function isLtsAlias(versionSpec) { return versionSpec.startsWith('lts/'); } +exports.isLtsAlias = isLtsAlias; function getManifest(auth) { core.debug('Getting manifest from actions/node-versions@main'); return tc.getManifestFromRepo('actions', 'node-versions', auth, 'main'); @@ -73391,6 +73484,7 @@ function resolveLtsAliasFromManifest(versionSpec, stable, manifest) { core.debug(`Found LTS release '${release.version}' for Node version '${versionSpec}'`); return release.version.split('.')[0]; } +exports.resolveLtsAliasFromManifest = resolveLtsAliasFromManifest; function getInfoFromManifest(versionSpec, stable, auth, osArch = translateArchToDistUrl(os_1.default.arch()), manifest) { return __awaiter(this, void 0, void 0, function* () { let info = null; @@ -73447,54 +73541,14 @@ function resolveVersionFromManifest(versionSpec, stable, auth, osArch = translat } }); } -function evaluateNightlyVersions(versions, versionSpec) { - let version = ''; - let range; - const [raw, prerelease] = versionSpec.split('-'); - const isValidVersion = semver.valid(raw); - const rawVersion = isValidVersion ? raw : semver.coerce(raw); - if (rawVersion) { - if (prerelease !== 'nightly') { - range = `${rawVersion}-${prerelease.replace('nightly', 'nightly.')}`; - } - else { - range = `${semver.validRange(`^${rawVersion}-0`)}-0`; - } - } - if (range) { - versions.sort(semver.rcompare); - for (const currentVersion of versions) { - const satisfied = semver.satisfies(currentVersion.replace('-nightly', '-nightly.'), range, { includePrerelease: true }) && currentVersion.includes('nightly'); - if (satisfied) { - version = currentVersion; - break; - } - } - } - if (version) { - core.debug(`matched: ${version}`); - } - else { - core.debug('match not found'); - } - return version; -} // TODO - should we just export this from @actions/tool-cache? Lifted directly from there +// - the answer from dsame@github.com - we have customized matcher and can not +// export `evaluateVersions` from tc. But it would be possible to modify tc to accept +// the matcher as an optional parameter to `evaluateVersions` function evaluateVersions(versions, versionSpec) { - let version = ''; core.debug(`evaluating ${versions.length} versions`); - if (versionSpec.includes('nightly')) { - return evaluateNightlyVersions(versions, versionSpec); - } - versions = versions.sort(semver.rcompare); - for (let i = versions.length - 1; i >= 0; i--) { - const potential = versions[i]; - const satisfied = semver.satisfies(potential, versionSpec); - if (satisfied) { - version = potential; - break; - } - } + const matcher = versionMatcherFactory(versionSpec); + const version = versions.sort(semver.rcompare).find(matcher) || ''; if (version) { core.debug(`matched: ${version}`); } @@ -73503,15 +73557,18 @@ function evaluateVersions(versions, versionSpec) { } return version; } +exports.evaluateVersions = evaluateVersions; function getNodejsDistUrl(version) { - const prerelease = semver.prerelease(version); - if (version.includes('nightly')) { - return 'https://nodejs.org/download/nightly'; + switch (exports.distributionOf(version)) { + case Distributions.CANARY: + return 'https://nodejs.org/download/v8-canary'; + case Distributions.NIGHTLY: + return 'https://nodejs.org/download/nightly'; + case Distributions.RC: + return 'https://nodejs.org/download/rc'; + case Distributions.DEFAULT: + return 'https://nodejs.org/dist'; } - else if (prerelease) { - return 'https://nodejs.org/download/rc'; - } - return 'https://nodejs.org/dist'; } exports.getNodejsDistUrl = getNodejsDistUrl; function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions) { @@ -73537,11 +73594,11 @@ function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions core.debug('No dist manifest cached'); nodeVersions = yield getVersionsFromDist(versionSpec); } - let versions = []; if (isLatestSyntax(versionSpec)) { core.info(`getting latest node version...`); return nodeVersions[0].version; } + const versions = []; nodeVersions.forEach((nodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { @@ -73549,14 +73606,15 @@ function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions } }); // get the latest version that matches the version spec - let version = evaluateVersions(versions, versionSpec); + const version = evaluateVersions(versions, versionSpec); return version; }); } +exports.queryDistForMatch = queryDistForMatch; function getVersionsFromDist(versionSpec) { return __awaiter(this, void 0, void 0, function* () { - const initialUrl = getNodejsDistUrl(versionSpec); - const dataUrl = `${initialUrl}/index.json`; + const distUrl = getNodejsDistUrl(versionSpec); + const dataUrl = `${distUrl}/index.json`; let httpClient = new hc.HttpClient('setup-node', [], { allowRetries: true, maxRetries: 3 @@ -73657,6 +73715,7 @@ exports.parseNodeVersionFile = parseNodeVersionFile; function isLatestSyntax(versionSpec) { return ['current', 'latest', 'node'].includes(versionSpec); } +exports.isLatestSyntax = isLatestSyntax; /***/ }), diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 4789f2ead..79998680d 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -104,6 +104,57 @@ jobs: - run: npm test ``` +## V8 Canary versions + +You can specify a nightly version to download it from https://nodejs.org/download/v8-canary. + +### Install v8 canary build for specific node version + +```yaml +jobs: + build: + runs-on: ubuntu-latest + name: Node sample + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '20.0.0-v8-canary' # it will install the latest v8 canary release for node 20.0.0 + - run: npm ci + - run: npm test +``` +### Install v8 canary build for major node version + +```yaml +jobs: + build: + runs-on: ubuntu-latest + name: Node sample + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '20-v8-canary' # it will install the latest v8 canary release for node 20 + - run: npm ci + - run: npm test +``` + +### Install the exact v8 canary version + +```yaml +jobs: + build: + runs-on: ubuntu-latest + name: Node sample + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 'v20.1.1-v8-canary20221103f7e2421e91' + - run: npm ci + - run: npm test +``` + ## Nightly versions You can specify a nightly version to download it from https://nodejs.org/download/nightly. diff --git a/src/installer.ts b/src/installer.ts index 1b5659b6a..c3dc349ff 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -30,6 +30,136 @@ interface INodeRelease extends tc.IToolRelease { lts?: string; } +export enum Distributions { + DEFAULT = 'default', + CANARY = 'v8-canary', + NIGHTLY = 'nightly', + RC = 'rc' +} + +export const distributionOf = (versionSpec: string): Distributions => { + if (versionSpec.includes('-v8-canary')) return Distributions.CANARY; + if (versionSpec.includes('nightly')) return Distributions.NIGHTLY; + if (semver.prerelease(versionSpec)) return Distributions.RC; + return Distributions.DEFAULT; +}; + +interface VersionMatcher { + (potential: string): boolean; + + // memoize the factory for testing and debug purposes + factory: + | ((ver: string, suffix: string) => VersionMatcher) + | ((semverRanger: string) => VersionMatcher) + | (() => VersionMatcher); +} + +export const semverVersionMatcherFactory = (range: string): VersionMatcher => { + const matcher = (potential: string): boolean => + semver.satisfies(potential, range); + matcher.factory = semverVersionMatcherFactory; + return matcher; +}; + +export const canaryRangeVersionMatcherFactory = ( + version: string +): VersionMatcher => { + const range = semver.validRange(`^${version}`); + const matcher = (potential: string): boolean => + semver.satisfies(potential.replace('-v8-canary', '+v8-canary.'), range); + matcher.factory = canaryRangeVersionMatcherFactory; + return matcher; +}; + +export const canaryExactVersionMatcherFactory = ( + version: string, + timestamp: string +): VersionMatcher => { + const range = `${version}-${timestamp}`; + const matcher = (potential: string): boolean => + semver.satisfies(potential, range); + matcher.factory = canaryExactVersionMatcherFactory; + return matcher; +}; + +export const nightlyRangeVersionMatcherFactory = ( + version: string +): VersionMatcher => { + const range = semver.validRange(`^${version}`); + // TODO: this makes v20.1.1-nightly to do not match v20.1.1-nightly20221103f7e2421e91 + // const range = `${semver.validRange(`^${version}-0`)}-0`; + const matcher = (potential: string): boolean => + distributionOf(potential) === Distributions.NIGHTLY && + // TODO: dmitry's variant was potential.replace('-nightly', '-nightly.') that made + // all unit tests to fail + semver.satisfies( + potential.replace('-nightly', '+nightly.'), + range /*, { + // TODO: what is for? + includePrerelease: true + }*/ + ); + matcher.factory = nightlyRangeVersionMatcherFactory; + return matcher; +}; + +export const nightlyExactVersionMatcherFactory = ( + version: string, + timestamp: string +): VersionMatcher => { + const range = `${version}-${timestamp.replace('nightly', 'nightly.')}`; + const matcher = (potential: string): boolean => + distributionOf(potential) === Distributions.NIGHTLY && + semver.satisfies( + potential.replace('-nightly', '-nightly.'), + range /*, { + // TODO: what is for? + includePrerelease: true + }*/ + ); + matcher.factory = nightlyExactVersionMatcherFactory; + return matcher; +}; + +const alwaysFalseVersionMatcherFactory = (): VersionMatcher => { + const matcher = () => false; + matcher.factory = alwaysFalseVersionMatcherFactory; + return matcher; +}; + +const alwaysFalseVersionMatcher = alwaysFalseVersionMatcherFactory(); + +// [raw, prerelease] +export const splitVersionSpec = (versionSpec: string): string[] => + versionSpec.split(/-(.*)/s); + +export function versionMatcherFactory(versionSpec: string): VersionMatcher { + const [raw, prerelease] = splitVersionSpec(versionSpec); + const validVersion = semver.valid(raw) ? raw : semver.coerce(raw)?.version; + + if (validVersion) { + switch (distributionOf(versionSpec)) { + case Distributions.CANARY: + return prerelease === 'v8-canary' // this means versionSpec does not have timestamp + ? canaryRangeVersionMatcherFactory(validVersion) + : canaryExactVersionMatcherFactory(validVersion, prerelease); + case Distributions.NIGHTLY: + return prerelease === 'nightly' // this means versionSpec does not have prerelease tag + ? nightlyRangeVersionMatcherFactory(validVersion) + : nightlyExactVersionMatcherFactory(validVersion, prerelease); + case Distributions.RC: + case Distributions.DEFAULT: + return semverVersionMatcherFactory(versionSpec); + } + } else { + // TODO: i prefer to have implicit exception for the malformed input + throw Error(`Invalid version input "${versionSpec}"`); + + // TODO: but it is possible to silently fail + // return alwaysFalseVersionMatcher + } +} + export async function getNode( versionSpec: string, stable: boolean, @@ -40,9 +170,9 @@ export async function getNode( // Store manifest data to avoid multiple calls let manifest: INodeRelease[] | undefined; let nodeVersions: INodeVersion[] | undefined; - let isNightly = versionSpec.includes('nightly'); - let osPlat: string = os.platform(); - let osArch: string = translateArchToDistUrl(arch); + const osPlat: string = os.platform(); + const osArch: string = translateArchToDistUrl(arch); + const distribution = distributionOf(versionSpec); if (isLtsAlias(versionSpec)) { core.info('Attempt to resolve LTS alias from manifest...'); @@ -56,15 +186,23 @@ export async function getNode( if (isLatestSyntax(versionSpec)) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); - core.info(`getting latest node version...`); + core.info(`getting latest node version ${versionSpec}...`); } - if (isNightly && checkLatest) { + if ( + (distribution === Distributions.NIGHTLY || + distribution === Distributions.CANARY) && + checkLatest + ) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); } - if (checkLatest && !isNightly) { + if ( + checkLatest && + distribution !== Distributions.NIGHTLY && + distribution !== Distributions.CANARY + ) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = await resolveVersionFromManifest( versionSpec, @@ -83,14 +221,12 @@ export async function getNode( // check cache let toolPath: string; - if (isNightly) { - const nightlyVersion = findNightlyVersionInHostedToolcache( - versionSpec, - osArch - ); - toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); - } else { + if (distribution === Distributions.DEFAULT) { toolPath = tc.find('node', versionSpec, osArch); + } else { + const localVersionPaths = tc.findAllVersions('node', osArch); + const localVersion = evaluateVersions(localVersionPaths, versionSpec); + toolPath = localVersion && tc.find('node', localVersion, osArch); } // If not found in cache, download @@ -215,17 +351,7 @@ export async function getNode( core.addPath(toolPath); } -function findNightlyVersionInHostedToolcache( - versionsSpec: string, - osArch: string -) { - const foundAllVersions = tc.findAllVersions('node', osArch); - const version = evaluateVersions(foundAllVersions, versionsSpec); - - return version; -} - -function isLtsAlias(versionSpec: string): boolean { +export function isLtsAlias(versionSpec: string): boolean { return versionSpec.startsWith('lts/'); } @@ -234,7 +360,7 @@ function getManifest(auth: string | undefined): Promise { return tc.getManifestFromRepo('actions', 'node-versions', auth, 'main'); } -function resolveLtsAliasFromManifest( +export function resolveLtsAliasFromManifest( versionSpec: string, stable: boolean, manifest: INodeRelease[] @@ -365,66 +491,18 @@ async function resolveVersionFromManifest( } } -function evaluateNightlyVersions( +// TODO - should we just export this from @actions/tool-cache? Lifted directly from there +// - the answer from dsame@github.com - we have customized matcher and can not +// export `evaluateVersions` from tc. But it would be possible to modify tc to accept +// the matcher as an optional parameter to `evaluateVersions` +export function evaluateVersions( versions: string[], versionSpec: string ): string { - let version = ''; - let range: string | undefined; - const [raw, prerelease] = versionSpec.split('-'); - const isValidVersion = semver.valid(raw); - const rawVersion = isValidVersion ? raw : semver.coerce(raw); - if (rawVersion) { - if (prerelease !== 'nightly') { - range = `${rawVersion}-${prerelease.replace('nightly', 'nightly.')}`; - } else { - range = `${semver.validRange(`^${rawVersion}-0`)}-0`; - } - } - - if (range) { - versions.sort(semver.rcompare); - for (const currentVersion of versions) { - const satisfied: boolean = - semver.satisfies( - currentVersion.replace('-nightly', '-nightly.'), - range, - {includePrerelease: true} - ) && currentVersion.includes('nightly'); - if (satisfied) { - version = currentVersion; - break; - } - } - } - - if (version) { - core.debug(`matched: ${version}`); - } else { - core.debug('match not found'); - } - - return version; -} - -// TODO - should we just export this from @actions/tool-cache? Lifted directly from there -function evaluateVersions(versions: string[], versionSpec: string): string { - let version = ''; core.debug(`evaluating ${versions.length} versions`); - if (versionSpec.includes('nightly')) { - return evaluateNightlyVersions(versions, versionSpec); - } - - versions = versions.sort(semver.rcompare); - for (let i = versions.length - 1; i >= 0; i--) { - const potential: string = versions[i]; - const satisfied: boolean = semver.satisfies(potential, versionSpec); - if (satisfied) { - version = potential; - break; - } - } + const matcher = versionMatcherFactory(versionSpec); + const version = versions.sort(semver.rcompare).find(matcher) || ''; if (version) { core.debug(`matched: ${version}`); @@ -436,17 +514,19 @@ function evaluateVersions(versions: string[], versionSpec: string): string { } export function getNodejsDistUrl(version: string) { - const prerelease = semver.prerelease(version); - if (version.includes('nightly')) { - return 'https://nodejs.org/download/nightly'; - } else if (prerelease) { - return 'https://nodejs.org/download/rc'; + switch (distributionOf(version)) { + case Distributions.CANARY: + return 'https://nodejs.org/download/v8-canary'; + case Distributions.NIGHTLY: + return 'https://nodejs.org/download/nightly'; + case Distributions.RC: + return 'https://nodejs.org/download/rc'; + case Distributions.DEFAULT: + return 'https://nodejs.org/dist'; } - - return 'https://nodejs.org/dist'; } -async function queryDistForMatch( +export async function queryDistForMatch( versionSpec: string, arch: string = os.arch(), nodeVersions?: INodeVersion[] @@ -475,13 +555,12 @@ async function queryDistForMatch( nodeVersions = await getVersionsFromDist(versionSpec); } - let versions: string[] = []; - if (isLatestSyntax(versionSpec)) { core.info(`getting latest node version...`); return nodeVersions[0].version; } + const versions: string[] = []; nodeVersions.forEach((nodeVersion: INodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { @@ -490,15 +569,15 @@ async function queryDistForMatch( }); // get the latest version that matches the version spec - let version = evaluateVersions(versions, versionSpec); + const version = evaluateVersions(versions, versionSpec); return version; } export async function getVersionsFromDist( versionSpec: string ): Promise { - const initialUrl = getNodejsDistUrl(versionSpec); - const dataUrl = `${initialUrl}/index.json`; + const distUrl = getNodejsDistUrl(versionSpec); + const dataUrl = `${distUrl}/index.json`; let httpClient = new hc.HttpClient('setup-node', [], { allowRetries: true, maxRetries: 3 @@ -601,6 +680,6 @@ export function parseNodeVersionFile(contents: string): string { return nodeVersion as string; } -function isLatestSyntax(versionSpec): boolean { +export function isLatestSyntax(versionSpec): boolean { return ['current', 'latest', 'node'].includes(versionSpec); }