From 9c158b3dbdc1f44c1417d04feb9d888fc038a8c4 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Wed, 9 Nov 2022 20:39:42 +0100 Subject: [PATCH 01/19] Add support of v8 canary release --- src/installer.ts | 72 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index 1b5659b6a..24b53970c 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -30,6 +30,9 @@ interface INodeRelease extends tc.IToolRelease { lts?: string; } +const isVersionCanary = (versionSpec: string): boolean => + versionSpec.includes(`-v8-canary`); + export async function getNode( versionSpec: string, stable: boolean, @@ -43,6 +46,7 @@ export async function getNode( let isNightly = versionSpec.includes('nightly'); let osPlat: string = os.platform(); let osArch: string = translateArchToDistUrl(arch); + let isCanary = isVersionCanary(versionSpec); if (isLtsAlias(versionSpec)) { core.info('Attempt to resolve LTS alias from manifest...'); @@ -53,10 +57,15 @@ export async function getNode( versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - if (isLatestSyntax(versionSpec)) { + // evaluate exact versionSpec from input + if (isLatestSyntax(versionSpec) || isCanary) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); - core.info(`getting latest node version...`); + core.info( + `getting ${ + isCanary ? 'v8-canary' : 'latest' + } node version ${versionSpec}...` + ); } if (isNightly && checkLatest) { @@ -82,6 +91,7 @@ export async function getNode( } // check cache + core.info('Attempt to find existing version in cache...'); let toolPath: string; if (isNightly) { const nightlyVersion = findNightlyVersionInHostedToolcache( @@ -89,6 +99,9 @@ export async function getNode( osArch ); toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); + } else if (isCanary) { + const localVersions = tc.findAllVersions('node', osArch); + toolPath = evaluateVersions(localVersions, versionSpec); } else { toolPath = tc.find('node', versionSpec, osArch); } @@ -417,9 +430,14 @@ function evaluateVersions(versions: string[], versionSpec: string): string { } versions = versions.sort(semver.rcompare); + + const matcher: (potential: string) => boolean = isVersionCanary(versionSpec) + ? evaluateCanaryMatcher(versionSpec) + : potential => semver.satisfies(potential, versionSpec); + for (let i = versions.length - 1; i >= 0; i--) { const potential: string = versions[i]; - const satisfied: boolean = semver.satisfies(potential, versionSpec); + const satisfied: boolean = matcher(potential); if (satisfied) { version = potential; break; @@ -438,15 +456,19 @@ function evaluateVersions(versions: string[], versionSpec: string): string { export function getNodejsDistUrl(version: string) { const prerelease = semver.prerelease(version); if (version.includes('nightly')) { + core.debug('requested nightly distribution'); return 'https://nodejs.org/download/nightly'; } else if (prerelease) { return 'https://nodejs.org/download/rc'; + } else if (isVersionCanary(version)) { + core.debug('requested v8 canary distribution'); + return 'https://nodejs.org/download/v8-canary'; + } else { + 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 +497,12 @@ async function queryDistForMatch( nodeVersions = await getVersionsFromDist(versionSpec); } - let versions: string[] = []; - if (isLatestSyntax(versionSpec)) { core.info(`getting latest node version...`); return nodeVersions[0].version; } + let versions: string[] = []; nodeVersions.forEach((nodeVersion: INodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { @@ -497,8 +518,8 @@ async function queryDistForMatch( 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 @@ -604,3 +625,34 @@ export function parseNodeVersionFile(contents: string): string { function isLatestSyntax(versionSpec): boolean { return ['current', 'latest', 'node'].includes(versionSpec); } + +export function evaluateCanaryMatcher( + versionSpec: string +): (potential: string) => boolean { + const [raw, prerelease] = versionSpec.split(/-(.*)/s); + const isValidVersion = semver.valid(raw); + const rawVersion = isValidVersion ? raw : semver.coerce(raw)?.version; + if (rawVersion) { + if (prerelease === 'v8-canary') { + // it means versionSpec does not have timestamp + const range = semver.validRange(`^${rawVersion}`); + return (potential: string) => + semver.satisfies( + // TODO: check latest? + potential.replace('-v8-canary', '+v8-canary.'), + range + ); + } else { + // see https://github.com/actions/setup-node/blob/00e1b6691b40cce14b5078cb411dd1ec7dab07f7/__tests__/verify-node.sh#L10 + // there must be exact match + const range = `${rawVersion}-${prerelease}`; + return (potential: string) => + semver.satisfies( + // TODO: check latest? + potential, + range + ); + } + } + return () => false; +} From 588599b9441f1a1afdc6b3a79c8ad3cb9b40a12d Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Wed, 9 Nov 2022 20:41:33 +0100 Subject: [PATCH 02/19] Add v8 canary unit tests --- __tests__/data/v8-canary-dist-index.json | 537 +++++++++++++++++++++++ __tests__/installer.test.ts | 268 ++++++++++- src/installer.ts | 15 +- 3 files changed, 811 insertions(+), 9 deletions(-) create mode 100644 __tests__/data/v8-canary-dist-index.json 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..321034ae2 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; @@ -71,6 +72,7 @@ describe('setup-node', () => { exSpy = jest.spyOn(tc, 'extractTar'); cacheSpy = jest.spyOn(tc, 'cacheDir'); getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo'); + // @ts-ignore getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile'); @@ -137,7 +139,7 @@ describe('setup-node', () => { }); warningSpy.mockImplementation(msg => { // uncomment to debug - // process.stderr.write('log:' + line + '\n'); + // process.stderr.write('log:' + msg + '\n'); }); // @actions/exec @@ -1252,6 +1254,268 @@ describe('setup-node', () => { } ); }); + + describe('setup-node v8 canary unit tests', () => { + it('is not LTS alias', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + // @ts-ignore + const isLtsAlias = im.isLtsAlias(versionSpec); + expect(isLtsAlias).toBeFalsy(); + }); + + it('is not isLatestSyntax', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + // @ts-ignore + const isLatestSyntax = im.isLatestSyntax(versionSpec); + expect(isLatestSyntax).toBeFalsy(); + }); + + it('dist url to be https://nodejs.org/download/v8-canary for input versionSpec', () => { + const versionSpec = 'v99.0.0-v8-canary'; + // @ts-ignore + 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'; + // @ts-ignore + const url = im.getNodejsDistUrl(versionSpec); + expect(url).toBe('https://nodejs.org/download/v8-canary'); + }); + + it('v20-v8-canary should match any minor and patch version', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); + 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('v20-v8-canary should not match v21.x & v19.x', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); + 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('v20.1-v8-canary should match any v20.1 patch version and minor above or eq v20.1', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.1-v8-canary'); + 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('v20.2-v8-canary should not match v21.x, v19.x, and v20 minor less v20.2', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.2-v8-canary'); + 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('v20.1.1-v8-canary should match v20.1.x patch versions above or eq v20.1.1', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('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('v20.1.1-v8-canary should match patch versions with any canary timestamp', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-v8-canary202211026bf85d0fb4')).toBeTruthy(); + }); + + it('v20.1.1-v8-canary should not match any other minor versions and patch versions below v20.1.1', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + 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('v20.0.0-v8-canary20221103f7e2421e91 should match only v20.0.0-v8-canary20221103f7e2421e91', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher( + 'v20.0.0-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(); + }); + + it('v8 canary evaluateVersions 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' + ]; + // @ts-ignore + const version = im.evaluateVersions(versions, 'v20-v8-canary'); + expect(version).toBe('v20.1.1-v8-canary20221103f7e2421e91'); + }); + + it('v8 canary evaluateVersions 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' + ]; + // @ts-ignore + const version = im.evaluateVersions( + versions, + 'v20.0.1-v8-canary20221103f7e2421e92' + ); + expect(version).toBe('v20.0.1-v8-canary20221103f7e2421e92'); + }); + + it('v8 canary queryDistForMatch', async () => { + jest.spyOn(osm, 'platform').mockImplementationOnce(() => 'linux'); + // @ts-ignore + const version = await im.queryDistForMatch( + 'v20-v8-canary', + 'x64', + nodeV8CanaryTestDist + ); + expect(version).toBe('v20.0.0-v8-canary20221103f7e2421e91'); + }); + }); + + describe('setup-node v8 canary e2e 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('requested v8 canary distribution'); + expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[2][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[0][0]).toBe( + 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' + ); + expect(logSpy.mock.calls[1][0]).toBe( + 'Attempt to find existing version in cache...' + ); + expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 0 versions'); + expect(dbgSpy.mock.calls[4][0]).toBe('match not found'); + expect(logSpy.mock.calls[2][0]).toBe( + 'Attempting to download v20.0.0-v8-canary20221103f7e2421e91...' + ); + expect(dbgSpy.mock.calls[5][0]).toBe('No manifest cached'); + expect(dbgSpy.mock.calls[6][0]).toBe( + 'Getting manifest from actions/node-versions@main' + ); + expect(dbgSpy.mock.calls[7][0].slice(0, 6)).toBe('check '); + expect(dbgSpy.mock.calls[13][0].slice(0, 6)).toBe('check '); + expect(logSpy.mock.calls[3][0]).toBe( + 'Not found in manifest. Falling back to download directly from Node' + ); + expect(dbgSpy.mock.calls[14][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[15][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(dbgSpy.mock.calls[16][0]).toBe('requested v8 canary distribution'); + expect(logSpy.mock.calls[4][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'; + + findAllVersionSpy.mockImplementation(() => [ + 'v20.0.0-v8-canary20221103f7e2421e91' + ]); + + await main.run(); + + expect(dbgSpy.mock.calls[0][0]).toBe('requested v8 canary distribution'); + expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[2][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[0][0]).toBe( + 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' + ); + expect(logSpy.mock.calls[1][0]).toBe( + 'Attempt to find existing version in cache...' + ); + expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 1 versions'); + expect(dbgSpy.mock.calls[4][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[2][0]).toBe( + 'Found in cache @ v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(cnSpy.mock.calls[1][0].trim()).toBe( + `::add-path::v20.0.0-v8-canary20221103f7e2421e91${path.sep}bin` + ); + + expect(dlSpy).not.toHaveBeenCalled(); + expect(exSpy).not.toHaveBeenCalled(); + expect(cacheSpy).not.toHaveBeenCalled(); + }); + }); }); describe('helper methods', () => { diff --git a/src/installer.ts b/src/installer.ts index 24b53970c..fa86711fc 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -233,12 +233,10 @@ function findNightlyVersionInHostedToolcache( osArch: string ) { const foundAllVersions = tc.findAllVersions('node', osArch); - const version = evaluateVersions(foundAllVersions, versionsSpec); - - return version; + return evaluateVersions(foundAllVersions, versionsSpec); } -function isLtsAlias(versionSpec: string): boolean { +export function isLtsAlias(versionSpec: string): boolean { return versionSpec.startsWith('lts/'); } @@ -247,7 +245,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[] @@ -421,7 +419,10 @@ function evaluateNightlyVersions( } // TODO - should we just export this from @actions/tool-cache? Lifted directly from there -function evaluateVersions(versions: string[], versionSpec: string): string { +export function evaluateVersions( + versions: string[], + versionSpec: string +): string { let version = ''; core.debug(`evaluating ${versions.length} versions`); @@ -622,7 +623,7 @@ 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); } From 3e844cd766709ca2a5d7821a641644295bfe3f92 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Wed, 9 Nov 2022 20:42:30 +0100 Subject: [PATCH 03/19] Add v8 canary tests to workflow --- .github/workflows/versions.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 88a1b0f2c..481ca1e43 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-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: From 21a386952d4bfce39160fd6d7a6431a5c583327c Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Wed, 9 Nov 2022 20:43:04 +0100 Subject: [PATCH 04/19] Change docs --- README.md | 19 +++++++-------- docs/advanced-usage.md | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f2fbd3d99..61edf1e26 100644 --- a/README.md +++ b/README.md @@ -129,15 +129,16 @@ 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) +1. [Using a node version file](docs/advanced-usage.md#node-version-file) +1. [Using different architectures](docs/advanced-usage.md#architecture) +1. [Using v8 canary versions](docs/advanced-usage.md#v8-canary-versions) +1. [Using nigthly versions](docs/advanced-usage.md#nightly-versions) +1. [Using rc versions](docs/advanced-usage.md#rc-versions) +1. [Caching packages data](docs/advanced-usage.md#caching-packages-data) +1. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures) +1. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm) +1. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn) +1. [Using private packages](docs/advanced-usage.md#use-private-packages) ## License diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 4789f2ead..771d0b829 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -104,6 +104,58 @@ 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 +>>>>>>> d703091 (Change docs) + +```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. From bced0aece0e7e3d1cd8189f551fd797e0d252af6 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Wed, 9 Nov 2022 20:43:40 +0100 Subject: [PATCH 05/19] Build compiled js code --- dist/setup/index.js | 65 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/dist/setup/index.js b/dist/setup/index.js index aa4f708af..291a6a129 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73216,6 +73216,7 @@ 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)); +const isVersionCanary = (versionSpec) => versionSpec.includes(`-v8-canary`); 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 @@ -73224,16 +73225,18 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc let isNightly = versionSpec.includes('nightly'); let osPlat = os_1.default.platform(); let osArch = translateArchToDistUrl(arch); + let isCanary = isVersionCanary(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 manifest = yield getManifest(auth); versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - if (isLatestSyntax(versionSpec)) { + // evaluate exact versionSpec from input + if (isLatestSyntax(versionSpec) || isCanary) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); - core.info(`getting latest node version...`); + core.info(`getting ${isCanary ? 'v8-canary' : 'latest'} node version ${versionSpec}...`); } if (isNightly && checkLatest) { nodeVersions = yield getVersionsFromDist(versionSpec); @@ -73251,11 +73254,16 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc } } // check cache + core.info('Attempt to find existing version in cache...'); let toolPath; if (isNightly) { const nightlyVersion = findNightlyVersionInHostedToolcache(versionSpec, osArch); toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); } + else if (isCanary) { + const localVersions = tc.findAllVersions('node', osArch); + toolPath = evaluateVersions(localVersions, versionSpec); + } else { toolPath = tc.find('node', versionSpec, osArch); } @@ -73356,12 +73364,12 @@ 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; + return evaluateVersions(foundAllVersions, versionsSpec); } 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 +73399,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; @@ -73487,9 +73496,12 @@ function evaluateVersions(versions, versionSpec) { return evaluateNightlyVersions(versions, versionSpec); } versions = versions.sort(semver.rcompare); + const matcher = isVersionCanary(versionSpec) + ? evaluateCanaryMatcher(versionSpec) + : potential => semver.satisfies(potential, versionSpec); for (let i = versions.length - 1; i >= 0; i--) { const potential = versions[i]; - const satisfied = semver.satisfies(potential, versionSpec); + const satisfied = matcher(potential); if (satisfied) { version = potential; break; @@ -73503,15 +73515,23 @@ function evaluateVersions(versions, versionSpec) { } return version; } +exports.evaluateVersions = evaluateVersions; function getNodejsDistUrl(version) { const prerelease = semver.prerelease(version); if (version.includes('nightly')) { + core.debug('requested nightly distribution'); return 'https://nodejs.org/download/nightly'; } else if (prerelease) { return 'https://nodejs.org/download/rc'; } - return 'https://nodejs.org/dist'; + else if (isVersionCanary(version)) { + core.debug('requested v8 canary distribution'); + return 'https://nodejs.org/download/v8-canary'; + } + else { + return 'https://nodejs.org/dist'; + } } exports.getNodejsDistUrl = getNodejsDistUrl; function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions) { @@ -73537,11 +73557,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; } + let versions = []; nodeVersions.forEach((nodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { @@ -73553,10 +73573,11 @@ function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions 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 +73678,32 @@ exports.parseNodeVersionFile = parseNodeVersionFile; function isLatestSyntax(versionSpec) { return ['current', 'latest', 'node'].includes(versionSpec); } +exports.isLatestSyntax = isLatestSyntax; +function evaluateCanaryMatcher(versionSpec) { + var _a; + const [raw, prerelease] = versionSpec.split(/-(.*)/s); + const isValidVersion = semver.valid(raw); + const rawVersion = isValidVersion ? raw : (_a = semver.coerce(raw)) === null || _a === void 0 ? void 0 : _a.version; + if (rawVersion) { + if (prerelease === 'v8-canary') { + // it means versionSpec does not have timestamp + const range = semver.validRange(`^${rawVersion}`); + return (potential) => semver.satisfies( + // TODO: check latest? + potential.replace('-v8-canary', '+v8-canary.'), range); + } + else { + // see https://github.com/actions/setup-node/blob/00e1b6691b40cce14b5078cb411dd1ec7dab07f7/__tests__/verify-node.sh#L10 + // there must be exact match + const range = `${rawVersion}-${prerelease}`; + return (potential) => semver.satisfies( + // TODO: check latest? + potential, range); + } + } + return () => false; +} +exports.evaluateCanaryMatcher = evaluateCanaryMatcher; /***/ }), From f40727714820d76fb8172952495c82468d7a0693 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Fri, 11 Nov 2022 09:10:22 +0100 Subject: [PATCH 06/19] Apply requested changes --- dist/setup/index.js | 3 +-- src/installer.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dist/setup/index.js b/dist/setup/index.js index 291a6a129..887654434 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73569,8 +73569,7 @@ function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions } }); // get the latest version that matches the version spec - let version = evaluateVersions(versions, versionSpec); - return version; + return evaluateVersions(versions, versionSpec); }); } exports.queryDistForMatch = queryDistForMatch; diff --git a/src/installer.ts b/src/installer.ts index fa86711fc..cb6826982 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -512,8 +512,7 @@ export async function queryDistForMatch( }); // get the latest version that matches the version spec - let version = evaluateVersions(versions, versionSpec); - return version; + return evaluateVersions(versions, versionSpec); } export async function getVersionsFromDist( From 5f26e47b75365cc7598c597b01ab90f57e594469 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Tue, 22 Nov 2022 12:33:08 +0100 Subject: [PATCH 07/19] fixes after rebase --- src/installer.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index cb6826982..2e54ba41b 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -273,8 +273,8 @@ export function resolveLtsAliasFromManifest( alias === '*' ? numbered[numbered.length - 1] : n < 0 - ? numbered[numbered.length - 1 + n] - : aliases[alias]; + ? numbered[numbered.length - 1 + n] + : aliases[alias]; if (!release) { throw new Error( @@ -419,31 +419,27 @@ function evaluateNightlyVersions( } // TODO - should we just export this from @actions/tool-cache? Lifted directly from there -export function evaluateVersions( - versions: string[], - versionSpec: string -): string { - let version = ''; +export function evaluateVersions(versions: string[], versionSpec: string): string { core.debug(`evaluating ${versions.length} versions`); if (versionSpec.includes('nightly')) { return evaluateNightlyVersions(versions, versionSpec); } - versions = versions.sort(semver.rcompare); - const matcher: (potential: string) => boolean = isVersionCanary(versionSpec) ? evaluateCanaryMatcher(versionSpec) : potential => semver.satisfies(potential, versionSpec); - for (let i = versions.length - 1; i >= 0; i--) { + const version = versions.sort(semver.rcompare).find(matcher) || '' + /* + for (let i = 0; i< versions.length; i++) { const potential: string = versions[i]; const satisfied: boolean = matcher(potential); if (satisfied) { version = potential; break; } - } + }*/ if (version) { core.debug(`matched: ${version}`); @@ -455,7 +451,7 @@ export function evaluateVersions( } export function getNodejsDistUrl(version: string) { - const prerelease = semver.prerelease(version); + const prerelease = semver.prerelease(version) && !isVersionCanary(version); if (version.includes('nightly')) { core.debug('requested nightly distribution'); return 'https://nodejs.org/download/nightly'; From 0081915898fa7e1a84ed846a623d023eb40b0996 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 05:16:09 +0100 Subject: [PATCH 08/19] refactor nightly and rc --- __tests__/installer.unit.test.ts | 1551 ++++++++++++++++++++++++++++++ src/installer.ts | 265 ++--- 2 files changed, 1686 insertions(+), 130 deletions(-) create mode 100644 __tests__/installer.unit.test.ts diff --git a/__tests__/installer.unit.test.ts b/__tests__/installer.unit.test.ts new file mode 100644 index 000000000..a38a75361 --- /dev/null +++ b/__tests__/installer.unit.test.ts @@ -0,0 +1,1551 @@ +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 fs from 'fs'; +import cp from 'child_process'; +import osm from 'os'; +import path from 'path'; +import each from 'jest-each'; +import * as main from '../src/main'; +import * as auth from '../src/authutil'; + +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; + let os = {} as any; + + let inSpy: jest.SpyInstance; + let findSpy: jest.SpyInstance; + let findAllVersionsSpy: jest.SpyInstance; + let cnSpy: jest.SpyInstance; + let logSpy: jest.SpyInstance; + let warningSpy: jest.SpyInstance; + let getManifestSpy: jest.SpyInstance; + let getDistSpy: jest.SpyInstance; + let platSpy: jest.SpyInstance; + let archSpy: jest.SpyInstance; + let dlSpy: jest.SpyInstance; + let exSpy: jest.SpyInstance; + let cacheSpy: jest.SpyInstance; + let dbgSpy: jest.SpyInstance; + let whichSpy: jest.SpyInstance; + let existsSpy: jest.SpyInstance; + let readFileSyncSpy: jest.SpyInstance; + let mkdirpSpy: jest.SpyInstance; + let execSpy: jest.SpyInstance; + let authSpy: jest.SpyInstance; + let parseNodeVersionSpy: jest.SpyInstance; + let isCacheActionAvailable: jest.SpyInstance; + let getExecOutputSpy: jest.SpyInstance; + let getJsonSpy: jest.SpyInstance; + + beforeEach(() => { + // @actions/core + console.log('::stop-commands::stoptoken'); // Disable executing of runner commands when running tests in actions + 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 + inputs = {}; + inSpy = jest.spyOn(core, 'getInput'); + inSpy.mockImplementation(name => inputs[name]); + + // node + os = {}; + platSpy = jest.spyOn(osm, 'platform'); + platSpy.mockImplementation(() => os['platform']); + archSpy = jest.spyOn(osm, 'arch'); + archSpy.mockImplementation(() => os['arch']); + execSpy = jest.spyOn(cp, 'execSync'); + + // @actions/tool-cache + findSpy = jest.spyOn(tc, 'find'); + findAllVersionsSpy = jest.spyOn(tc, 'findAllVersions'); + dlSpy = jest.spyOn(tc, 'downloadTool'); + exSpy = jest.spyOn(tc, 'extractTar'); + cacheSpy = jest.spyOn(tc, 'cacheDir'); + getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo'); + // @ts-ignore + getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); + parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile'); + + // http-client + getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson'); + + // io + whichSpy = jest.spyOn(io, 'which'); + existsSpy = jest.spyOn(fs, 'existsSync'); + mkdirpSpy = jest.spyOn(io, 'mkdirP'); + + // @actions/tool-cache + isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable'); + + // disable authentication portion for installer tests + authSpy = jest.spyOn(auth, 'configAuthentication'); + authSpy.mockImplementation(() => { + }); + + // gets + getManifestSpy.mockImplementation( + () => nodeTestManifest + ); + + getDistSpy.mockImplementation(version => { + const initialUrl = im.getNodejsDistUrl(version); + if (initialUrl.endsWith('/rc')) { + return nodeTestDistRc; + } else if (initialUrl.endsWith('/nightly')) { + return nodeTestDistNightly; + } else { + return nodeTestDist; + } + }); + + getJsonSpy.mockImplementation(url => { + let res: any; + if (url.includes('/rc')) { + res = nodeTestDistRc; + } else if (url.includes('/nightly')) { + res = nodeTestDistNightly; + } else { + res = nodeTestDist; + } + + return {result: res}; + }); + + // writes + cnSpy = jest.spyOn(process.stdout, 'write'); + logSpy = jest.spyOn(core, 'info'); + dbgSpy = jest.spyOn(core, 'debug'); + warningSpy = jest.spyOn(core, 'warning'); + cnSpy.mockImplementation(line => { + // uncomment to debug + // process.stderr.write('write:' + line + '\n'); + }); + logSpy.mockImplementation(line => { + // uncomment to debug + // process.stderr.write('log:' + line + '\n'); + }); + dbgSpy.mockImplementation(msg => { + // uncomment to see debug output + // process.stderr.write(msg + '\n'); + }); + warningSpy.mockImplementation(msg => { + // uncomment to debug + // process.stderr.write('log:' + msg + '\n'); + }); + + // @actions/exec + getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); + getExecOutputSpy.mockImplementation(() => 'v16.15.0'); + }); + + afterEach(() => { + jest.resetAllMocks(); + jest.clearAllMocks(); + //jest.restoreAllMocks(); + }); + + afterAll(async () => { + console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions + jest.restoreAllMocks(); + }, 100000); + + //-------------------------------------------------- + // Manifest find tests + //-------------------------------------------------- + it('can mock manifest versions', async () => { + let versions: tc.IToolRelease[] | null = await tc.getManifestFromRepo( + 'actions', + 'node-versions', + 'mocktoken' + ); + expect(versions).toBeDefined(); + expect(versions?.length).toBe(7); + }); + + it('can mock dist versions', async () => { + const versionSpec = '1.2.3'; + let versions: im.INodeVersion[] = await im.getVersionsFromDist(versionSpec); + expect(versions).toBeDefined(); + expect(versions?.length).toBe(23); + }); + + it.each([ + ['12.16.2', 'darwin', '12.16.2', 'Erbium'], + ['12', 'linux', '12.16.2', 'Erbium'], + ['10', 'win32', '10.20.1', 'Dubnium'], + ['*', 'linux', '14.0.0', 'Fermium'] + ])( + 'can find %s from manifest on %s', + async (versionSpec, platform, expectedVersion, expectedLts) => { + os.platform = platform; + os.arch = 'x64'; + let versions: tc.IToolRelease[] | null = await tc.getManifestFromRepo( + 'actions', + 'node-versions', + 'mocktoken' + ); + expect(versions).toBeDefined(); + let match = await tc.findFromManifest(versionSpec, true, versions); + expect(match).toBeDefined(); + expect(match?.version).toBe(expectedVersion); + expect((match as any).lts).toBe(expectedLts); + } + ); + + //-------------------------------------------------- + // Found in cache tests + //-------------------------------------------------- + + it('finds version in cache with stable true', async () => { + inputs['node-version'] = '12'; + inputs.stable = 'true'; + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + await main.run(); + + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + }); + + it('finds version in cache with stable not supplied', async () => { + inputs['node-version'] = '12'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + await main.run(); + + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + }); + + it('finds version in cache and adds it to the path', async () => { + inputs['node-version'] = '12'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + await main.run(); + + let expPath = path.join(toolPath, 'bin'); + expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); + }); + + it('handles unhandled find error and reports error', async () => { + let errMsg = 'unhandled error message'; + inputs['node-version'] = '12'; + + findSpy.mockImplementation(() => { + throw new Error(errMsg); + }); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL); + }); + + //-------------------------------------------------- + // Manifest tests + //-------------------------------------------------- + + it('downloads a version from a manifest match', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + // a version which is in the manifest + let versionSpec = '12.16.2'; + let resolvedVersion = versionSpec; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + let expectedUrl = + 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz'; + + // ... but not in the local cache + 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(); + + let expPath = path.join(toolPath, 'bin'); + + expect(getExecOutputSpy).toHaveBeenCalledWith( + 'node', + ['--version'], + expect.anything() + ); + expect(getExecOutputSpy).toHaveBeenCalledWith( + 'npm', + ['--version'], + expect.anything() + ); + expect(getExecOutputSpy).toHaveBeenCalledWith( + 'yarn', + ['--version'], + expect.anything() + ); + expect(dlSpy).toHaveBeenCalled(); + expect(exSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + `Acquiring ${resolvedVersion} - ${os.arch} from ${expectedUrl}` + ); + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${versionSpec}...` + ); + expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); + }); + + it('falls back to a version from node dist', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + // a version which is not in the manifest but is in node dist + let versionSpec = '11.15.0'; + let resolvedVersion = versionSpec; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + let expectedUrl = + 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz'; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + let toolPath = path.normalize('/cache/node/11.11.0/x64'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + await main.run(); + + let expPath = path.join(toolPath, 'bin'); + + expect(dlSpy).toHaveBeenCalled(); + expect(exSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + 'Not found in manifest. Falling back to download directly from Node' + ); + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${versionSpec}...` + ); + expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); + }); + + it('does not find a version that does not exist', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + let versionSpec = '9.99.9'; + inputs['node-version'] = versionSpec; + + findSpy.mockImplementation(() => ''); + await main.run(); + + expect(logSpy).toHaveBeenCalledWith( + 'Not found in manifest. Falling back to download directly from Node' + ); + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${versionSpec}...` + ); + expect(cnSpy).toHaveBeenCalledWith( + `::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}` + ); + }); + + it('reports a failed download', async () => { + let errMsg = 'unhandled download message'; + os.platform = 'linux'; + os.arch = 'x64'; + + // a version which is in the manifest + let versionSpec = '12.16.2'; + let resolvedVersion = versionSpec; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + findSpy.mockImplementation(() => ''); + dlSpy.mockImplementation(() => { + throw new Error(errMsg); + }); + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`); + }); + + it('acquires specified architecture of node', async () => { + for (const {arch, version, osSpec} of [ + {arch: 'x86', version: '12.16.2', osSpec: 'win32'}, + {arch: 'x86', version: '14.0.0', osSpec: 'win32'} + ]) { + os.platform = osSpec; + os.arch = arch; + const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz'; + const platform = { + linux: 'linux', + darwin: 'darwin', + win32: 'win' + }[os.platform]; + + inputs['node-version'] = version; + inputs['architecture'] = arch; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + let expectedUrl = + arch === 'x64' + ? `https://github.com/actions/node-versions/releases/download/${version}/node-${version}-${platform}-${arch}.zip` + : `https://nodejs.org/dist/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + let toolPath = path.normalize(`/cache/node/${version}/${arch}`); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + await main.run(); + expect(dlSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + `Acquiring ${version} - ${arch} from ${expectedUrl}` + ); + } + }, 100000); + + describe('check-latest flag', () => { + it('use local version and dont check manifest if check-latest is not specified', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + inputs['node-version'] = '12'; + inputs['check-latest'] = 'false'; + + const toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockReturnValue(toolPath); + await main.run(); + + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + expect(logSpy).not.toHaveBeenCalledWith( + 'Attempt to resolve the latest version from manifest...' + ); + expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached'); + expect(dbgSpy).not.toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + }); + + it('check latest version and resolve it from local cache', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + inputs['node-version'] = '12'; + inputs['check-latest'] = 'true'; + + const toolPath = path.normalize('/cache/node/12.16.2/x64'); + findSpy.mockReturnValue(toolPath); + dlSpy.mockImplementation(async () => '/some/temp/path'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve the latest version from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(logSpy).toHaveBeenCalledWith("Resolved as '12.16.2'"); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + }); + + it('check latest version and install it from manifest', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + inputs['node-version'] = '12'; + inputs['check-latest'] = 'true'; + + findSpy.mockImplementation(() => ''); + dlSpy.mockImplementation(async () => '/some/temp/path'); + const toolPath = path.normalize('/cache/node/12.16.2/x64'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + const expectedUrl = + 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz'; + + await main.run(); + + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve the latest version from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(logSpy).toHaveBeenCalledWith("Resolved as '12.16.2'"); + expect(logSpy).toHaveBeenCalledWith( + `Acquiring 12.16.2 - ${os.arch} from ${expectedUrl}` + ); + expect(logSpy).toHaveBeenCalledWith('Extracting ...'); + }); + + it('fallback to dist if version if not found in manifest', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + // a version which is not in the manifest but is in node dist + let versionSpec = '11'; + + inputs['node-version'] = versionSpec; + inputs['check-latest'] = 'true'; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + let toolPath = path.normalize('/cache/node/11.11.0/x64'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + await main.run(); + + let expPath = path.join(toolPath, 'bin'); + + expect(dlSpy).toHaveBeenCalled(); + expect(exSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve the latest version from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(logSpy).toHaveBeenCalledWith( + `Failed to resolve version ${versionSpec} from manifest` + ); + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${versionSpec}...` + ); + expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); + }); + + it('fallback to dist if manifest is not available', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + // a version which is not in the manifest but is in node dist + let versionSpec = '12'; + + inputs['node-version'] = versionSpec; + inputs['check-latest'] = 'true'; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + getManifestSpy.mockImplementation(() => { + throw new Error('Unable to download manifest'); + }); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + let toolPath = path.normalize('/cache/node/12.11.0/x64'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + await main.run(); + + let expPath = path.join(toolPath, 'bin'); + + expect(dlSpy).toHaveBeenCalled(); + expect(exSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve the latest version from manifest...' + ); + expect(logSpy).toHaveBeenCalledWith( + 'Unable to resolve version from manifest...' + ); + expect(logSpy).toHaveBeenCalledWith( + `Failed to resolve version ${versionSpec} from manifest` + ); + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${versionSpec}...` + ); + expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); + }); + }); + + describe('node-version-file flag', () => { + it('not used if node-version is provided', async () => { + // Arrange + inputs['node-version'] = '12'; + + // Act + await main.run(); + + // Assert + expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); + }); + + it('not used if node-version-file not provided', async () => { + // Act + await main.run(); + + // Assert + expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); + }); + + it('reads node-version-file if 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) + ); + + // 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('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'; + 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) + ); + + // Act + await main.run(); + + // Assert + expect(existsSpy).toHaveBeenCalled(); + expect(existsSpy).toHaveReturnedWith(false); + expect(parseNodeVersionSpy).not.toHaveBeenCalled(); + expect(cnSpy).toHaveBeenCalledWith( + `::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}` + ); + }); + }); + + describe('cache on GHES', () => { + it('Should throw an error, because cache is not supported', async () => { + inputs['node-version'] = '12'; + inputs['cache'] = 'npm'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + + // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + process.env['GITHUB_SERVER_URL'] = 'https://www.test.com'; + isCacheActionAvailable.mockImplementation(() => false); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + `::error::Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.${osm.EOL}` + ); + }); + + it('Should throw an internal error', async () => { + inputs['node-version'] = '12'; + inputs['cache'] = 'npm'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + + // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + process.env['GITHUB_SERVER_URL'] = ''; + isCacheActionAvailable.mockImplementation(() => false); + + await main.run(); + + expect(warningSpy).toHaveBeenCalledWith( + 'The runner was not able to contact the cache service. Caching will be skipped' + ); + }); + }); + + describe('LTS version', () => { + beforeEach(() => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs.stable = 'true'; + }); + + it.each([ + ['erbium', '12.16.2'], + ['*', '14.0.0'], + ['-1', '12.16.2'] + ])( + 'find latest LTS version and resolve it from local cache (lts/%s)', + async (lts, expectedVersion) => { + // arrange + inputs['node-version'] = `lts/${lts}`; + + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + findSpy.mockReturnValue(toolPath); + + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve LTS alias from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached'); + expect(dbgSpy).toHaveBeenCalledWith( + `LTS alias '${lts}' for Node version 'lts/${lts}'` + ); + expect(dbgSpy).toHaveBeenCalledWith( + `Found LTS release '${expectedVersion}' for Node version 'lts/${lts}'` + ); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + + it.each([ + [ + 'erbium', + '12.16.2', + 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz' + ], + [ + '*', + '14.0.0', + 'https://github.com/actions/node-versions/releases/download/14.0.0-20200507.99/node-14.0.0-linux-x64.tar.gz' + ], + [ + '-1', + '12.16.2', + 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz' + ] + ])( + 'find latest LTS version and install it from manifest (lts/%s)', + async (lts, expectedVersion, expectedUrl) => { + // arrange + inputs['node-version'] = `lts/${lts}`; + + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + findSpy.mockImplementation(() => ''); + dlSpy.mockImplementation(async () => '/some/temp/path'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + const expectedMajor = expectedVersion.split('.')[0]; + + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve LTS alias from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached'); + expect(dbgSpy).toHaveBeenCalledWith( + `LTS alias '${lts}' for Node version 'lts/${lts}'` + ); + expect(dbgSpy).toHaveBeenCalledWith( + `Found LTS release '${expectedVersion}' for Node version 'lts/${lts}'` + ); + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${expectedMajor}...` + ); + expect(logSpy).toHaveBeenCalledWith( + `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` + ); + expect(logSpy).toHaveBeenCalledWith('Extracting ...'); + expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + + it('fail with unable to parse LTS alias (lts/)', async () => { + // arrange + inputs['node-version'] = 'lts/'; + + findSpy.mockImplementation(() => ''); + + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve LTS alias from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(cnSpy).toHaveBeenCalledWith( + `::error::Unable to parse LTS alias for Node version 'lts/'${osm.EOL}` + ); + }); + + it('fail to find LTS version (lts/unknown)', async () => { + // arrange + inputs['node-version'] = 'lts/unknown'; + + findSpy.mockImplementation(() => ''); + + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve LTS alias from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(dbgSpy).toHaveBeenCalledWith( + `LTS alias 'unknown' for Node version 'lts/unknown'` + ); + expect(cnSpy).toHaveBeenCalledWith( + `::error::Unable to find LTS release 'unknown' for Node version 'lts/unknown'.${osm.EOL}` + ); + }); + + it('fail if manifest is not available', async () => { + // arrange + inputs['node-version'] = 'lts/erbium'; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + getManifestSpy.mockImplementation(() => { + throw new Error('Unable to download manifest'); + }); + + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + 'Attempt to resolve LTS alias from manifest...' + ); + expect(dbgSpy).toHaveBeenCalledWith( + 'Getting manifest from actions/node-versions@main' + ); + expect(cnSpy).toHaveBeenCalledWith( + `::error::Unable to download manifest${osm.EOL}` + ); + }); + }); + + describe('rc versions', () => { + it.each([ + [ + '13.10.1-rc.0', + '13.10.1-rc.0', + 'https://nodejs.org/download/rc/v13.10.1-rc.0/node-v13.10.1-rc.0-linux-x64.tar.gz' + ], + [ + '14.15.5-rc.1', + '14.15.5-rc.1', + 'https://nodejs.org/download/rc/v14.15.5-rc.1/node-v14.15.5-rc.1-linux-x64.tar.gz' + ], + [ + '16.17.0-rc.1', + '16.17.0-rc.1', + 'https://nodejs.org/download/rc/v16.17.0-rc.1/node-v16.17.0-rc.1-linux-x64.tar.gz' + ], + [ + '17.0.0-rc.1', + '17.0.0-rc.1', + 'https://nodejs.org/download/rc/v17.0.0-rc.1/node-v17.0.0-rc.1-linux-x64.tar.gz' + ], + [ + '19.0.0-rc.2', + '19.0.0-rc.2', + 'https://nodejs.org/download/rc/v19.0.0-rc.2/node-v19.0.0-rc.2-linux-x64.tar.gz' + ] + ])( + 'finds the versions in the index.json and installs it', + async (input, expectedVersion, expectedUrl) => { + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => []); + dlSpy.mockImplementation(async () => '/some/temp/path'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + inputs['node-version'] = input; + os['arch'] = 'x64'; + os['platform'] = 'linux'; + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${input}...` + ); + + expect(logSpy).toHaveBeenCalledWith( + `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` + ); + expect(logSpy).toHaveBeenCalledWith('Extracting ...'); + expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + + it.each([ + ['13.10.1-rc.0', '13.10.1-rc.0'], + ['14.15.5-rc.1', '14.15.5-rc.1'], + ['16.17.0-rc.1', '16.17.0-rc.1'], + ['17.0.0-rc.1', '17.0.0-rc.1'] + ])( + 'finds the %s version in the hostedToolcache', + async (input, expectedVersion) => { + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + 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'; + os['platform'] = 'linux'; + + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + + it('throws an error if version is not found', async () => { + const versionSpec = '19.0.0-rc.3'; + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => []); + dlSpy.mockImplementation(async () => '/some/temp/path'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + + inputs['node-version'] = versionSpec; + os['arch'] = 'x64'; + os['platform'] = 'linux'; + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${versionSpec}...` + ); + expect(cnSpy).toHaveBeenCalledWith( + `::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}` + ); + }); + }); + + describe('nightly versions', () => { + it.each([ + [ + '17.5.0-nightly', + '17.5.0-nightly20220209e43808936a', + 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' + ], + [ + '17-nightly', + '17.5.0-nightly20220209e43808936a', + 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' + ], + [ + '18.0.0-nightly', + '18.0.0-nightly20220419bde889bd4e', + 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' + ], + [ + '18-nightly', + '18.0.0-nightly20220419bde889bd4e', + 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' + ], + [ + '20.0.0-nightly', + '20.0.0-nightly2022101987cdf7d412', + 'https://nodejs.org/download/nightly/v20.0.0-nightly2022101987cdf7d412/node-v20.0.0-nightly2022101987cdf7d412-linux-x64.tar.gz' + ] + ])( + 'finds the versions in the index.json and installs it', + async (input, expectedVersion, expectedUrl) => { + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => []); + dlSpy.mockImplementation(async () => '/some/temp/path'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + inputs['node-version'] = input; + os['arch'] = 'x64'; + os['platform'] = 'linux'; + // act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith( + `Attempting to download ${input}...` + ); + + expect(logSpy).toHaveBeenCalledWith( + `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` + ); + expect(logSpy).toHaveBeenCalledWith('Extracting ...'); + expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + + it.each([ + ['17.5.0-nightly', '17.5.0-nightly20220209e43808936a'], + ['17-nightly', '17.5.0-nightly20220209e43808936a'], + ['20.0.0-nightly', '20.0.0-nightly2022101987cdf7d412'] + ])( + 'finds the %s version in the hostedToolcache', + async (input, expectedVersion) => { + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + findSpy.mockReturnValue(toolPath); + findAllVersionsSpy.mockReturnValue([ + '17.5.0-nightly20220209e43808936a', + '17.5.0-nightly20220209e43808935a', + '20.0.0-nightly2022101987cdf7d412', + '20.0.0-nightly2022101987cdf7d411' + ]); + + inputs['node-version'] = input; + os['arch'] = 'x64'; + os['platform'] = 'linux'; + + // act + await main.run(); + + // assert + expect(findAllVersionsSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + + it.each([ + [ + '17.5.0-nightly', + '17.5.0-nightly20220209e43808936a', + '17.0.0-nightly202110193f11666dc7', + 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' + ], + [ + '17-nightly', + '17.5.0-nightly20220209e43808936a', + '17.0.0-nightly202110193f11666dc7', + 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' + ], + [ + '18.0.0-nightly', + '18.0.0-nightly20220419bde889bd4e', + '18.0.0-nightly202204180699150267', + 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' + ], + [ + '18-nightly', + '18.0.0-nightly20220419bde889bd4e', + '18.0.0-nightly202204180699150267', + 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' + ], + [ + '20.0.0-nightly', + '20.0.0-nightly2022101987cdf7d412', + '20.0.0-nightly2022101987cdf7d411', + 'https://nodejs.org/download/nightly/v20.0.0-nightly2022101987cdf7d412/node-v20.0.0-nightly2022101987cdf7d412-linux-x64.tar.gz' + ] + ])( + 'get %s version from dist if check-latest is true', + async (input, expectedVersion, foundVersion, expectedUrl) => { + const foundToolPath = path.normalize(`/cache/node/${foundVersion}/x64`); + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + + inputs['node-version'] = input; + inputs['check-latest'] = 'true'; + os['arch'] = 'x64'; + os['platform'] = 'linux'; + + findSpy.mockReturnValue(foundToolPath); + findAllVersionsSpy.mockReturnValue([ + '17.0.0-nightly202110193f11666dc7', + '18.0.0-nightly202204180699150267', + '20.0.0-nightly2022101987cdf7d411' + ]); + dlSpy.mockImplementation(async () => '/some/temp/path'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + + // act + await main.run(); + + // assert + expect(findAllVersionsSpy).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalledWith( + `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` + ); + expect(logSpy).toHaveBeenCalledWith('Extracting ...'); + expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` + ); + } + ); + }); + + describe('latest alias syntax', () => { + it.each(['latest', 'current', 'node'])( + 'download the %s version if alias is provided', + async inputVersion => { + // Arrange + inputs['node-version'] = inputVersion; + + os.platform = 'darwin'; + os.arch = 'x64'; + + findSpy.mockImplementation(() => ''); + getManifestSpy.mockImplementation(() => { + throw new Error('Unable to download manifest'); + }); + + // Act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith('Unable to download manifest'); + + expect(logSpy).toHaveBeenCalledWith('getting latest node version...'); + } + ); + }); + + describe('latest alias syntax from cache', () => { + it.each(['latest', 'current', 'node'])( + 'download the %s version if alias is provided', + async inputVersion => { + // Arrange + inputs['node-version'] = inputVersion; + const expectedVersion = nodeTestDist[0]; + + os.platform = 'darwin'; + os.arch = 'x64'; + + const toolPath = path.normalize( + `/cache/node/${expectedVersion.version}/x64` + ); + findSpy.mockReturnValue(toolPath); + + // Act + await main.run(); + + // assert + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + + expect(logSpy).toHaveBeenCalledWith('getting latest node version...'); + } + ); + }); + + describe('setup-node v8 canary unit tests', () => { + + it('v20-v8-canary should match any minor and patch version', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); + 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('v20-v8-canary should not match v21.x & v19.x', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); + 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('v20.1-v8-canary should match any v20.1 patch version and minor above or eq v20.1', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.1-v8-canary'); + 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('v20.2-v8-canary should not match v21.x, v19.x, and v20 minor less v20.2', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.2-v8-canary'); + 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('v20.1.1-v8-canary should match v20.1.x patch versions above or eq v20.1.1', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('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('v20.1.1-v8-canary should match patch versions with any canary timestamp', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); + expect(matcher('v20.1.1-v8-canary202211026bf85d0fb4')).toBeTruthy(); + }); + + it('v20.1.1-v8-canary should not match any other minor versions and patch versions below v20.1.1', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + 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('v20.0.0-v8-canary20221103f7e2421e91 should match only v20.0.0-v8-canary20221103f7e2421e91', () => { + // @ts-ignore + const matcher = im.evaluateCanaryMatcher( + 'v20.0.0-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(); + }); + + it('v8 canary evaluateVersions 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' + ]; + // @ts-ignore + const version = im.evaluateVersions(versions, 'v20-v8-canary'); + expect(version).toBe('v20.1.1-v8-canary20221103f7e2421e91'); + }); + + it('v8 canary evaluateVersions 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' + ]; + // @ts-ignore + const version = im.evaluateVersions( + versions, + 'v20.0.1-v8-canary20221103f7e2421e92' + ); + expect(version).toBe('v20.0.1-v8-canary20221103f7e2421e92'); + }); + + it('v8 canary queryDistForMatch', async () => { + jest.spyOn(osm, 'platform').mockImplementationOnce(() => 'linux'); + // @ts-ignore + const version = await im.queryDistForMatch( + 'v20-v8-canary', + 'x64', + nodeV8CanaryTestDist + ); + expect(version).toBe('v20.0.0-v8-canary20221103f7e2421e91'); + }); + }); + + describe('setup-node v8 canary e2e 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('requested v8 canary distribution'); + expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[2][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[0][0]).toBe( + 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' + ); + expect(logSpy.mock.calls[1][0]).toBe( + 'Attempt to find existing version in cache...' + ); + expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 0 versions'); + expect(dbgSpy.mock.calls[4][0]).toBe('match not found'); + expect(logSpy.mock.calls[2][0]).toBe( + 'Attempting to download v20.0.0-v8-canary20221103f7e2421e91...' + ); + expect(dbgSpy.mock.calls[5][0]).toBe('No manifest cached'); + expect(dbgSpy.mock.calls[6][0]).toBe( + 'Getting manifest from actions/node-versions@main' + ); + expect(dbgSpy.mock.calls[7][0].slice(0, 6)).toBe('check '); + expect(dbgSpy.mock.calls[13][0].slice(0, 6)).toBe('check '); + expect(logSpy.mock.calls[3][0]).toBe( + 'Not found in manifest. Falling back to download directly from Node' + ); + expect(dbgSpy.mock.calls[14][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[15][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(dbgSpy.mock.calls[16][0]).toBe('requested v8 canary distribution'); + expect(logSpy.mock.calls[4][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'; + + findAllVersionSpy.mockImplementation(() => [ + 'v20.0.0-v8-canary20221103f7e2421e91' + ]); + + await main.run(); + + expect(dbgSpy.mock.calls[0][0]).toBe('requested v8 canary distribution'); + expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[2][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[0][0]).toBe( + 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' + ); + expect(logSpy.mock.calls[1][0]).toBe( + 'Attempt to find existing version in cache...' + ); + expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 1 versions'); + expect(dbgSpy.mock.calls[4][0]).toBe( + 'matched: v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(logSpy.mock.calls[2][0]).toBe( + 'Found in cache @ v20.0.0-v8-canary20221103f7e2421e91' + ); + expect(cnSpy.mock.calls[1][0].trim()).toBe( + `::add-path::v20.0.0-v8-canary20221103f7e2421e91${path.sep}bin` + ); + + 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'; + // @ts-ignore + const isLtsAlias = im.isLtsAlias(versionSpec); + expect(isLtsAlias).toBeFalsy(); + }); + + it('is not isLatestSyntax', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + // @ts-ignore + 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'; + // @ts-ignore + 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'; + // @ts-ignore + const url = im.getNodejsDistUrl(versionSpec); + expect(url).toBe('https://nodejs.org/download/v8-canary'); + }); + }); + + 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/src/installer.ts b/src/installer.ts index 2e54ba41b..fcd871eb7 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -30,8 +30,112 @@ interface INodeRelease extends tc.IToolRelease { lts?: string; } -const isVersionCanary = (versionSpec: string): boolean => - versionSpec.includes(`-v8-canary`); +export enum Distributions { + DEFAULT, + CANARY, + NIGHTLY, + RC, +} + +export const distributionOf = (versionSpec: string): Distributions => + versionSpec.includes('-v8-canary') + ? Distributions.CANARY + // TODO: i'd like to have this check, do you? + : versionSpec.includes('-canary') + ? (() => { + throw Error('Canary version must have "-v8-canary suffix"') + })() + : versionSpec.includes('nightly') + ? Distributions.NIGHTLY + : semver.prerelease(versionSpec) + ? Distributions.RC + : 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}-0`)}-0` + const matcher = (potential: string): boolean => + distributionOf(potential) === Distributions.NIGHTLY && + semver.satisfies(potential.replace('-nightly', '-nightly.'), range, {includePrerelease: true}) + matcher.factory = nightlyRangeVersionMatcherFactory + return matcher +} + +export const nightlyExactVersionMatcherFactory = (version: string, prerelease_tag: string): VersionMatcher => { + const range = `${version}-${prerelease_tag.replace('nightly', 'nightly.')}`; + const matcher = (potential: string): boolean => + distributionOf(potential) === Distributions.NIGHTLY && + semver.satisfies(potential.replace('-nightly', '-nightly.'), range, {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, @@ -43,10 +147,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); - let isCanary = isVersionCanary(versionSpec); + 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...'); @@ -57,23 +160,23 @@ export async function getNode( versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - // evaluate exact versionSpec from input - if (isLatestSyntax(versionSpec) || isCanary) { + // TODO: 121-127 and 131-132 seems to be the same. Why do we need them? + if (isLatestSyntax(versionSpec) || distribution == Distributions.CANARY) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); core.info( `getting ${ - isCanary ? 'v8-canary' : 'latest' + distribution == Distributions.CANARY ? 'v8-canary' : 'latest' } node version ${versionSpec}...` ); } - if (isNightly && checkLatest) { + if (distribution === Distributions.NIGHTLY && checkLatest) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); } - if (checkLatest && !isNightly) { + if (checkLatest && distribution !== Distributions.NIGHTLY) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = await resolveVersionFromManifest( versionSpec, @@ -93,17 +196,12 @@ export async function getNode( // check cache core.info('Attempt to find existing version in cache...'); let toolPath: string; - if (isNightly) { - const nightlyVersion = findNightlyVersionInHostedToolcache( - versionSpec, - osArch - ); - toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); - } else if (isCanary) { - const localVersions = tc.findAllVersions('node', osArch); - toolPath = evaluateVersions(localVersions, versionSpec); - } 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 @@ -228,14 +326,6 @@ export async function getNode( core.addPath(toolPath); } -function findNightlyVersionInHostedToolcache( - versionsSpec: string, - osArch: string -) { - const foundAllVersions = tc.findAllVersions('node', osArch); - return evaluateVersions(foundAllVersions, versionsSpec); -} - export function isLtsAlias(versionSpec: string): boolean { return versionSpec.startsWith('lts/'); } @@ -376,70 +466,15 @@ async function resolveVersionFromManifest( } } -function evaluateNightlyVersions( - 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 +// - 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 { core.debug(`evaluating ${versions.length} versions`); - if (versionSpec.includes('nightly')) { - return evaluateNightlyVersions(versions, versionSpec); - } - - const matcher: (potential: string) => boolean = isVersionCanary(versionSpec) - ? evaluateCanaryMatcher(versionSpec) - : potential => semver.satisfies(potential, versionSpec); - + const matcher = versionMatcherFactory(versionSpec) const version = versions.sort(semver.rcompare).find(matcher) || '' - /* - for (let i = 0; i< versions.length; i++) { - const potential: string = versions[i]; - const satisfied: boolean = matcher(potential); - if (satisfied) { - version = potential; - break; - } - }*/ if (version) { core.debug(`matched: ${version}`); @@ -451,17 +486,18 @@ export function evaluateVersions(versions: string[], versionSpec: string): strin } export function getNodejsDistUrl(version: string) { - const prerelease = semver.prerelease(version) && !isVersionCanary(version); - if (version.includes('nightly')) { - core.debug('requested nightly distribution'); - return 'https://nodejs.org/download/nightly'; - } else if (prerelease) { - return 'https://nodejs.org/download/rc'; - } else if (isVersionCanary(version)) { - core.debug('requested v8 canary distribution'); - return 'https://nodejs.org/download/v8-canary'; - } else { - return 'https://nodejs.org/dist'; + switch (distributionOf(version)) { + case Distributions.CANARY: + core.debug('requested v8 canary distribution'); + return 'https://nodejs.org/download/v8-canary'; + case Distributions.NIGHTLY: + core.debug('requested nightly distribution'); + return 'https://nodejs.org/download/nightly'; + case Distributions.RC: + core.debug('requested release candidates distribution'); + return 'https://nodejs.org/download/rc'; + case Distributions.DEFAULT: + return 'https://nodejs.org/dist'; } } @@ -621,34 +657,3 @@ export function parseNodeVersionFile(contents: string): string { export function isLatestSyntax(versionSpec): boolean { return ['current', 'latest', 'node'].includes(versionSpec); } - -export function evaluateCanaryMatcher( - versionSpec: string -): (potential: string) => boolean { - const [raw, prerelease] = versionSpec.split(/-(.*)/s); - const isValidVersion = semver.valid(raw); - const rawVersion = isValidVersion ? raw : semver.coerce(raw)?.version; - if (rawVersion) { - if (prerelease === 'v8-canary') { - // it means versionSpec does not have timestamp - const range = semver.validRange(`^${rawVersion}`); - return (potential: string) => - semver.satisfies( - // TODO: check latest? - potential.replace('-v8-canary', '+v8-canary.'), - range - ); - } else { - // see https://github.com/actions/setup-node/blob/00e1b6691b40cce14b5078cb411dd1ec7dab07f7/__tests__/verify-node.sh#L10 - // there must be exact match - const range = `${rawVersion}-${prerelease}`; - return (potential: string) => - semver.satisfies( - // TODO: check latest? - potential, - range - ); - } - } - return () => false; -} From 1536edb40eb53d15caf27da3e2bc63fd5c2d0681 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 05:16:27 +0100 Subject: [PATCH 09/19] Add tests --- __tests__/installer.test.ts | 217 +--- __tests__/installer.unit.test.ts | 1636 +++--------------------------- 2 files changed, 203 insertions(+), 1650 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 321034ae2..893f2471a 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -89,7 +89,8 @@ describe('setup-node', () => { // disable authentication portion for installer tests authSpy = jest.spyOn(auth, 'configAuthentication'); - authSpy.mockImplementation(() => {}); + authSpy.mockImplementation(() => { + }); // gets getManifestSpy.mockImplementation( @@ -1000,7 +1001,15 @@ 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'; @@ -1255,149 +1264,7 @@ describe('setup-node', () => { ); }); - describe('setup-node v8 canary unit tests', () => { - it('is not LTS alias', async () => { - const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore - const isLtsAlias = im.isLtsAlias(versionSpec); - expect(isLtsAlias).toBeFalsy(); - }); - - it('is not isLatestSyntax', async () => { - const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore - const isLatestSyntax = im.isLatestSyntax(versionSpec); - expect(isLatestSyntax).toBeFalsy(); - }); - - it('dist url to be https://nodejs.org/download/v8-canary for input versionSpec', () => { - const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore - 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'; - // @ts-ignore - const url = im.getNodejsDistUrl(versionSpec); - expect(url).toBe('https://nodejs.org/download/v8-canary'); - }); - - it('v20-v8-canary should match any minor and patch version', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); - 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('v20-v8-canary should not match v21.x & v19.x', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); - 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('v20.1-v8-canary should match any v20.1 patch version and minor above or eq v20.1', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1-v8-canary'); - 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('v20.2-v8-canary should not match v21.x, v19.x, and v20 minor less v20.2', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.2-v8-canary'); - 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('v20.1.1-v8-canary should match v20.1.x patch versions above or eq v20.1.1', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('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('v20.1.1-v8-canary should match patch versions with any canary timestamp', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); - expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-v8-canary202211026bf85d0fb4')).toBeTruthy(); - }); - - it('v20.1.1-v8-canary should not match any other minor versions and patch versions below v20.1.1', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); - 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('v20.0.0-v8-canary20221103f7e2421e91 should match only v20.0.0-v8-canary20221103f7e2421e91', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher( - 'v20.0.0-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(); - }); - - it('v8 canary evaluateVersions 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' - ]; - // @ts-ignore - const version = im.evaluateVersions(versions, 'v20-v8-canary'); - expect(version).toBe('v20.1.1-v8-canary20221103f7e2421e91'); - }); - - it('v8 canary evaluateVersions 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' - ]; - // @ts-ignore - const version = im.evaluateVersions( - versions, - 'v20.0.1-v8-canary20221103f7e2421e92' - ); - expect(version).toBe('v20.0.1-v8-canary20221103f7e2421e92'); - }); - - it('v8 canary queryDistForMatch', async () => { - jest.spyOn(osm, 'platform').mockImplementationOnce(() => 'linux'); - // @ts-ignore - const version = await im.queryDistForMatch( - 'v20-v8-canary', - 'x64', - nodeV8CanaryTestDist - ); - expect(version).toBe('v20.0.0-v8-canary20221103f7e2421e91'); - }); - }); - - describe('setup-node v8 canary e2e tests', () => { + describe('setup-node v8 canary tests', () => { // @actions/http-client let getDistIndexJsonSpy: jest.SpyInstance; let findAllVersionSpy: jest.SpyInstance; @@ -1483,33 +1350,15 @@ describe('setup-node', () => { os.platform = 'linux'; os.arch = 'x64'; - findAllVersionSpy.mockImplementation(() => [ - 'v20.0.0-v8-canary20221103f7e2421e91' - ]); + const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91' + findAllVersionSpy.mockImplementation(() => [ versionExpected]); + + let toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); + findSpy.mockImplementation((version) => toolPath); await main.run(); - expect(dbgSpy.mock.calls[0][0]).toBe('requested v8 canary distribution'); - expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[2][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(logSpy.mock.calls[0][0]).toBe( - 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' - ); - expect(logSpy.mock.calls[1][0]).toBe( - 'Attempt to find existing version in cache...' - ); - expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 1 versions'); - expect(dbgSpy.mock.calls[4][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(logSpy.mock.calls[2][0]).toBe( - 'Found in cache @ v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(cnSpy.mock.calls[1][0].trim()).toBe( - `::add-path::v20.0.0-v8-canary20221103f7e2421e91${path.sep}bin` - ); + expect(cnSpy).toHaveBeenCalledWith(`::add-path::${toolPath}/bin${osm.EOL}`) expect(dlSpy).not.toHaveBeenCalled(); expect(exSpy).not.toHaveBeenCalled(); @@ -1519,6 +1368,36 @@ describe('setup-node', () => { }); describe('helper methods', () => { + it('is not LTS alias', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + // @ts-ignore + const isLtsAlias = im.isLtsAlias(versionSpec); + expect(isLtsAlias).toBeFalsy(); + }); + + it('is not isLatestSyntax', async () => { + const versionSpec = 'v99.0.0-v8-canary'; + // @ts-ignore + 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'; + // @ts-ignore + 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'; + // @ts-ignore + 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 index a38a75361..5e271681c 100644 --- a/__tests__/installer.unit.test.ts +++ b/__tests__/installer.unit.test.ts @@ -1,1274 +1,114 @@ -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 fs from 'fs'; -import cp from 'child_process'; -import osm from 'os'; -import path from 'path'; -import each from 'jest-each'; -import * as main from '../src/main'; -import * as auth from '../src/authutil'; - -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; - let os = {} as any; - - let inSpy: jest.SpyInstance; - let findSpy: jest.SpyInstance; - let findAllVersionsSpy: jest.SpyInstance; - let cnSpy: jest.SpyInstance; - let logSpy: jest.SpyInstance; - let warningSpy: jest.SpyInstance; - let getManifestSpy: jest.SpyInstance; - let getDistSpy: jest.SpyInstance; - let platSpy: jest.SpyInstance; - let archSpy: jest.SpyInstance; - let dlSpy: jest.SpyInstance; - let exSpy: jest.SpyInstance; - let cacheSpy: jest.SpyInstance; - let dbgSpy: jest.SpyInstance; - let whichSpy: jest.SpyInstance; - let existsSpy: jest.SpyInstance; - let readFileSyncSpy: jest.SpyInstance; - let mkdirpSpy: jest.SpyInstance; - let execSpy: jest.SpyInstance; - let authSpy: jest.SpyInstance; - let parseNodeVersionSpy: jest.SpyInstance; - let isCacheActionAvailable: jest.SpyInstance; - let getExecOutputSpy: jest.SpyInstance; - let getJsonSpy: jest.SpyInstance; - - beforeEach(() => { - // @actions/core - console.log('::stop-commands::stoptoken'); // Disable executing of runner commands when running tests in actions - 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 - inputs = {}; - inSpy = jest.spyOn(core, 'getInput'); - inSpy.mockImplementation(name => inputs[name]); - - // node - os = {}; - platSpy = jest.spyOn(osm, 'platform'); - platSpy.mockImplementation(() => os['platform']); - archSpy = jest.spyOn(osm, 'arch'); - archSpy.mockImplementation(() => os['arch']); - execSpy = jest.spyOn(cp, 'execSync'); - - // @actions/tool-cache - findSpy = jest.spyOn(tc, 'find'); - findAllVersionsSpy = jest.spyOn(tc, 'findAllVersions'); - dlSpy = jest.spyOn(tc, 'downloadTool'); - exSpy = jest.spyOn(tc, 'extractTar'); - cacheSpy = jest.spyOn(tc, 'cacheDir'); - getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo'); - // @ts-ignore - getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); - parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile'); - - // http-client - getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson'); - - // io - whichSpy = jest.spyOn(io, 'which'); - existsSpy = jest.spyOn(fs, 'existsSync'); - mkdirpSpy = jest.spyOn(io, 'mkdirP'); - - // @actions/tool-cache - isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable'); - - // disable authentication portion for installer tests - authSpy = jest.spyOn(auth, 'configAuthentication'); - authSpy.mockImplementation(() => { - }); - - // gets - getManifestSpy.mockImplementation( - () => nodeTestManifest - ); - - getDistSpy.mockImplementation(version => { - const initialUrl = im.getNodejsDistUrl(version); - if (initialUrl.endsWith('/rc')) { - return nodeTestDistRc; - } else if (initialUrl.endsWith('/nightly')) { - return nodeTestDistNightly; - } else { - return nodeTestDist; - } - }); - - getJsonSpy.mockImplementation(url => { - let res: any; - if (url.includes('/rc')) { - res = nodeTestDistRc; - } else if (url.includes('/nightly')) { - res = nodeTestDistNightly; - } else { - res = nodeTestDist; - } - - return {result: res}; - }); - - // writes - cnSpy = jest.spyOn(process.stdout, 'write'); - logSpy = jest.spyOn(core, 'info'); - dbgSpy = jest.spyOn(core, 'debug'); - warningSpy = jest.spyOn(core, 'warning'); - cnSpy.mockImplementation(line => { - // uncomment to debug - // process.stderr.write('write:' + line + '\n'); - }); - logSpy.mockImplementation(line => { - // uncomment to debug - // process.stderr.write('log:' + line + '\n'); - }); - dbgSpy.mockImplementation(msg => { - // uncomment to see debug output - // process.stderr.write(msg + '\n'); - }); - warningSpy.mockImplementation(msg => { - // uncomment to debug - // process.stderr.write('log:' + msg + '\n'); - }); - - // @actions/exec - getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); - getExecOutputSpy.mockImplementation(() => 'v16.15.0'); - }); - - afterEach(() => { - jest.resetAllMocks(); - jest.clearAllMocks(); - //jest.restoreAllMocks(); - }); - - afterAll(async () => { - console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions - jest.restoreAllMocks(); - }, 100000); - - //-------------------------------------------------- - // Manifest find tests - //-------------------------------------------------- - it('can mock manifest versions', async () => { - let versions: tc.IToolRelease[] | null = await tc.getManifestFromRepo( - 'actions', - 'node-versions', - 'mocktoken' - ); - expect(versions).toBeDefined(); - expect(versions?.length).toBe(7); - }); - - it('can mock dist versions', async () => { - const versionSpec = '1.2.3'; - let versions: im.INodeVersion[] = await im.getVersionsFromDist(versionSpec); - expect(versions).toBeDefined(); - expect(versions?.length).toBe(23); - }); - - it.each([ - ['12.16.2', 'darwin', '12.16.2', 'Erbium'], - ['12', 'linux', '12.16.2', 'Erbium'], - ['10', 'win32', '10.20.1', 'Dubnium'], - ['*', 'linux', '14.0.0', 'Fermium'] - ])( - 'can find %s from manifest on %s', - async (versionSpec, platform, expectedVersion, expectedLts) => { - os.platform = platform; - os.arch = 'x64'; - let versions: tc.IToolRelease[] | null = await tc.getManifestFromRepo( - 'actions', - 'node-versions', - 'mocktoken' - ); - expect(versions).toBeDefined(); - let match = await tc.findFromManifest(versionSpec, true, versions); - expect(match).toBeDefined(); - expect(match?.version).toBe(expectedVersion); - expect((match as any).lts).toBe(expectedLts); - } - ); - - //-------------------------------------------------- - // Found in cache tests - //-------------------------------------------------- - - it('finds version in cache with stable true', async () => { - inputs['node-version'] = '12'; - inputs.stable = 'true'; - - let toolPath = path.normalize('/cache/node/12.16.1/x64'); - findSpy.mockImplementation(() => toolPath); - await main.run(); - - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - }); - - it('finds version in cache with stable not supplied', async () => { - inputs['node-version'] = '12'; - - inSpy.mockImplementation(name => inputs[name]); - - let toolPath = path.normalize('/cache/node/12.16.1/x64'); - findSpy.mockImplementation(() => toolPath); - await main.run(); - - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - }); - - it('finds version in cache and adds it to the path', async () => { - inputs['node-version'] = '12'; - - inSpy.mockImplementation(name => inputs[name]); - - let toolPath = path.normalize('/cache/node/12.16.1/x64'); - findSpy.mockImplementation(() => toolPath); - await main.run(); - - let expPath = path.join(toolPath, 'bin'); - expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); - }); - - it('handles unhandled find error and reports error', async () => { - let errMsg = 'unhandled error message'; - inputs['node-version'] = '12'; - - findSpy.mockImplementation(() => { - throw new Error(errMsg); - }); - - await main.run(); - - expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL); - }); - - //-------------------------------------------------- - // Manifest tests - //-------------------------------------------------- - - it('downloads a version from a manifest match', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - // a version which is in the manifest - let versionSpec = '12.16.2'; - let resolvedVersion = versionSpec; - - inputs['node-version'] = versionSpec; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - let expectedUrl = - 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz'; - - // ... but not in the local cache - 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(); - - let expPath = path.join(toolPath, 'bin'); - - expect(getExecOutputSpy).toHaveBeenCalledWith( - 'node', - ['--version'], - expect.anything() - ); - expect(getExecOutputSpy).toHaveBeenCalledWith( - 'npm', - ['--version'], - expect.anything() - ); - expect(getExecOutputSpy).toHaveBeenCalledWith( - 'yarn', - ['--version'], - expect.anything() - ); - expect(dlSpy).toHaveBeenCalled(); - expect(exSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith( - `Acquiring ${resolvedVersion} - ${os.arch} from ${expectedUrl}` - ); - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${versionSpec}...` - ); - expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); - }); - - it('falls back to a version from node dist', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - // a version which is not in the manifest but is in node dist - let versionSpec = '11.15.0'; - let resolvedVersion = versionSpec; - - inputs['node-version'] = versionSpec; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - let expectedUrl = - 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz'; - - // ... but not in the local cache - findSpy.mockImplementation(() => ''); - - dlSpy.mockImplementation(async () => '/some/temp/path'); - let toolPath = path.normalize('/cache/node/11.11.0/x64'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - await main.run(); - - let expPath = path.join(toolPath, 'bin'); - - expect(dlSpy).toHaveBeenCalled(); - expect(exSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith( - 'Not found in manifest. Falling back to download directly from Node' - ); - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${versionSpec}...` - ); - expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); - }); - - it('does not find a version that does not exist', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - let versionSpec = '9.99.9'; - inputs['node-version'] = versionSpec; - - findSpy.mockImplementation(() => ''); - await main.run(); - - expect(logSpy).toHaveBeenCalledWith( - 'Not found in manifest. Falling back to download directly from Node' - ); - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${versionSpec}...` - ); - expect(cnSpy).toHaveBeenCalledWith( - `::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}` - ); - }); - - it('reports a failed download', async () => { - let errMsg = 'unhandled download message'; - os.platform = 'linux'; - os.arch = 'x64'; - - // a version which is in the manifest - let versionSpec = '12.16.2'; - let resolvedVersion = versionSpec; - - inputs['node-version'] = versionSpec; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - findSpy.mockImplementation(() => ''); - dlSpy.mockImplementation(() => { - throw new Error(errMsg); - }); - await main.run(); - - expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`); - }); - - it('acquires specified architecture of node', async () => { - for (const {arch, version, osSpec} of [ - {arch: 'x86', version: '12.16.2', osSpec: 'win32'}, - {arch: 'x86', version: '14.0.0', osSpec: 'win32'} - ]) { - os.platform = osSpec; - os.arch = arch; - const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz'; - const platform = { - linux: 'linux', - darwin: 'darwin', - win32: 'win' - }[os.platform]; - - inputs['node-version'] = version; - inputs['architecture'] = arch; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - let expectedUrl = - arch === 'x64' - ? `https://github.com/actions/node-versions/releases/download/${version}/node-${version}-${platform}-${arch}.zip` - : `https://nodejs.org/dist/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`; - - // ... but not in the local cache - findSpy.mockImplementation(() => ''); - - dlSpy.mockImplementation(async () => '/some/temp/path'); - let toolPath = path.normalize(`/cache/node/${version}/${arch}`); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - await main.run(); - expect(dlSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith( - `Acquiring ${version} - ${arch} from ${expectedUrl}` - ); - } - }, 100000); - - describe('check-latest flag', () => { - it('use local version and dont check manifest if check-latest is not specified', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - inputs['node-version'] = '12'; - inputs['check-latest'] = 'false'; - - const toolPath = path.normalize('/cache/node/12.16.1/x64'); - findSpy.mockReturnValue(toolPath); - await main.run(); - - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - expect(logSpy).not.toHaveBeenCalledWith( - 'Attempt to resolve the latest version from manifest...' - ); - expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached'); - expect(dbgSpy).not.toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - }); - - it('check latest version and resolve it from local cache', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - inputs['node-version'] = '12'; - inputs['check-latest'] = 'true'; - - const toolPath = path.normalize('/cache/node/12.16.2/x64'); - findSpy.mockReturnValue(toolPath); - dlSpy.mockImplementation(async () => '/some/temp/path'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - await main.run(); - - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve the latest version from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(logSpy).toHaveBeenCalledWith("Resolved as '12.16.2'"); - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - }); - - it('check latest version and install it from manifest', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - inputs['node-version'] = '12'; - inputs['check-latest'] = 'true'; - - findSpy.mockImplementation(() => ''); - dlSpy.mockImplementation(async () => '/some/temp/path'); - const toolPath = path.normalize('/cache/node/12.16.2/x64'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - const expectedUrl = - 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz'; - - await main.run(); - - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve the latest version from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(logSpy).toHaveBeenCalledWith("Resolved as '12.16.2'"); - expect(logSpy).toHaveBeenCalledWith( - `Acquiring 12.16.2 - ${os.arch} from ${expectedUrl}` - ); - expect(logSpy).toHaveBeenCalledWith('Extracting ...'); - }); - - it('fallback to dist if version if not found in manifest', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - // a version which is not in the manifest but is in node dist - let versionSpec = '11'; - - inputs['node-version'] = versionSpec; - inputs['check-latest'] = 'true'; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - // ... but not in the local cache - findSpy.mockImplementation(() => ''); - - dlSpy.mockImplementation(async () => '/some/temp/path'); - let toolPath = path.normalize('/cache/node/11.11.0/x64'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - await main.run(); - - let expPath = path.join(toolPath, 'bin'); - - expect(dlSpy).toHaveBeenCalled(); - expect(exSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve the latest version from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(logSpy).toHaveBeenCalledWith( - `Failed to resolve version ${versionSpec} from manifest` - ); - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${versionSpec}...` - ); - expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); - }); - - it('fallback to dist if manifest is not available', async () => { - os.platform = 'linux'; - os.arch = 'x64'; - - // a version which is not in the manifest but is in node dist - let versionSpec = '12'; - - inputs['node-version'] = versionSpec; - inputs['check-latest'] = 'true'; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - // ... but not in the local cache - findSpy.mockImplementation(() => ''); - getManifestSpy.mockImplementation(() => { - throw new Error('Unable to download manifest'); - }); - - dlSpy.mockImplementation(async () => '/some/temp/path'); - let toolPath = path.normalize('/cache/node/12.11.0/x64'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - await main.run(); - - let expPath = path.join(toolPath, 'bin'); - - expect(dlSpy).toHaveBeenCalled(); - expect(exSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve the latest version from manifest...' - ); - expect(logSpy).toHaveBeenCalledWith( - 'Unable to resolve version from manifest...' - ); - expect(logSpy).toHaveBeenCalledWith( - `Failed to resolve version ${versionSpec} from manifest` - ); - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${versionSpec}...` - ); - expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); - }); - }); - - describe('node-version-file flag', () => { - it('not used if node-version is provided', async () => { - // Arrange - inputs['node-version'] = '12'; - - // Act - await main.run(); - - // Assert - expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); - }); - - it('not used if node-version-file not provided', async () => { - // Act - await main.run(); - - // Assert - expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); - }); - - it('reads node-version-file if 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) - ); - - // 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('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'; - 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) - ); - - // Act - await main.run(); - - // Assert - expect(existsSpy).toHaveBeenCalled(); - expect(existsSpy).toHaveReturnedWith(false); - expect(parseNodeVersionSpy).not.toHaveBeenCalled(); - expect(cnSpy).toHaveBeenCalledWith( - `::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}` - ); - }); - }); - - describe('cache on GHES', () => { - it('Should throw an error, because cache is not supported', async () => { - inputs['node-version'] = '12'; - inputs['cache'] = 'npm'; - - inSpy.mockImplementation(name => inputs[name]); - - let toolPath = path.normalize('/cache/node/12.16.1/x64'); - findSpy.mockImplementation(() => toolPath); - - // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - process.env['GITHUB_SERVER_URL'] = 'https://www.test.com'; - isCacheActionAvailable.mockImplementation(() => false); - - await main.run(); - - expect(cnSpy).toHaveBeenCalledWith( - `::error::Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.${osm.EOL}` - ); - }); - - it('Should throw an internal error', async () => { - inputs['node-version'] = '12'; - inputs['cache'] = 'npm'; - - inSpy.mockImplementation(name => inputs[name]); - - let toolPath = path.normalize('/cache/node/12.16.1/x64'); - findSpy.mockImplementation(() => toolPath); - - // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - process.env['GITHUB_SERVER_URL'] = ''; - isCacheActionAvailable.mockImplementation(() => false); - - await main.run(); - - expect(warningSpy).toHaveBeenCalledWith( - 'The runner was not able to contact the cache service. Caching will be skipped' - ); - }); - }); - - describe('LTS version', () => { - beforeEach(() => { - os.platform = 'linux'; - os.arch = 'x64'; - inputs.stable = 'true'; - }); - - it.each([ - ['erbium', '12.16.2'], - ['*', '14.0.0'], - ['-1', '12.16.2'] - ])( - 'find latest LTS version and resolve it from local cache (lts/%s)', - async (lts, expectedVersion) => { - // arrange - inputs['node-version'] = `lts/${lts}`; - - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - findSpy.mockReturnValue(toolPath); - - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve LTS alias from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached'); - expect(dbgSpy).toHaveBeenCalledWith( - `LTS alias '${lts}' for Node version 'lts/${lts}'` - ); - expect(dbgSpy).toHaveBeenCalledWith( - `Found LTS release '${expectedVersion}' for Node version 'lts/${lts}'` - ); - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - - it.each([ - [ - 'erbium', - '12.16.2', - 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz' - ], - [ - '*', - '14.0.0', - 'https://github.com/actions/node-versions/releases/download/14.0.0-20200507.99/node-14.0.0-linux-x64.tar.gz' - ], - [ - '-1', - '12.16.2', - 'https://github.com/actions/node-versions/releases/download/12.16.2-20200507.95/node-12.16.2-linux-x64.tar.gz' - ] - ])( - 'find latest LTS version and install it from manifest (lts/%s)', - async (lts, expectedVersion, expectedUrl) => { - // arrange - inputs['node-version'] = `lts/${lts}`; - - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - findSpy.mockImplementation(() => ''); - dlSpy.mockImplementation(async () => '/some/temp/path'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - const expectedMajor = expectedVersion.split('.')[0]; - - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve LTS alias from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached'); - expect(dbgSpy).toHaveBeenCalledWith( - `LTS alias '${lts}' for Node version 'lts/${lts}'` - ); - expect(dbgSpy).toHaveBeenCalledWith( - `Found LTS release '${expectedVersion}' for Node version 'lts/${lts}'` - ); - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${expectedMajor}...` - ); - expect(logSpy).toHaveBeenCalledWith( - `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` - ); - expect(logSpy).toHaveBeenCalledWith('Extracting ...'); - expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - - it('fail with unable to parse LTS alias (lts/)', async () => { - // arrange - inputs['node-version'] = 'lts/'; - - findSpy.mockImplementation(() => ''); - - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve LTS alias from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(cnSpy).toHaveBeenCalledWith( - `::error::Unable to parse LTS alias for Node version 'lts/'${osm.EOL}` - ); - }); - - it('fail to find LTS version (lts/unknown)', async () => { - // arrange - inputs['node-version'] = 'lts/unknown'; - - findSpy.mockImplementation(() => ''); - - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve LTS alias from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(dbgSpy).toHaveBeenCalledWith( - `LTS alias 'unknown' for Node version 'lts/unknown'` - ); - expect(cnSpy).toHaveBeenCalledWith( - `::error::Unable to find LTS release 'unknown' for Node version 'lts/unknown'.${osm.EOL}` - ); - }); - - it('fail if manifest is not available', async () => { - // arrange - inputs['node-version'] = 'lts/erbium'; - - // ... but not in the local cache - findSpy.mockImplementation(() => ''); - getManifestSpy.mockImplementation(() => { - throw new Error('Unable to download manifest'); - }); - - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - 'Attempt to resolve LTS alias from manifest...' - ); - expect(dbgSpy).toHaveBeenCalledWith( - 'Getting manifest from actions/node-versions@main' - ); - expect(cnSpy).toHaveBeenCalledWith( - `::error::Unable to download manifest${osm.EOL}` - ); - }); - }); - - describe('rc versions', () => { - it.each([ - [ - '13.10.1-rc.0', - '13.10.1-rc.0', - 'https://nodejs.org/download/rc/v13.10.1-rc.0/node-v13.10.1-rc.0-linux-x64.tar.gz' - ], - [ - '14.15.5-rc.1', - '14.15.5-rc.1', - 'https://nodejs.org/download/rc/v14.15.5-rc.1/node-v14.15.5-rc.1-linux-x64.tar.gz' - ], - [ - '16.17.0-rc.1', - '16.17.0-rc.1', - 'https://nodejs.org/download/rc/v16.17.0-rc.1/node-v16.17.0-rc.1-linux-x64.tar.gz' - ], - [ - '17.0.0-rc.1', - '17.0.0-rc.1', - 'https://nodejs.org/download/rc/v17.0.0-rc.1/node-v17.0.0-rc.1-linux-x64.tar.gz' - ], - [ - '19.0.0-rc.2', - '19.0.0-rc.2', - 'https://nodejs.org/download/rc/v19.0.0-rc.2/node-v19.0.0-rc.2-linux-x64.tar.gz' - ] - ])( - 'finds the versions in the index.json and installs it', - async (input, expectedVersion, expectedUrl) => { - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - - findSpy.mockImplementation(() => ''); - findAllVersionsSpy.mockImplementation(() => []); - dlSpy.mockImplementation(async () => '/some/temp/path'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - inputs['node-version'] = input; - os['arch'] = 'x64'; - os['platform'] = 'linux'; - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${input}...` - ); - - expect(logSpy).toHaveBeenCalledWith( - `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` - ); - expect(logSpy).toHaveBeenCalledWith('Extracting ...'); - expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - - it.each([ - ['13.10.1-rc.0', '13.10.1-rc.0'], - ['14.15.5-rc.1', '14.15.5-rc.1'], - ['16.17.0-rc.1', '16.17.0-rc.1'], - ['17.0.0-rc.1', '17.0.0-rc.1'] - ])( - 'finds the %s version in the hostedToolcache', - async (input, expectedVersion) => { - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - 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'; - os['platform'] = 'linux'; - - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - - it('throws an error if version is not found', async () => { - const versionSpec = '19.0.0-rc.3'; - - findSpy.mockImplementation(() => ''); - findAllVersionsSpy.mockImplementation(() => []); - dlSpy.mockImplementation(async () => '/some/temp/path'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - - inputs['node-version'] = versionSpec; - os['arch'] = 'x64'; - os['platform'] = 'linux'; - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${versionSpec}...` - ); - expect(cnSpy).toHaveBeenCalledWith( - `::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}` - ); - }); - }); - - describe('nightly versions', () => { - it.each([ - [ - '17.5.0-nightly', - '17.5.0-nightly20220209e43808936a', - 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' - ], - [ - '17-nightly', - '17.5.0-nightly20220209e43808936a', - 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' - ], - [ - '18.0.0-nightly', - '18.0.0-nightly20220419bde889bd4e', - 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' - ], - [ - '18-nightly', - '18.0.0-nightly20220419bde889bd4e', - 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' - ], - [ - '20.0.0-nightly', - '20.0.0-nightly2022101987cdf7d412', - 'https://nodejs.org/download/nightly/v20.0.0-nightly2022101987cdf7d412/node-v20.0.0-nightly2022101987cdf7d412-linux-x64.tar.gz' - ] - ])( - 'finds the versions in the index.json and installs it', - async (input, expectedVersion, expectedUrl) => { - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - - findSpy.mockImplementation(() => ''); - findAllVersionsSpy.mockImplementation(() => []); - dlSpy.mockImplementation(async () => '/some/temp/path'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - inputs['node-version'] = input; - os['arch'] = 'x64'; - os['platform'] = 'linux'; - // act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith( - `Attempting to download ${input}...` - ); - - expect(logSpy).toHaveBeenCalledWith( - `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` - ); - expect(logSpy).toHaveBeenCalledWith('Extracting ...'); - expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - - it.each([ - ['17.5.0-nightly', '17.5.0-nightly20220209e43808936a'], - ['17-nightly', '17.5.0-nightly20220209e43808936a'], - ['20.0.0-nightly', '20.0.0-nightly2022101987cdf7d412'] - ])( - 'finds the %s version in the hostedToolcache', - async (input, expectedVersion) => { - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - findSpy.mockReturnValue(toolPath); - findAllVersionsSpy.mockReturnValue([ - '17.5.0-nightly20220209e43808936a', - '17.5.0-nightly20220209e43808935a', - '20.0.0-nightly2022101987cdf7d412', - '20.0.0-nightly2022101987cdf7d411' - ]); - - inputs['node-version'] = input; - os['arch'] = 'x64'; - os['platform'] = 'linux'; - - // act - await main.run(); - - // assert - expect(findAllVersionsSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - - it.each([ - [ - '17.5.0-nightly', - '17.5.0-nightly20220209e43808936a', - '17.0.0-nightly202110193f11666dc7', - 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' - ], - [ - '17-nightly', - '17.5.0-nightly20220209e43808936a', - '17.0.0-nightly202110193f11666dc7', - 'https://nodejs.org/download/nightly/v17.5.0-nightly20220209e43808936a/node-v17.5.0-nightly20220209e43808936a-linux-x64.tar.gz' - ], - [ - '18.0.0-nightly', - '18.0.0-nightly20220419bde889bd4e', - '18.0.0-nightly202204180699150267', - 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' - ], - [ - '18-nightly', - '18.0.0-nightly20220419bde889bd4e', - '18.0.0-nightly202204180699150267', - 'https://nodejs.org/download/nightly/v18.0.0-nightly20220419bde889bd4e/node-v18.0.0-nightly20220419bde889bd4e-linux-x64.tar.gz' - ], - [ - '20.0.0-nightly', - '20.0.0-nightly2022101987cdf7d412', - '20.0.0-nightly2022101987cdf7d411', - 'https://nodejs.org/download/nightly/v20.0.0-nightly2022101987cdf7d412/node-v20.0.0-nightly2022101987cdf7d412-linux-x64.tar.gz' - ] - ])( - 'get %s version from dist if check-latest is true', - async (input, expectedVersion, foundVersion, expectedUrl) => { - const foundToolPath = path.normalize(`/cache/node/${foundVersion}/x64`); - const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - - inputs['node-version'] = input; - inputs['check-latest'] = 'true'; - os['arch'] = 'x64'; - os['platform'] = 'linux'; - - findSpy.mockReturnValue(foundToolPath); - findAllVersionsSpy.mockReturnValue([ - '17.0.0-nightly202110193f11666dc7', - '18.0.0-nightly202204180699150267', - '20.0.0-nightly2022101987cdf7d411' - ]); - dlSpy.mockImplementation(async () => '/some/temp/path'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - // act - await main.run(); - - // assert - expect(findAllVersionsSpy).toHaveBeenCalled(); - expect(logSpy).toHaveBeenCalledWith( - `Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}` - ); - expect(logSpy).toHaveBeenCalledWith('Extracting ...'); - expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...'); - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${path.join(toolPath, 'bin')}${osm.EOL}` - ); - } - ); - }); - - describe('latest alias syntax', () => { - it.each(['latest', 'current', 'node'])( - 'download the %s version if alias is provided', - async inputVersion => { - // Arrange - inputs['node-version'] = inputVersion; - - os.platform = 'darwin'; - os.arch = 'x64'; - - findSpy.mockImplementation(() => ''); - getManifestSpy.mockImplementation(() => { - throw new Error('Unable to download manifest'); - }); - - // Act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith('Unable to download manifest'); - - expect(logSpy).toHaveBeenCalledWith('getting latest node version...'); - } - ); - }); - - describe('latest alias syntax from cache', () => { - it.each(['latest', 'current', 'node'])( - 'download the %s version if alias is provided', - async inputVersion => { - // Arrange - inputs['node-version'] = inputVersion; - const expectedVersion = nodeTestDist[0]; - - os.platform = 'darwin'; - os.arch = 'x64'; - - const toolPath = path.normalize( - `/cache/node/${expectedVersion.version}/x64` - ); - findSpy.mockReturnValue(toolPath); - - // Act - await main.run(); - - // assert - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); - - expect(logSpy).toHaveBeenCalledWith('getting latest node version...'); - } - ); - }); - - describe('setup-node v8 canary unit tests', () => { +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-canary should throw exception', () => { + expect(() => distributionOf('1.1.1-canary')).toThrow('Canary version must have "-v8-canary suffix"') + }) + it('1.1.1-canary20221103f7e2421e91 should throw exception', () => { + expect(() => distributionOf('1.1.1-canary20221103f7e2421e91')).toThrow('Canary version must have "-v8-canary suffix"') + }) + 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', () => { + 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('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(); + }) - it('v20-v8-canary should match any minor and patch version', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); + it('canaryRangeVersionMatcherFactory should match v20-v8-canary to any minor and patch version', () => { + 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(); @@ -1276,9 +116,9 @@ describe('setup-node', () => { expect(matcher('v20.0.0-v8-canary202211026bf85d0fb4')).toBeTruthy(); }); - it('v20-v8-canary should not match v21.x & v19.x', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20-v8-canary'); + 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(); @@ -1287,57 +127,49 @@ describe('setup-node', () => { expect(matcher('v19.1.-v8-canary20221103f7e2421e91')).toBeFalsy(); }); - it('v20.1-v8-canary should match any v20.1 patch version and minor above or eq v20.1', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1-v8-canary'); + it('canaryRangeVersionMatcherFactory should match v20.1-v8-canary to any v20.1 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('v20.2-v8-canary should not match v21.x, v19.x, and v20 minor less v20.2', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.2-v8-canary'); + it('canaryRangeVersionMatcherFactory should not match canaryRangeVersionMatcherFactory to v21.x, v19.x, and v20 minor less 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('v20.1.1-v8-canary should match v20.1.x patch versions above or eq v20.1.1', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + it('canaryRangeVersionMatcherFactory should not match v20.1.1-v8-canary v20.1.x to 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('v20.1.1-v8-canary should match patch versions with any canary timestamp', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + 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(); }); - it('v20.1.1-v8-canary should not match any other minor versions and patch versions below v20.1.1', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher('v20.1.1-v8-canary'); + 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('v20.0.0-v8-canary20221103f7e2421e91 should match only v20.0.0-v8-canary20221103f7e2421e91', () => { - // @ts-ignore - const matcher = im.evaluateCanaryMatcher( - 'v20.0.0-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(); - }); - - it('v8 canary evaluateVersions without timestamp', () => { + describe('evaluateVersions', () => { + it('evaluateVersions should handle v8-canary version spec without timestamp', () => { const versions = [ 'v20.0.0-v8-canary20221103f7e2421e91', 'v20.0.1-v8-canary20221103f7e2421e91', @@ -1346,12 +178,11 @@ describe('setup-node', () => { 'v21.1.0-v8-canary20221103f7e2421e91', 'v19.1.0-v8-canary20221103f7e2421e91' ]; - // @ts-ignore - const version = im.evaluateVersions(versions, 'v20-v8-canary'); + const version = evaluateVersions(versions, 'v20-v8-canary'); expect(version).toBe('v20.1.1-v8-canary20221103f7e2421e91'); }); - it('v8 canary evaluateVersions with timestamp', () => { + it('evaluateVersions should handle v8-canary version spec with timestamp', () => { const versions = [ 'v20.0.0-v8-canary20221103f7e2421e91', 'v20.0.1-v8-canary20221103f7e2421e91', @@ -1359,193 +190,36 @@ describe('setup-node', () => { 'v20.0.1-v8-canary20221103f7e2421e93', 'v20.0.2-v8-canary20221103f7e2421e91' ]; - // @ts-ignore - const version = im.evaluateVersions( - versions, - 'v20.0.1-v8-canary20221103f7e2421e92' - ); + const version = evaluateVersions(versions, 'v20.0.1-v8-canary20221103f7e2421e92'); expect(version).toBe('v20.0.1-v8-canary20221103f7e2421e92'); }); + }) - it('v8 canary queryDistForMatch', async () => { - jest.spyOn(osm, 'platform').mockImplementationOnce(() => 'linux'); - // @ts-ignore - const version = await im.queryDistForMatch( - 'v20-v8-canary', - 'x64', - nodeV8CanaryTestDist - ); - expect(version).toBe('v20.0.0-v8-canary20221103f7e2421e91'); - }); - }); - - describe('setup-node v8 canary e2e 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('requested v8 canary distribution'); - expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[2][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(logSpy.mock.calls[0][0]).toBe( - 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' - ); - expect(logSpy.mock.calls[1][0]).toBe( - 'Attempt to find existing version in cache...' - ); - expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 0 versions'); - expect(dbgSpy.mock.calls[4][0]).toBe('match not found'); - expect(logSpy.mock.calls[2][0]).toBe( - 'Attempting to download v20.0.0-v8-canary20221103f7e2421e91...' - ); - expect(dbgSpy.mock.calls[5][0]).toBe('No manifest cached'); - expect(dbgSpy.mock.calls[6][0]).toBe( - 'Getting manifest from actions/node-versions@main' - ); - expect(dbgSpy.mock.calls[7][0].slice(0, 6)).toBe('check '); - expect(dbgSpy.mock.calls[13][0].slice(0, 6)).toBe('check '); - expect(logSpy.mock.calls[3][0]).toBe( - 'Not found in manifest. Falling back to download directly from Node' - ); - expect(dbgSpy.mock.calls[14][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[15][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(dbgSpy.mock.calls[16][0]).toBe('requested v8 canary distribution'); - expect(logSpy.mock.calls[4][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'; - - findAllVersionSpy.mockImplementation(() => [ - 'v20.0.0-v8-canary20221103f7e2421e91' - ]); - - await main.run(); - - expect(dbgSpy.mock.calls[0][0]).toBe('requested v8 canary distribution'); - expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[2][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(logSpy.mock.calls[0][0]).toBe( - 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' - ); - expect(logSpy.mock.calls[1][0]).toBe( - 'Attempt to find existing version in cache...' - ); - expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 1 versions'); - expect(dbgSpy.mock.calls[4][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(logSpy.mock.calls[2][0]).toBe( - 'Found in cache @ v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(cnSpy.mock.calls[1][0].trim()).toBe( - `::add-path::v20.0.0-v8-canary20221103f7e2421e91${path.sep}bin` - ); - - expect(dlSpy).not.toHaveBeenCalled(); - expect(exSpy).not.toHaveBeenCalled(); - expect(cacheSpy).not.toHaveBeenCalled(); + 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'); }); - }); -}); - -describe('helper methods', () => { - it('is not LTS alias', async () => { - const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore - const isLtsAlias = im.isLtsAlias(versionSpec); - expect(isLtsAlias).toBeFalsy(); - }); - it('is not isLatestSyntax', async () => { - const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore - 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'; - // @ts-ignore - const url = im.getNodejsDistUrl(versionSpec); - expect(url).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('dist url to be https://nodejs.org/download/v8-canary for full versionSpec', () => { - const versionSpec = 'v20.0.0-v8-canary20221103f7e2421e91'; - // @ts-ignore - const url = im.getNodejsDistUrl(versionSpec); - expect(url).toBe('https://nodejs.org/download/v8-canary'); + 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'); }); - }); - 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); + 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'); }); - }); + }) }); From d38528abe04d99a41a27fc6855cfb5f0fa6010d3 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 05:29:48 +0100 Subject: [PATCH 10/19] prettier --- __tests__/installer.test.ts | 21 +-- __tests__/installer.unit.test.ts | 214 +++++++++++++++++++------------ src/installer.ts | 143 ++++++++++++--------- 3 files changed, 228 insertions(+), 150 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 893f2471a..1f93e1f06 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -89,8 +89,7 @@ describe('setup-node', () => { // disable authentication portion for installer tests authSpy = jest.spyOn(auth, 'configAuthentication'); - authSpy.mockImplementation(() => { - }); + authSpy.mockImplementation(() => {}); // gets getManifestSpy.mockImplementation( @@ -1001,15 +1000,17 @@ describe('setup-node', () => { 'finds the %s version in the hostedToolcache', async (input, expectedVersion) => { const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); - findSpy.mockImplementation((_,version)=>path.normalize(`/cache/node/${version}/x64`)) + 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', - ]) + '3.3.3-rc.3' + ]); inputs['node-version'] = input; os['arch'] = 'x64'; @@ -1350,15 +1351,17 @@ describe('setup-node', () => { os.platform = 'linux'; os.arch = 'x64'; - const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91' - findAllVersionSpy.mockImplementation(() => [ versionExpected]); + const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91'; + findAllVersionSpy.mockImplementation(() => [versionExpected]); let toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); - findSpy.mockImplementation((version) => toolPath); + findSpy.mockImplementation(version => toolPath); await main.run(); - expect(cnSpy).toHaveBeenCalledWith(`::add-path::${toolPath}/bin${osm.EOL}`) + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${toolPath}/bin${osm.EOL}` + ); expect(dlSpy).not.toHaveBeenCalled(); expect(exSpy).not.toHaveBeenCalled(); diff --git a/__tests__/installer.unit.test.ts b/__tests__/installer.unit.test.ts index 5e271681c..622ba375e 100644 --- a/__tests__/installer.unit.test.ts +++ b/__tests__/installer.unit.test.ts @@ -16,98 +16,125 @@ import { 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() - }) + 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') - }) + 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') - }) - }) + 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) - }) + 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) - }) + expect(distributionOf('1.1.1-v8-canary20221103f7e2421e91')).toBe( + Distributions.CANARY + ); + }); it('1.1.1-canary should throw exception', () => { - expect(() => distributionOf('1.1.1-canary')).toThrow('Canary version must have "-v8-canary suffix"') - }) + expect(() => distributionOf('1.1.1-canary')).toThrow( + 'Canary version must have "-v8-canary suffix"' + ); + }); it('1.1.1-canary20221103f7e2421e91 should throw exception', () => { - expect(() => distributionOf('1.1.1-canary20221103f7e2421e91')).toThrow('Canary version must have "-v8-canary suffix"') - }) + expect(() => distributionOf('1.1.1-canary20221103f7e2421e91')).toThrow( + 'Canary version must have "-v8-canary suffix"' + ); + }); it('1.1.1-nightly should be NIGHTLY', () => { - expect(distributionOf('1.1.1-nightly')).toBe(Distributions.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) - }) + 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) - }) - }) + 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) - }) + 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) - }) + 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) - }) + 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) - }) + 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) - }) + 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) - }) + 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) - }) + 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) - }) - }) + expect(versionMatcherFactory('v1.1.1-rc.1').factory).toBe( + semverVersionMatcherFactory + ); + }); + }); describe('Version spec matchers', () => { 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 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)) + 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('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'); + 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(); - }) + }); it('canaryRangeVersionMatcherFactory should match v20-v8-canary to any minor and patch version', () => { - const version = semver.coerce('v20')!.version + 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(); @@ -117,7 +144,7 @@ describe('setup-node unit tests', () => { }); it('canaryRangeVersionMatcherFactory should not match v20-v8-canary to v21.x & v19.x', () => { - const version = semver.coerce('v20')!.version + 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(); @@ -128,7 +155,7 @@ describe('setup-node unit tests', () => { }); it('canaryRangeVersionMatcherFactory should match v20.1-v8-canary to any v20.1 patch version and minor above or eq v20.1', () => { - const version = semver.coerce('v20.1')!.version + 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(); @@ -137,7 +164,7 @@ describe('setup-node unit tests', () => { }); it('canaryRangeVersionMatcherFactory should not match canaryRangeVersionMatcherFactory to v21.x, v19.x, and v20 minor less v20.2', () => { - const version = semver.coerce('v20.2')!.version + 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(); @@ -145,7 +172,7 @@ describe('setup-node unit tests', () => { }); it('canaryRangeVersionMatcherFactory should not match v20.1.1-v8-canary v20.1.x to patch versions above or eq v20.1.1', () => { - const version = semver.coerce('v20.1.1')!.version + 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(); @@ -153,20 +180,20 @@ describe('setup-node unit tests', () => { }); 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 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(); }); 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 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(); }); - }) + }); describe('evaluateVersions', () => { it('evaluateVersions should handle v8-canary version spec without timestamp', () => { @@ -190,36 +217,63 @@ describe('setup-node unit tests', () => { 'v20.0.1-v8-canary20221103f7e2421e93', 'v20.0.2-v8-canary20221103f7e2421e91' ]; - const version = evaluateVersions(versions, 'v20.0.1-v8-canary20221103f7e2421e92'); + 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'); + 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'); + 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'); + 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/src/installer.ts b/src/installer.ts index fcd871eb7..e9f252f98 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -34,103 +34,121 @@ export enum Distributions { DEFAULT, CANARY, NIGHTLY, - RC, + RC } export const distributionOf = (versionSpec: string): Distributions => versionSpec.includes('-v8-canary') ? Distributions.CANARY - // TODO: i'd like to have this check, do you? - : versionSpec.includes('-canary') - ? (() => { - throw Error('Canary version must have "-v8-canary suffix"') + : // TODO: i'd like to have this check, do you? + versionSpec.includes('-canary') + ? (() => { + throw Error('Canary version must have "-v8-canary suffix"'); })() - : versionSpec.includes('nightly') - ? Distributions.NIGHTLY - : semver.prerelease(versionSpec) - ? Distributions.RC - : Distributions.DEFAULT + : versionSpec.includes('nightly') + ? Distributions.NIGHTLY + : semver.prerelease(versionSpec) + ? Distributions.RC + : Distributions.DEFAULT; interface VersionMatcher { - (potential: string): boolean + (potential: string): boolean; // memoize the factory for testing and debug purposes - factory: ((ver: string, suffix: string) => VersionMatcher) | - ((semverRanger: string) => VersionMatcher) | (() => VersionMatcher) + 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, 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 -} + matcher.factory = canaryRangeVersionMatcherFactory; + return matcher; +}; -export const canaryExactVersionMatcherFactory = (version: string, timestamp: string): VersionMatcher => { - const range = `${version}-${timestamp}` +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}-0`)}-0` + matcher.factory = canaryExactVersionMatcherFactory; + return matcher; +}; + +export const nightlyRangeVersionMatcherFactory = ( + version: string +): VersionMatcher => { + const range = `${semver.validRange(`^${version}-0`)}-0`; const matcher = (potential: string): boolean => distributionOf(potential) === Distributions.NIGHTLY && - semver.satisfies(potential.replace('-nightly', '-nightly.'), range, {includePrerelease: true}) - matcher.factory = nightlyRangeVersionMatcherFactory - return matcher -} - -export const nightlyExactVersionMatcherFactory = (version: string, prerelease_tag: string): VersionMatcher => { + semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { + includePrerelease: true + }); + matcher.factory = nightlyRangeVersionMatcherFactory; + return matcher; +}; + +export const nightlyExactVersionMatcherFactory = ( + version: string, + prerelease_tag: string +): VersionMatcher => { const range = `${version}-${prerelease_tag.replace('nightly', 'nightly.')}`; const matcher = (potential: string): boolean => distributionOf(potential) === Distributions.NIGHTLY && - semver.satisfies(potential.replace('-nightly', '-nightly.'), range, {includePrerelease: true}) - matcher.factory = nightlyExactVersionMatcherFactory - return matcher -} + semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { + includePrerelease: true + }); + matcher.factory = nightlyExactVersionMatcherFactory; + return matcher; +}; const alwaysFalseVersionMatcherFactory = (): VersionMatcher => { const matcher = () => false; - matcher.factory = alwaysFalseVersionMatcherFactory - return matcher -} + matcher.factory = alwaysFalseVersionMatcherFactory; + return matcher; +}; -const alwaysFalseVersionMatcher = alwaysFalseVersionMatcherFactory() +const alwaysFalseVersionMatcher = alwaysFalseVersionMatcherFactory(); // [raw, prerelease] -export const splitVersionSpec = (versionSpec: string): string[] => versionSpec.split(/-(.*)/s); +export const splitVersionSpec = (versionSpec: string): string[] => + versionSpec.split(/-(.*)/s); export function versionMatcherFactory(versionSpec: string): VersionMatcher { - const [raw, prerelease] = splitVersionSpec(versionSpec) + 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 + return prerelease === 'v8-canary' // this means versionSpec does not have timestamp ? canaryRangeVersionMatcherFactory(validVersion) - : canaryExactVersionMatcherFactory(validVersion, prerelease) + : canaryExactVersionMatcherFactory(validVersion, prerelease); case Distributions.NIGHTLY: - return (prerelease === 'nightly') // this means versionSpec does not have prerelease tag - ? nightlyRangeVersionMatcherFactory(validVersion) - : nightlyExactVersionMatcherFactory(validVersion, prerelease) + 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) + return semverVersionMatcherFactory(versionSpec); } } else { // TODO: i prefer to have implicit exception for the malformed input - throw Error(`Invalid version input "${versionSpec}"`) + throw Error(`Invalid version input "${versionSpec}"`); // TODO: but it is possible to silently fail // return alwaysFalseVersionMatcher @@ -149,7 +167,7 @@ export async function getNode( let nodeVersions: INodeVersion[] | undefined; const osPlat: string = os.platform(); const osArch: string = translateArchToDistUrl(arch); - const distribution = distributionOf(versionSpec) + const distribution = distributionOf(versionSpec); if (isLtsAlias(versionSpec)) { core.info('Attempt to resolve LTS alias from manifest...'); @@ -199,7 +217,7 @@ export async function getNode( if (distribution === Distributions.DEFAULT) { toolPath = tc.find('node', versionSpec, osArch); } else { - const localVersionPaths = tc.findAllVersions('node', osArch) + const localVersionPaths = tc.findAllVersions('node', osArch); const localVersion = evaluateVersions(localVersionPaths, versionSpec); toolPath = localVersion && tc.find('node', localVersion, osArch); } @@ -363,8 +381,8 @@ export function resolveLtsAliasFromManifest( alias === '*' ? numbered[numbered.length - 1] : n < 0 - ? numbered[numbered.length - 1 + n] - : aliases[alias]; + ? numbered[numbered.length - 1 + n] + : aliases[alias]; if (!release) { throw new Error( @@ -470,11 +488,14 @@ async function resolveVersionFromManifest( // - 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 { +export function evaluateVersions( + versions: string[], + versionSpec: string +): string { core.debug(`evaluating ${versions.length} versions`); - const matcher = versionMatcherFactory(versionSpec) - const version = versions.sort(semver.rcompare).find(matcher) || '' + const matcher = versionMatcherFactory(versionSpec); + const version = versions.sort(semver.rcompare).find(matcher) || ''; if (version) { core.debug(`matched: ${version}`); From a3c8983ccb38e3b5753494c89a440c6d50ce07c9 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 05:30:41 +0100 Subject: [PATCH 11/19] dist --- dist/setup/index.js | 228 +++++++++++++++++++++++--------------------- 1 file changed, 119 insertions(+), 109 deletions(-) diff --git a/dist/setup/index.js b/dist/setup/index.js index 887654434..edaf1ed08 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73216,33 +73216,120 @@ 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)); -const isVersionCanary = (versionSpec) => versionSpec.includes(`-v8-canary`); +var Distributions; +(function (Distributions) { + Distributions[Distributions["DEFAULT"] = 0] = "DEFAULT"; + Distributions[Distributions["CANARY"] = 1] = "CANARY"; + Distributions[Distributions["NIGHTLY"] = 2] = "NIGHTLY"; + Distributions[Distributions["RC"] = 3] = "RC"; +})(Distributions = exports.Distributions || (exports.Distributions = {})); +exports.distributionOf = (versionSpec) => versionSpec.includes('-v8-canary') + ? Distributions.CANARY + : // TODO: i'd like to have this check, do you? + versionSpec.includes('-canary') + ? (() => { + throw Error('Canary version must have "-v8-canary suffix"'); + })() + : versionSpec.includes('nightly') + ? Distributions.NIGHTLY + : semver.prerelease(versionSpec) + ? Distributions.RC + : 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}-0`)}-0`; + const matcher = (potential) => exports.distributionOf(potential) === Distributions.NIGHTLY && + semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { + includePrerelease: true + }); + matcher.factory = exports.nightlyRangeVersionMatcherFactory; + return matcher; +}; +exports.nightlyExactVersionMatcherFactory = (version, prerelease_tag) => { + const range = `${version}-${prerelease_tag.replace('nightly', 'nightly.')}`; + const matcher = (potential) => exports.distributionOf(potential) === Distributions.NIGHTLY && + semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { + 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); - let isCanary = isVersionCanary(versionSpec); + 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 manifest = yield getManifest(auth); versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - // evaluate exact versionSpec from input - if (isLatestSyntax(versionSpec) || isCanary) { + // TODO: 121-127 and 131-132 seems to be the same. Why do we need them? + if (isLatestSyntax(versionSpec) || distribution == Distributions.CANARY) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); - core.info(`getting ${isCanary ? 'v8-canary' : 'latest'} node version ${versionSpec}...`); + core.info(`getting ${distribution == Distributions.CANARY ? 'v8-canary' : 'latest'} node version ${versionSpec}...`); } - if (isNightly && checkLatest) { + if (distribution === Distributions.NIGHTLY && checkLatest) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); } - if (checkLatest && !isNightly) { + if (checkLatest && distribution !== Distributions.NIGHTLY) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = yield resolveVersionFromManifest(versionSpec, stable, auth, osArch, manifest); if (resolvedVersion) { @@ -73256,16 +73343,13 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc // check cache core.info('Attempt to find existing version in cache...'); let toolPath; - if (isNightly) { - const nightlyVersion = findNightlyVersionInHostedToolcache(versionSpec, osArch); - toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); - } - else if (isCanary) { - const localVersions = tc.findAllVersions('node', osArch); - toolPath = evaluateVersions(localVersions, versionSpec); + 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) { @@ -73362,10 +73446,6 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc }); } exports.getNode = getNode; -function findNightlyVersionInHostedToolcache(versionsSpec, osArch) { - const foundAllVersions = tc.findAllVersions('node', osArch); - return evaluateVersions(foundAllVersions, versionsSpec); -} function isLtsAlias(versionSpec) { return versionSpec.startsWith('lts/'); } @@ -73456,57 +73536,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); - const matcher = isVersionCanary(versionSpec) - ? evaluateCanaryMatcher(versionSpec) - : potential => semver.satisfies(potential, versionSpec); - for (let i = versions.length - 1; i >= 0; i--) { - const potential = versions[i]; - const satisfied = matcher(potential); - if (satisfied) { - version = potential; - break; - } - } + const matcher = versionMatcherFactory(versionSpec); + const version = versions.sort(semver.rcompare).find(matcher) || ''; if (version) { core.debug(`matched: ${version}`); } @@ -73517,20 +73554,18 @@ function evaluateVersions(versions, versionSpec) { } exports.evaluateVersions = evaluateVersions; function getNodejsDistUrl(version) { - const prerelease = semver.prerelease(version); - if (version.includes('nightly')) { - core.debug('requested nightly distribution'); - return 'https://nodejs.org/download/nightly'; - } - else if (prerelease) { - return 'https://nodejs.org/download/rc'; - } - else if (isVersionCanary(version)) { - core.debug('requested v8 canary distribution'); - return 'https://nodejs.org/download/v8-canary'; - } - else { - return 'https://nodejs.org/dist'; + switch (exports.distributionOf(version)) { + case Distributions.CANARY: + core.debug('requested v8 canary distribution'); + return 'https://nodejs.org/download/v8-canary'; + case Distributions.NIGHTLY: + core.debug('requested nightly distribution'); + return 'https://nodejs.org/download/nightly'; + case Distributions.RC: + core.debug('requested release candidates distribution'); + return 'https://nodejs.org/download/rc'; + case Distributions.DEFAULT: + return 'https://nodejs.org/dist'; } } exports.getNodejsDistUrl = getNodejsDistUrl; @@ -73678,31 +73713,6 @@ function isLatestSyntax(versionSpec) { return ['current', 'latest', 'node'].includes(versionSpec); } exports.isLatestSyntax = isLatestSyntax; -function evaluateCanaryMatcher(versionSpec) { - var _a; - const [raw, prerelease] = versionSpec.split(/-(.*)/s); - const isValidVersion = semver.valid(raw); - const rawVersion = isValidVersion ? raw : (_a = semver.coerce(raw)) === null || _a === void 0 ? void 0 : _a.version; - if (rawVersion) { - if (prerelease === 'v8-canary') { - // it means versionSpec does not have timestamp - const range = semver.validRange(`^${rawVersion}`); - return (potential) => semver.satisfies( - // TODO: check latest? - potential.replace('-v8-canary', '+v8-canary.'), range); - } - else { - // see https://github.com/actions/setup-node/blob/00e1b6691b40cce14b5078cb411dd1ec7dab07f7/__tests__/verify-node.sh#L10 - // there must be exact match - const range = `${rawVersion}-${prerelease}`; - return (potential) => semver.satisfies( - // TODO: check latest? - potential, range); - } - } - return () => false; -} -exports.evaluateCanaryMatcher = evaluateCanaryMatcher; /***/ }), From 6e2b26d0977fdb8673930619737c6f4cbf0d535a Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 08:42:52 +0100 Subject: [PATCH 12/19] Fix windows test --- __tests__/installer.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 1f93e1f06..0041ab849 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -1354,13 +1354,13 @@ describe('setup-node', () => { const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91'; findAllVersionSpy.mockImplementation(() => [versionExpected]); - let toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); + const toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); findSpy.mockImplementation(version => toolPath); await main.run(); expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${toolPath}/bin${osm.EOL}` + `::add-path::${toolPath}${path.sep}bin${osm.EOL}` ); expect(dlSpy).not.toHaveBeenCalled(); From 505679238a1a9cc4655c8a4490e1436cfe386fad Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 12:15:42 +0100 Subject: [PATCH 13/19] Fix nightly unit tests --- __tests__/installer.unit.test.ts | 235 +++++++++++++++++++++---------- src/installer.ts | 28 ++-- 2 files changed, 184 insertions(+), 79 deletions(-) diff --git a/__tests__/installer.unit.test.ts b/__tests__/installer.unit.test.ts index 622ba375e..26c674d48 100644 --- a/__tests__/installer.unit.test.ts +++ b/__tests__/installer.unit.test.ts @@ -108,90 +108,183 @@ describe('setup-node unit tests', () => { }); describe('Version spec matchers', () => { - 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)); + 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)); + 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 - }); + // TODO: add for discovered issues if any + }); - 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(); - }); + 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) + ); - it('canaryRangeVersionMatcherFactory should match v20-v8-canary to any minor and patch version', () => { - 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(); + 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) + ); + }); }); - 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(); + 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(); + }); }); - it('canaryRangeVersionMatcherFactory should match v20.1-v8-canary to any v20.1 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(); - }); + 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 canaryRangeVersionMatcherFactory to v21.x, v19.x, and v20 minor less 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 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 not match v20.1.1-v8-canary v20.1.x to 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 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 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(); + 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(); + }); }); - 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(); + 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(); + }); }); }); diff --git a/src/installer.ts b/src/installer.ts index e9f252f98..e3c4df473 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -92,26 +92,38 @@ export const canaryExactVersionMatcherFactory = ( export const nightlyRangeVersionMatcherFactory = ( version: string ): VersionMatcher => { - const range = `${semver.validRange(`^${version}-0`)}-0`; + 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 && - semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { + // 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, - prerelease_tag: string + timestamp: string ): VersionMatcher => { - const range = `${version}-${prerelease_tag.replace('nightly', 'nightly.')}`; + const range = `${version}-${timestamp.replace('nightly', 'nightly.')}`; const matcher = (potential: string): boolean => distributionOf(potential) === Distributions.NIGHTLY && - semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { + semver.satisfies( + potential.replace('-nightly', '-nightly.'), + range /*, { + // TODO: what is for? includePrerelease: true - }); + }*/ + ); matcher.factory = nightlyExactVersionMatcherFactory; return matcher; }; @@ -178,7 +190,7 @@ export async function getNode( versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - // TODO: 121-127 and 131-132 seems to be the same. Why do we need them? + // TODO: 183-189 and 193-194 seems to be the same. Why do we need them? if (isLatestSyntax(versionSpec) || distribution == Distributions.CANARY) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); From d2ef4bc907656a1c4b2e718604b21a5ae875b089 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 12:21:17 +0100 Subject: [PATCH 14/19] build dist --- dist/setup/index.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/dist/setup/index.js b/dist/setup/index.js index edaf1ed08..848bec334 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73253,20 +73253,26 @@ exports.canaryExactVersionMatcherFactory = (version, timestamp) => { return matcher; }; exports.nightlyRangeVersionMatcherFactory = (version) => { - const range = `${semver.validRange(`^${version}-0`)}-0`; + 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 && - semver.satisfies(potential.replace('-nightly', '-nightly.'), range, { - includePrerelease: true - }); + // 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, prerelease_tag) => { - const range = `${version}-${prerelease_tag.replace('nightly', 'nightly.')}`; +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, { - includePrerelease: true - }); + semver.satisfies(potential.replace('-nightly', '-nightly.'), range /*, { + // TODO: what is for? + includePrerelease: true + }*/); matcher.factory = exports.nightlyExactVersionMatcherFactory; return matcher; }; @@ -73319,7 +73325,7 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc manifest = yield getManifest(auth); versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - // TODO: 121-127 and 131-132 seems to be the same. Why do we need them? + // TODO: 183-189 and 193-194 seems to be the same. Why do we need them? if (isLatestSyntax(versionSpec) || distribution == Distributions.CANARY) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); From 4a961ae73713a08c1ceaf29e3a859be30c992613 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 13:29:42 +0100 Subject: [PATCH 15/19] Fix docs --- docs/advanced-usage.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 771d0b829..79998680d 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -124,7 +124,6 @@ jobs: - run: npm test ``` ### Install v8 canary build for major node version ->>>>>>> d703091 (Change docs) ```yaml jobs: From e542443cfac067650cd454b4930f083030bb3278 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 13:30:14 +0100 Subject: [PATCH 16/19] Fix versions.yml --- .github/workflows/versions.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 481ca1e43..6dd6f646a 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -58,18 +58,18 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node-version: ['20-v8-canary', '20.0-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 + 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 }} From 63e6b4af5a2ebbad34940b7e196ce06da96ae3cb Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 16:34:12 +0100 Subject: [PATCH 17/19] change numbered list to bullet --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 61edf1e26..f075d0cfa 100644 --- a/README.md +++ b/README.md @@ -128,17 +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) -1. [Using a node version file](docs/advanced-usage.md#node-version-file) -1. [Using different architectures](docs/advanced-usage.md#architecture) -1. [Using v8 canary versions](docs/advanced-usage.md#v8-canary-versions) -1. [Using nigthly versions](docs/advanced-usage.md#nightly-versions) -1. [Using rc versions](docs/advanced-usage.md#rc-versions) -1. [Caching packages data](docs/advanced-usage.md#caching-packages-data) -1. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures) -1. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm) -1. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn) -1. [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 From 658b51b920c3c16aa0a14fe22636755c8b9ad38f Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Thu, 24 Nov 2022 18:52:31 +0100 Subject: [PATCH 18/19] requested changes --- __tests__/installer.test.ts | 39 ++++++---------------- __tests__/installer.unit.test.ts | 10 ------ dist/setup/index.js | 51 ++++++++++++++-------------- src/installer.ts | 57 ++++++++++++++------------------ 4 files changed, 60 insertions(+), 97 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 0041ab849..3ec837c22 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -72,7 +72,6 @@ describe('setup-node', () => { exSpy = jest.spyOn(tc, 'extractTar'); cacheSpy = jest.spyOn(tc, 'cacheDir'); getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo'); - // @ts-ignore getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile'); @@ -1302,37 +1301,25 @@ describe('setup-node', () => { await main.run(); - expect(dbgSpy.mock.calls[0][0]).toBe('requested v8 canary distribution'); - expect(dbgSpy.mock.calls[1][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[2][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); + 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( - 'getting v8-canary node version v20.0.0-v8-canary20221103f7e2421e91...' - ); - expect(logSpy.mock.calls[1][0]).toBe( - 'Attempt to find existing version in cache...' - ); - expect(dbgSpy.mock.calls[3][0]).toBe('evaluating 0 versions'); - expect(dbgSpy.mock.calls[4][0]).toBe('match not found'); - expect(logSpy.mock.calls[2][0]).toBe( - 'Attempting to download v20.0.0-v8-canary20221103f7e2421e91...' + `Attempting to download ${versionSpec}...` ); - expect(dbgSpy.mock.calls[5][0]).toBe('No manifest cached'); - expect(dbgSpy.mock.calls[6][0]).toBe( + 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[7][0].slice(0, 6)).toBe('check '); - expect(dbgSpy.mock.calls[13][0].slice(0, 6)).toBe('check '); - expect(logSpy.mock.calls[3][0]).toBe( + 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[14][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[15][0]).toBe( + expect(dbgSpy.mock.calls[12][0]).toBe('evaluating 17 versions'); + expect(dbgSpy.mock.calls[13][0]).toBe( 'matched: v20.0.0-v8-canary20221103f7e2421e91' ); - expect(dbgSpy.mock.calls[16][0]).toBe('requested v8 canary distribution'); - expect(logSpy.mock.calls[4][0]).toBe( + 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' ); @@ -1373,14 +1360,12 @@ describe('setup-node', () => { describe('helper methods', () => { it('is not LTS alias', async () => { const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore const isLtsAlias = im.isLtsAlias(versionSpec); expect(isLtsAlias).toBeFalsy(); }); it('is not isLatestSyntax', async () => { const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore const isLatestSyntax = im.isLatestSyntax(versionSpec); expect(isLatestSyntax).toBeFalsy(); }); @@ -1388,14 +1373,12 @@ describe('helper methods', () => { describe('getNodejsDistUrl', () => { it('dist url to be https://nodejs.org/download/v8-canary for input versionSpec', () => { const versionSpec = 'v99.0.0-v8-canary'; - // @ts-ignore 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'; - // @ts-ignore const url = im.getNodejsDistUrl(versionSpec); expect(url).toBe('https://nodejs.org/download/v8-canary'); }); diff --git a/__tests__/installer.unit.test.ts b/__tests__/installer.unit.test.ts index 26c674d48..11d4b1bb0 100644 --- a/__tests__/installer.unit.test.ts +++ b/__tests__/installer.unit.test.ts @@ -41,16 +41,6 @@ describe('setup-node unit tests', () => { Distributions.CANARY ); }); - it('1.1.1-canary should throw exception', () => { - expect(() => distributionOf('1.1.1-canary')).toThrow( - 'Canary version must have "-v8-canary suffix"' - ); - }); - it('1.1.1-canary20221103f7e2421e91 should throw exception', () => { - expect(() => distributionOf('1.1.1-canary20221103f7e2421e91')).toThrow( - 'Canary version must have "-v8-canary suffix"' - ); - }); it('1.1.1-nightly should be NIGHTLY', () => { expect(distributionOf('1.1.1-nightly')).toBe(Distributions.NIGHTLY); }); diff --git a/dist/setup/index.js b/dist/setup/index.js index 848bec334..2d4f882fb 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73218,23 +73218,20 @@ const semver = __importStar(__nccwpck_require__(5911)); const fs_1 = __importDefault(__nccwpck_require__(7147)); var Distributions; (function (Distributions) { - Distributions[Distributions["DEFAULT"] = 0] = "DEFAULT"; - Distributions[Distributions["CANARY"] = 1] = "CANARY"; - Distributions[Distributions["NIGHTLY"] = 2] = "NIGHTLY"; - Distributions[Distributions["RC"] = 3] = "RC"; + Distributions["DEFAULT"] = "default"; + Distributions["CANARY"] = "v8-canary"; + Distributions["NIGHTLY"] = "nightly"; + Distributions["RC"] = "rc"; })(Distributions = exports.Distributions || (exports.Distributions = {})); -exports.distributionOf = (versionSpec) => versionSpec.includes('-v8-canary') - ? Distributions.CANARY - : // TODO: i'd like to have this check, do you? - versionSpec.includes('-canary') - ? (() => { - throw Error('Canary version must have "-v8-canary suffix"'); - })() - : versionSpec.includes('nightly') - ? Distributions.NIGHTLY - : semver.prerelease(versionSpec) - ? Distributions.RC - : Distributions.DEFAULT; +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; @@ -73325,17 +73322,20 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc manifest = yield getManifest(auth); versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - // TODO: 183-189 and 193-194 seems to be the same. Why do we need them? - if (isLatestSyntax(versionSpec) || distribution == Distributions.CANARY) { + if (isLatestSyntax(versionSpec)) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); - core.info(`getting ${distribution == Distributions.CANARY ? 'v8-canary' : 'latest'} node version ${versionSpec}...`); + core.info(`getting latest node version ${versionSpec}...`); } - if (distribution === Distributions.NIGHTLY && checkLatest) { + if ((distribution === Distributions.NIGHTLY || + distribution === Distributions.CANARY) && + checkLatest) { nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); } - if (checkLatest && distribution !== Distributions.NIGHTLY) { + 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) { @@ -73347,7 +73347,6 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os_1.default.arc } } // check cache - core.info('Attempt to find existing version in cache...'); let toolPath; if (distribution === Distributions.DEFAULT) { toolPath = tc.find('node', versionSpec, osArch); @@ -73562,13 +73561,10 @@ exports.evaluateVersions = evaluateVersions; function getNodejsDistUrl(version) { switch (exports.distributionOf(version)) { case Distributions.CANARY: - core.debug('requested v8 canary distribution'); return 'https://nodejs.org/download/v8-canary'; case Distributions.NIGHTLY: - core.debug('requested nightly distribution'); return 'https://nodejs.org/download/nightly'; case Distributions.RC: - core.debug('requested release candidates distribution'); return 'https://nodejs.org/download/rc'; case Distributions.DEFAULT: return 'https://nodejs.org/dist'; @@ -73602,7 +73598,7 @@ function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions core.info(`getting latest node version...`); return nodeVersions[0].version; } - let versions = []; + const versions = []; nodeVersions.forEach((nodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { @@ -73610,7 +73606,8 @@ function queryDistForMatch(versionSpec, arch = os_1.default.arch(), nodeVersions } }); // get the latest version that matches the version spec - return evaluateVersions(versions, versionSpec); + const version = evaluateVersions(versions, versionSpec); + return version; }); } exports.queryDistForMatch = queryDistForMatch; diff --git a/src/installer.ts b/src/installer.ts index e3c4df473..c3dc349ff 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -31,25 +31,18 @@ interface INodeRelease extends tc.IToolRelease { } export enum Distributions { - DEFAULT, - CANARY, - NIGHTLY, - RC + DEFAULT = 'default', + CANARY = 'v8-canary', + NIGHTLY = 'nightly', + RC = 'rc' } -export const distributionOf = (versionSpec: string): Distributions => - versionSpec.includes('-v8-canary') - ? Distributions.CANARY - : // TODO: i'd like to have this check, do you? - versionSpec.includes('-canary') - ? (() => { - throw Error('Canary version must have "-v8-canary suffix"'); - })() - : versionSpec.includes('nightly') - ? Distributions.NIGHTLY - : semver.prerelease(versionSpec) - ? Distributions.RC - : Distributions.DEFAULT; +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; @@ -190,23 +183,26 @@ export async function getNode( versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } - // TODO: 183-189 and 193-194 seems to be the same. Why do we need them? - if (isLatestSyntax(versionSpec) || distribution == Distributions.CANARY) { + if (isLatestSyntax(versionSpec)) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); - core.info( - `getting ${ - distribution == Distributions.CANARY ? 'v8-canary' : 'latest' - } node version ${versionSpec}...` - ); + core.info(`getting latest node version ${versionSpec}...`); } - if (distribution === Distributions.NIGHTLY && checkLatest) { + if ( + (distribution === Distributions.NIGHTLY || + distribution === Distributions.CANARY) && + checkLatest + ) { nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); } - if (checkLatest && distribution !== Distributions.NIGHTLY) { + if ( + checkLatest && + distribution !== Distributions.NIGHTLY && + distribution !== Distributions.CANARY + ) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = await resolveVersionFromManifest( versionSpec, @@ -224,7 +220,6 @@ export async function getNode( } // check cache - core.info('Attempt to find existing version in cache...'); let toolPath: string; if (distribution === Distributions.DEFAULT) { toolPath = tc.find('node', versionSpec, osArch); @@ -521,13 +516,10 @@ export function evaluateVersions( export function getNodejsDistUrl(version: string) { switch (distributionOf(version)) { case Distributions.CANARY: - core.debug('requested v8 canary distribution'); return 'https://nodejs.org/download/v8-canary'; case Distributions.NIGHTLY: - core.debug('requested nightly distribution'); return 'https://nodejs.org/download/nightly'; case Distributions.RC: - core.debug('requested release candidates distribution'); return 'https://nodejs.org/download/rc'; case Distributions.DEFAULT: return 'https://nodejs.org/dist'; @@ -568,7 +560,7 @@ export async function queryDistForMatch( return nodeVersions[0].version; } - let versions: string[] = []; + const versions: string[] = []; nodeVersions.forEach((nodeVersion: INodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { @@ -577,7 +569,8 @@ export async function queryDistForMatch( }); // get the latest version that matches the version spec - return evaluateVersions(versions, versionSpec); + const version = evaluateVersions(versions, versionSpec); + return version; } export async function getVersionsFromDist( From d8923d1f9d5d11f347df63c2770cbc06ff09d802 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Fri, 25 Nov 2022 09:44:05 +0100 Subject: [PATCH 19/19] remove v20.0.0 from workflow check --- .github/workflows/versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 6dd6f646a..1482ae289 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -57,7 +57,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: ['20-v8-canary', '20.0-v8-canary', '20.0.0-v8-canary','20.0.0-v8-canary20221103f7e2421e91'] + node-version: ['20-v8-canary', '20.0.0-v8-canary','20.0.0-v8-canary20221103f7e2421e91'] steps: - uses: actions/checkout@v3 - name: Setup Node