diff --git a/__tests__/canary-installer.test.ts b/__tests__/canary-installer.test.ts new file mode 100644 index 00000000..9ce645c6 --- /dev/null +++ b/__tests__/canary-installer.test.ts @@ -0,0 +1,531 @@ +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 cache from '@actions/cache'; +import fs from 'fs'; +import cp from 'child_process'; +import osm from 'os'; +import path from 'path'; +import * as main from '../src/main'; +import * as auth from '../src/authutil'; +import {INodeVersion} from '../src/distibutions/base-models'; + +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'); + + // 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 + ); + + getJsonSpy.mockImplementation(url => { + let res: any; + if (url.includes('/rc')) { + res = nodeTestDistRc; + } else if (url.includes('/nightly')) { + res = nodeTestDistNightly; + } else if (url.includes('/v8-canary')) { + res = nodeV8CanaryTestDist; + } 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); + + //-------------------------------------------------- + // Found in cache tests + //-------------------------------------------------- + + it('finds version in cache with stable true', async () => { + inputs['node-version'] = '20-v8-canary'; + os['arch'] = 'x64'; + inputs.stable = 'true'; + + let toolPath = path.normalize( + '/cache/node/20.0.0-v8-canary20221103f7e2421e91/x64' + ); + findSpy.mockImplementation(() => toolPath); + findAllVersionsSpy.mockImplementation(() => [ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + '19.0.0-v8-canary202210172ec229fc56', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + await main.run(); + + expect(findSpy).toHaveBeenCalledWith( + 'node', + '20.0.0-v8-canary20221103f7e2421e91', + 'x64' + ); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + }); + + it('finds version in cache and adds it to the path', async () => { + inputs['node-version'] = '20-v8-canary'; + os['arch'] = 'x64'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize( + '/cache/node/20.0.0-v8-canary20221103f7e2421e91/x64' + ); + findSpy.mockImplementation(() => toolPath); + findAllVersionsSpy.mockImplementation(() => [ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + '19.0.0-v8-canary202210172ec229fc56', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + 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 () => { + os.platform = 'linux'; + let errMsg = 'unhandled error message'; + inputs['node-version'] = '20.0.0-v8-canary20221103f7e2421e91'; + + findSpy.mockImplementation(() => { + throw new Error(errMsg); + }); + findAllVersionsSpy.mockImplementation(() => [ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + '19.0.0-v8-canary202210172ec229fc56', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL); + }); + + //-------------------------------------------------- + // Manifest tests + //-------------------------------------------------- + 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'; + + inputs['node-version'] = versionSpec; + 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( + '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 = '23.0.0-v8-canary20221103f7e2421e91'; + inputs['node-version'] = versionSpec; + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => [ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + '19.0.0-v8-canary202210172ec229fc56', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + await main.run(); + + 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 = '19.0.0-v8-canary'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => [ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + 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: '20.0.0-v8-canary20221022e83bcb6c41', + osSpec: 'win32' + }, + { + arch: 'x86', + version: '20.0.0-v8-canary20221103f7e2421e91', + 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 = `https://nodejs.org/download/v8-canary/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.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('nightly versions', () => { + it.each([ + [ + '20.0.0-v8-canary', + '20.0.0-v8-canary20221103f7e2421e91', + 'https://nodejs.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz' + ], + [ + '20-v8-canary', + '20.0.0-v8-canary20221103f7e2421e91', + 'https://nodejs.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz' + ], + [ + '19.0.0-v8-canary', + '19.0.0-v8-canary202210187d6960f23f', + 'https://nodejs.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz' + ], + [ + '19-v8-canary', + '19.0.0-v8-canary202210187d6960f23f', + 'https://nodejs.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz' + ], + [ + '19.0.0-v8-canary202210187d6960f23f', + '19.0.0-v8-canary202210187d6960f23f', + 'https://nodejs.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-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( + `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([ + [ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221103f7e2421e91' + ], + ['20.0.0-v8-canary', '20.0.0-v8-canary20221103f7e2421e91'], + ['20-v8-canary', '20.0.0-v8-canary20221103f7e2421e91'] + ])( + 'finds the %s version in the hostedToolcache', + async (input, expectedVersion) => { + const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`); + findSpy.mockReturnValue(toolPath); + findAllVersionsSpy.mockReturnValue([ + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + '19.0.0-v8-canary202210172ec229fc56', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + + 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([ + [ + '20.0.0-v8-canary', + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + 'https://nodejs.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz' + ], + [ + '20-v8-canary', + '20.0.0-v8-canary20221103f7e2421e91', + '20.0.0-v8-canary20221030fefe1c0879', + 'https://nodejs.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz' + ], + [ + '19.0.0-v8-canary', + '19.0.0-v8-canary202210187d6960f23f', + '19.0.0-v8-canary202210172ec229fc56', + 'https://nodejs.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz' + ], + [ + '19-v8-canary', + '19.0.0-v8-canary202210187d6960f23f', + '19.0.0-v8-canary202210172ec229fc56', + 'https://nodejs.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-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([ + '20.0.0-v8-canary20221030fefe1c0879', + '19.0.0-v8-canary202210172ec229fc56', + '20.0.0-v8-canary2022102310ff1e5a8d' + ]); + 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('setup-node v8 canary tests', () => { + it('v8 canary setup node flow with cached', async () => { + let versionSpec = 'v20-v8-canary'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + os.platform = 'linux'; + os.arch = 'x64'; + + const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91'; + findAllVersionsSpy.mockImplementation(() => [versionExpected]); + + const toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); + findSpy.mockImplementation(version => toolPath); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + `::add-path::${toolPath}${path.sep}bin${osm.EOL}` + ); + + expect(dlSpy).not.toHaveBeenCalled(); + expect(exSpy).not.toHaveBeenCalled(); + expect(cacheSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/__tests__/installer.unit.test.ts b/__tests__/installer.unit.test.ts deleted file mode 100644 index 11d4b1bb..00000000 --- a/__tests__/installer.unit.test.ts +++ /dev/null @@ -1,362 +0,0 @@ -import semver from 'semver'; -import { - canaryExactVersionMatcherFactory, - canaryRangeVersionMatcherFactory, - distributionOf, - Distributions, - evaluateVersions, - getNodejsDistUrl, - nightlyExactVersionMatcherFactory, - nightlyRangeVersionMatcherFactory, - semverVersionMatcherFactory, - splitVersionSpec, - versionMatcherFactory -} from '../src/installer'; - -describe('setup-node unit tests', () => { - describe('splitVersionSpec', () => { - it('splitVersionSpec correctly splits version spec without dashes', () => { - const [raw, prerelease] = splitVersionSpec('1.1.1'); - expect(raw).toBe('1.1.1'); - expect(prerelease).toBeUndefined(); - }); - it('splitVersionSpec correctly splits version spec with one dash', () => { - const [raw, prerelease] = splitVersionSpec('1.1.1-nightly12345678'); - expect(raw).toBe('1.1.1'); - expect(prerelease).toBe('nightly12345678'); - }); - it('splitVersionSpec correctly splits version spec with 2 dashes', () => { - const [raw, prerelease] = splitVersionSpec('1.1.1-v8-canary12345678'); - expect(raw).toBe('1.1.1'); - expect(prerelease).toBe('v8-canary12345678'); - }); - }); - - describe('distributionOf', () => { - it('1.1.1-v8-canary should be CANARY', () => { - expect(distributionOf('1.1.1-v8-canary')).toBe(Distributions.CANARY); - }); - it('1.1.1-v8-canary20221103f7e2421e91 should be CANARY', () => { - expect(distributionOf('1.1.1-v8-canary20221103f7e2421e91')).toBe( - Distributions.CANARY - ); - }); - it('1.1.1-nightly should be NIGHTLY', () => { - expect(distributionOf('1.1.1-nightly')).toBe(Distributions.NIGHTLY); - }); - it('1.1.1-nightly20221103f7e2421e91 should be NIGHTLY', () => { - expect(distributionOf('1.1.1-nightly20221103f7e2421e91')).toBe( - Distributions.NIGHTLY - ); - }); - it('1.1.1-rc.0 should be RC', () => { - expect(distributionOf('1.1.1-rc.0')).toBe(Distributions.RC); - }); - }); - - describe('versionMatcherFactory', () => { - it('1.1.1 should be handled by semverVersionMatcherFactory', () => { - expect(versionMatcherFactory('1.1.1').factory).toBe( - semverVersionMatcherFactory - ); - }); - it('v1.1.1 should be handled by semverVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1').factory).toBe( - semverVersionMatcherFactory - ); - }); - it('v1.1.1-v8-canary should be handled by canaryRangeVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1-v8-canary').factory).toBe( - canaryRangeVersionMatcherFactory - ); - }); - it('v1.1.1-v8-canary123 should be handled by canaryExactVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1-v8-canary123').factory).toBe( - canaryExactVersionMatcherFactory - ); - }); - it('v1.1.1-nightly should be handled by nightlyRangeVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1-nightly').factory).toBe( - nightlyRangeVersionMatcherFactory - ); - }); - it('v1.1.1-nigthly123 should be handled by nightlyExactVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1-nightly123').factory).toBe( - nightlyExactVersionMatcherFactory - ); - }); - it('v1.1.1-rc should be handled by semverVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1-rc').factory).toBe( - semverVersionMatcherFactory - ); - }); - it('v1.1.1-rc.1 should be handled by semverVersionMatcherFactory', () => { - expect(versionMatcherFactory('v1.1.1-rc.1').factory).toBe( - semverVersionMatcherFactory - ); - }); - }); - - describe('Version spec matchers', () => { - describe('semverVersionMatcher', () => { - it('semverVersionMatcher should always work as semver.satisfies does', () => { - const rangePlain = '1.1.1'; - const matcherPlain = semverVersionMatcherFactory(rangePlain); - expect(matcherPlain('1.1.1')).toBe( - semver.satisfies('1.1.1', rangePlain) - ); - expect(matcherPlain('1.1.2')).toBe( - semver.satisfies('1.1.2', rangePlain) - ); - - const rangeEq = '=1.1.1'; - const matcherEq = semverVersionMatcherFactory(rangeEq); - expect(matcherEq('1.1.1')).toBe(semver.satisfies('1.1.1', rangeEq)); - expect(matcherEq('1.1.2')).toBe(semver.satisfies('1.1.2', rangeEq)); - - // TODO: add for discovered issues if any - }); - - it("semverVersionMatcher should match release candidate as semver.satisfies does'", () => { - const rangePlain = 'v19.0.0-rc.2'; - const matcherPlain = semverVersionMatcherFactory(rangePlain); - expect(matcherPlain('v19.0.0-rc.2')).toBe( - semver.satisfies('v19.0.0-rc.2', rangePlain) - ); - expect(matcherPlain('v19.0.1-rc.2')).toBe( - semver.satisfies('v19.0.01rc.2', rangePlain) - ); - - const rangeEq = '=1.1.1'; - const matcherEq = semverVersionMatcherFactory(rangeEq); - expect(matcherPlain('v19.0.0-rc.2')).toBe( - semver.satisfies('v19.0.0-rc.2', rangePlain) - ); - expect(matcherPlain('v19.0.1-rc.2')).toBe( - semver.satisfies('v19.0.1-rc.2', rangePlain) - ); - }); - }); - - describe('canaryExactVersionMatcher', () => { - it('canaryExactVersionMatcher should match v20.0.0-v8-canary20221103f7e2421e91 only v20.0.0-v8-canary20221103f7e2421e91', () => { - const version = semver.coerce('v20.0.0')!.version; - const matcher = canaryExactVersionMatcherFactory( - version, - 'v8-canary20221103f7e2421e91' - ); - expect(matcher('v20.0.0-v8-canary20221103f7e2421e91')).toBeTruthy(); - // see https://github.com/actions/setup-node/blob/00e1b6691b40cce14b5078cb411dd1ec7dab07f7/__tests__/verify-node.sh#L10 - expect(matcher('v20.0.0-v8-canary202211026bf85d0fb4')).toBeFalsy(); - }); - }); - - describe('canaryRangeVersionMatcherFactory', () => { - it('canaryRangeVersionMatcherFactory should match v20-v8-canary to any v20.x.x', () => { - const version = semver.coerce('v20')!.version; - const matcher = canaryRangeVersionMatcherFactory(version); - expect(matcher('v20.0.0-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.0.1-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.0.0-v8-canary202211026bf85d0fb4')).toBeTruthy(); - }); - - it('canaryRangeVersionMatcherFactory should not match v20-v8-canary to v21.x & v19.x', () => { - const version = semver.coerce('v20')!.version; - const matcher = canaryRangeVersionMatcherFactory(version); - expect(matcher('v21.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.1.1-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.1.-v8-canary20221103f7e2421e91')).toBeFalsy(); - }); - - it('canaryRangeVersionMatcherFactory should match v20.1-v8-canary to any v20.1.x patch version and minor above or eq v20.1', () => { - const version = semver.coerce('v20.1')!.version; - const matcher = canaryRangeVersionMatcherFactory(version); - expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.0-v8-canary202211026bf85d0fb4')).toBeTruthy(); - expect(matcher('v20.2.0-v8-canary20221103f7e2421e91')).toBeTruthy(); - }); - - it('canaryRangeVersionMatcherFactory should not match v20.2-v8-canary to v21.x, v19.x, and v20 minor less than v20.2', () => { - const version = semver.coerce('v20.2')!.version; - const matcher = canaryRangeVersionMatcherFactory(version); - expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - }); - - it('canaryRangeVersionMatcherFactory should match v20.1.1-v8-canary to v20.1.x patch versions above or eq v20.1.1', () => { - const version = semver.coerce('v20.1.1')!.version; - const matcher = canaryRangeVersionMatcherFactory('v20.1.1-v8-canary'); - expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.2-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.2.0-v8-canary20221103f7e2421e91')).toBeTruthy(); - }); - - it('canaryRangeVersionMatcherFactory should not match v20.1.1-v8-canary to any other minor versions and patch versions below v20.1.1', () => { - const version = semver.coerce('v20.1.1')!.version; - const matcher = canaryRangeVersionMatcherFactory(version); - expect(matcher('v20.1.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.0.0-v8-canary20221103f7e2421e91')).toBeFalsy(); - }); - - it('canaryRangeVersionMatcherFactory should match v20.1.1-v8-canary to patch versions with any canary timestamp', () => { - const version = semver.coerce('v20.1.1')!.version; - const matcher = canaryRangeVersionMatcherFactory(version); - expect(matcher('v20.1.1-v8-canary20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-v8-canary202211026bf85d0fb4')).toBeTruthy(); - }); - }); - - describe('nightlyRangeVersionMatcherFactory', () => { - it('nightlyRangeVersionMatcherFactory should match v20-nightly to any v20.x.x', () => { - const version = semver.coerce('v20')!.version; - const matcher = nightlyRangeVersionMatcherFactory(version); - expect(matcher('v20.0.0-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.0.1-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.0.0-nightly202211026bf85d0fb4')).toBeTruthy(); - }); - - it('nightlyRangeVersionMatcherFactory should not match v20-nightly to v21.x & v19.x', () => { - const version = semver.coerce('v20')!.version; - const matcher = nightlyRangeVersionMatcherFactory(version); - expect(matcher('v21.0.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.1.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.1.1-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.0.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.1.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.1.-nightly20221103f7e2421e91')).toBeFalsy(); - }); - - it('nightlyRangeVersionMatcherFactory should match v20.1-nightly to any v20.1.x patch version and minor above or eq v20.1', () => { - const version = semver.coerce('v20.1')!.version; - const matcher = nightlyRangeVersionMatcherFactory(version); - expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.0-nightly202211026bf85d0fb4')).toBeTruthy(); - expect(matcher('v20.2.0-nightly20221103f7e2421e91')).toBeTruthy(); - }); - - it('nightlyRangeVersionMatcherFactory should not match v20.2-nightly to v21.x, v19.x, and v20 minor less v20.2', () => { - const version = semver.coerce('v20.2')!.version; - const matcher = nightlyRangeVersionMatcherFactory(version); - expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.0.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.0.0-nightly20221103f7e2421e91')).toBeFalsy(); - }); - - it('nightlyRangeVersionMatcherFactory should match v20.1.1-nightly to v20.1.x patch versions above or eq v20.1.1', () => { - const version = semver.coerce('v20.1.1')!.version; - const matcher = nightlyRangeVersionMatcherFactory('v20.1.1-nightly'); - expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.2-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.2.0-nightly20221103f7e2421e91')).toBeTruthy(); - }); - - it('nightlyRangeVersionMatcherFactory should not match v20.1.1-nightly to any other minor versions and patch versions below v20.1.1', () => { - const version = semver.coerce('v20.1.1')!.version; - const matcher = nightlyRangeVersionMatcherFactory(version); - expect(matcher('v20.1.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v21.0.0-nightly20221103f7e2421e91')).toBeFalsy(); - expect(matcher('v19.0.0-nightly20221103f7e2421e91')).toBeFalsy(); - }); - - it('nightlyRangeVersionMatcherFactory should match v20.1.1-nightly to patch versions with any timestamp', () => { - const version = semver.coerce('v20.1.1')!.version; - const matcher = nightlyRangeVersionMatcherFactory(version); - expect(matcher('v20.1.1-nightly20221103f7e2421e91')).toBeTruthy(); - expect(matcher('v20.1.1-nightly202211026bf85d0fb4')).toBeTruthy(); - }); - }); - }); - - describe('evaluateVersions', () => { - it('evaluateVersions should handle v8-canary version spec without timestamp', () => { - const versions = [ - 'v20.0.0-v8-canary20221103f7e2421e91', - 'v20.0.1-v8-canary20221103f7e2421e91', - 'v20.1.0-v8-canary20221103f7e2421e91', - 'v20.1.1-v8-canary20221103f7e2421e91', - 'v21.1.0-v8-canary20221103f7e2421e91', - 'v19.1.0-v8-canary20221103f7e2421e91' - ]; - const version = evaluateVersions(versions, 'v20-v8-canary'); - expect(version).toBe('v20.1.1-v8-canary20221103f7e2421e91'); - }); - - it('evaluateVersions should handle v8-canary version spec with timestamp', () => { - const versions = [ - 'v20.0.0-v8-canary20221103f7e2421e91', - 'v20.0.1-v8-canary20221103f7e2421e91', - 'v20.0.1-v8-canary20221103f7e2421e92', - 'v20.0.1-v8-canary20221103f7e2421e93', - 'v20.0.2-v8-canary20221103f7e2421e91' - ]; - const version = evaluateVersions( - versions, - 'v20.0.1-v8-canary20221103f7e2421e92' - ); - expect(version).toBe('v20.0.1-v8-canary20221103f7e2421e92'); - }); - }); - - describe('getNodejsDistUrl', () => { - it('getNodejsDistUrl should handle v8 canary version spec', async () => { - expect(getNodejsDistUrl('1.1.1-v8-canary')).toBe( - 'https://nodejs.org/download/v8-canary' - ); - expect(getNodejsDistUrl('1.1.1-v8-canary123')).toBe( - 'https://nodejs.org/download/v8-canary' - ); - expect(getNodejsDistUrl('v1.1.1-v8-canary')).toBe( - 'https://nodejs.org/download/v8-canary' - ); - expect(getNodejsDistUrl('v1.1.1-v8-canary123')).toBe( - 'https://nodejs.org/download/v8-canary' - ); - }); - - it('getNodejsDistUrl should handle nightly version spec', async () => { - expect(getNodejsDistUrl('1.1.1-nightly')).toBe( - 'https://nodejs.org/download/nightly' - ); - expect(getNodejsDistUrl('v1.1.1-nightly')).toBe( - 'https://nodejs.org/download/nightly' - ); - expect(getNodejsDistUrl('1.1.1-nightly123')).toBe( - 'https://nodejs.org/download/nightly' - ); - expect(getNodejsDistUrl('v1.1.1-nightly123')).toBe( - 'https://nodejs.org/download/nightly' - ); - }); - - it('getNodejsDistUrl should handle rc version spec', async () => { - expect(getNodejsDistUrl('1.1.1-rc')).toBe( - 'https://nodejs.org/download/rc' - ); - expect(getNodejsDistUrl('v1.1.1-rc')).toBe( - 'https://nodejs.org/download/rc' - ); - expect(getNodejsDistUrl('1.1.1-rc.0')).toBe( - 'https://nodejs.org/download/rc' - ); - expect(getNodejsDistUrl('v1.1.1-rc.0')).toBe( - 'https://nodejs.org/download/rc' - ); - }); - - it('getNodejsDistUrl should handle unspecific version spec', async () => { - expect(getNodejsDistUrl('1.1.1')).toBe('https://nodejs.org/dist'); - expect(getNodejsDistUrl('v1.1.1')).toBe('https://nodejs.org/dist'); - }); - }); -}); diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts new file mode 100644 index 00000000..3a711096 --- /dev/null +++ b/__tests__/main.test.ts @@ -0,0 +1,295 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import * as tc from '@actions/tool-cache'; +import * as cache from '@actions/cache'; + +import fs from 'fs'; +import path from 'path'; +import osm from 'os'; + +import each from 'jest-each'; + +import * as main from '../src/main'; +import * as util from '../src/util'; + +describe('main tests', () => { + let inputs = {} as any; + let os = {} as any; + + let infoSpy: jest.SpyInstance; + let warningSpy: jest.SpyInstance; + let inSpy: jest.SpyInstance; + let setOutputSpy: jest.SpyInstance; + let startGroupSpy: jest.SpyInstance; + let endGroupSpy: jest.SpyInstance; + + let existsSpy: jest.SpyInstance; + + let getExecOutputSpy: jest.SpyInstance; + + let parseNodeVersionSpy: jest.SpyInstance; + let cnSpy: jest.SpyInstance; + let findSpy: jest.SpyInstance; + let isCacheActionAvailable: jest.SpyInstance; + + beforeEach(() => { + inputs = {}; + + // node + os = {}; + console.log('::stop-commands::stoptoken'); + infoSpy = jest.spyOn(core, 'info'); + infoSpy.mockImplementation(() => {}); + setOutputSpy = jest.spyOn(core, 'setOutput'); + setOutputSpy.mockImplementation(() => {}); + warningSpy = jest.spyOn(core, 'warning'); + warningSpy.mockImplementation(() => {}); + startGroupSpy = jest.spyOn(core, 'startGroup'); + startGroupSpy.mockImplementation(() => {}); + endGroupSpy = jest.spyOn(core, 'endGroup'); + endGroupSpy.mockImplementation(() => {}); + inSpy = jest.spyOn(core, 'getInput'); + inSpy.mockImplementation(name => inputs[name]); + + getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); + + findSpy = jest.spyOn(tc, 'find'); + + isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable'); + + existsSpy = jest.spyOn(fs, 'existsSync'); + + cnSpy = jest.spyOn(process.stdout, 'write'); + cnSpy.mockImplementation(line => { + // uncomment to debug + // process.stderr.write('write:' + line + '\n'); + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + jest.clearAllMocks(); + //jest.restoreAllMocks(); + }); + + afterAll(async () => { + console.log('::stoptoken::'); + jest.restoreAllMocks(); + }, 100000); + + 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'} + ${' 14.1.0 '} | ${'14.1.0'} + ${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'} + ${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'} + `.it('parses "$contents"', ({contents, expected}) => { + expect(util.parseNodeVersionFile(contents)).toBe(expected); + }); + }); + + describe('printEnvDetailsAndSetOutput', () => { + it.each([ + [{node: '12.0.2', npm: '6.3.3', yarn: '1.22.11'}], + [{node: '16.0.2', npm: '7.3.3', yarn: '2.22.11'}], + [{node: '14.0.1', npm: '8.1.0', yarn: '3.2.1'}], + [{node: '17.0.2', npm: '6.3.3', yarn: ''}] + ])('Tools versions %p', async obj => { + getExecOutputSpy.mockImplementation(async command => { + if (Reflect.has(obj, command) && !obj[command]) { + return { + stdout: '', + stderr: `${command} does not exist`, + exitCode: 1 + }; + } + + return {stdout: obj[command], stderr: '', exitCode: 0}; + }); + + await util.printEnvDetailsAndSetOutput(); + + expect(setOutputSpy).toHaveBeenCalledWith('node-version', obj['node']); + Object.getOwnPropertyNames(obj).forEach(name => { + if (!obj[name]) { + expect(infoSpy).toHaveBeenCalledWith( + `[warning]${name} does not exist` + ); + } + expect(infoSpy).toHaveBeenCalledWith(`${name}: ${obj[name]}`); + }); + }); + }); + + describe('node-version-file flag', () => { + beforeEach(() => { + parseNodeVersionSpy = jest.spyOn(util, 'parseNodeVersionFile'); + }); + + 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(infoSpy).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(infoSpy).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(warningSpy).toHaveBeenCalledWith( + `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.` + ); + }); + + 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' + ); + }); + }); +}); diff --git a/__tests__/nightly-installer.test.ts b/__tests__/nightly-installer.test.ts new file mode 100644 index 00000000..c37b72bd --- /dev/null +++ b/__tests__/nightly-installer.test.ts @@ -0,0 +1,517 @@ +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 cache from '@actions/cache'; +import fs from 'fs'; +import cp from 'child_process'; +import osm from 'os'; +import path from 'path'; +import * as main from '../src/main'; +import * as auth from '../src/authutil'; +import {INodeVersion} from '../src/distibutions/base-models'; + +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 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'); + + // 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(() => {}); + + 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); + + //-------------------------------------------------- + // Found in cache tests + //-------------------------------------------------- + + it('finds version in cache with stable true', async () => { + inputs['node-version'] = '16-nightly'; + os['arch'] = 'x64'; + inputs.stable = 'true'; + + let toolPath = path.normalize( + '/cache/node/16.0.0-nightly20210417bc31dc0e0f/x64' + ); + findSpy.mockImplementation(() => toolPath); + findAllVersionsSpy.mockImplementation(() => [ + '12.0.1', + '16.0.0-nightly20210415c3a5e15ebe', + '16.0.0-nightly20210417bc31dc0e0f', + '16.1.3' + ]); + + await main.run(); + + expect(findSpy).toHaveBeenCalledWith( + 'node', + '16.0.0-nightly20210417bc31dc0e0f', + 'x64' + ); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + }); + + it('finds version in cache with stable false', async () => { + inputs['node-version'] = '16.0.0-nightly20210415c3a5e15ebe'; + os['arch'] = 'x64'; + inputs.stable = 'false'; + + let toolPath = path.normalize( + '/cache/node/16.0.0-nightly20210415c3a5e15ebe/x64' + ); + findSpy.mockImplementation(() => toolPath); + findAllVersionsSpy.mockImplementation(() => [ + '12.0.1', + '16.0.0-nightly20210415c3a5e15ebe', + '16.0.0-nightly20210417bc31dc0e0f', + '16.1.3' + ]); + + await main.run(); + + expect(findSpy).toHaveBeenCalledWith( + 'node', + '16.0.0-nightly20210415c3a5e15ebe', + 'x64' + ); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + }); + + it('finds version in cache and adds it to the path', async () => { + inputs['node-version'] = '16-nightly'; + os['arch'] = 'x64'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize( + '/cache/node/16.0.0-nightly20210417bc31dc0e0f/x64' + ); + findSpy.mockImplementation(() => toolPath); + findAllVersionsSpy.mockImplementation(() => [ + '12.0.1', + '16.0.0-nightly20210415c3a5e15ebe', + '16.0.0-nightly20210417bc31dc0e0f', + '16.1.3' + ]); + + await main.run(); + + expect(findSpy).toHaveBeenCalledWith( + 'node', + '16.0.0-nightly20210417bc31dc0e0f', + 'x64' + ); + + 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'] = '16.0.0-nightly20210417bc31dc0e0f'; + + findAllVersionsSpy.mockImplementation(() => [ + '12.0.1', + '16.0.0-nightly20210415c3a5e15ebe', + '16.0.0-nightly20210417bc31dc0e0f', + '16.1.3' + ]); + + findSpy.mockImplementation(() => { + throw new Error(errMsg); + }); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + 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 = '13.13.1-nightly20200415947ddec091'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => []); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + let toolPath = path.normalize( + '/cache/node/13.13.1-nightly20200415947ddec091/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(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 = '10.13.1-nightly20200415947ddec091'; + inputs['node-version'] = versionSpec; + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.mockImplementation(() => []); + await main.run(); + + 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 = '18.0.0-nightly202204180699150267'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.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: '18.0.0-nightly202110204cb3e06ed8', + osSpec: 'win32' + }, + { + arch: 'x86', + version: '20.0.0-nightly2022101987cdf7d412', + 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 = `https://nodejs.org/download/nightly/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.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('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( + `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}` + ); + } + ); + }); +}); diff --git a/__tests__/installer.test.ts b/__tests__/official-installer.test.ts similarity index 53% rename from __tests__/installer.test.ts rename to __tests__/official-installer.test.ts index 2f32aad0..5061bdf6 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/official-installer.test.ts @@ -3,15 +3,15 @@ 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'; +import OfficialBuilds from '../src/distibutions/official_builds/official_builds'; +import {INodeVersion} from '../src/distibutions/base-models'; const nodeTestManifest = require('./data/versions-manifest.json'); const nodeTestDist = require('./data/node-dist-index.json'); @@ -20,6 +20,7 @@ const nodeTestDistRc = require('./data/node-rc-index.json'); const nodeV8CanaryTestDist = require('./data/v8-canary-dist-index.json'); describe('setup-node', () => { + let build: OfficialBuilds; let inputs = {} as any; let os = {} as any; @@ -30,7 +31,6 @@ describe('setup-node', () => { 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; @@ -43,7 +43,6 @@ describe('setup-node', () => { 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; @@ -72,8 +71,6 @@ describe('setup-node', () => { exSpy = jest.spyOn(tc, 'extractTar'); cacheSpy = jest.spyOn(tc, 'cacheDir'); getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo'); - getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); - parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile'); // http-client getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson'); @@ -95,25 +92,14 @@ describe('setup-node', () => { () => 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; + res = nodeTestDistRc; } else if (url.includes('/nightly')) { - res = nodeTestDistNightly; + res = nodeTestDistNightly; } else { - res = nodeTestDist; + res = nodeTestDist; } return {result: res}; @@ -126,11 +112,11 @@ describe('setup-node', () => { warningSpy = jest.spyOn(core, 'warning'); cnSpy.mockImplementation(line => { // uncomment to debug - // process.stderr.write('write:' + line + '\n'); + process.stderr.write('write:' + line + '\n'); }); logSpy.mockImplementation(line => { - // uncomment to debug - // process.stderr.write('log:' + line + '\n'); + // uncomment to debug + process.stderr.write('log:' + line + '\n'); }); dbgSpy.mockImplementation(msg => { // uncomment to see debug output @@ -160,23 +146,6 @@ describe('setup-node', () => { //-------------------------------------------------- // 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'], @@ -316,35 +285,33 @@ describe('setup-node', () => { // 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'); + const toolPath = path.normalize('/cache/node/11.15.0/x64'); exSpy.mockImplementation(async () => '/some/other/temp/path'); cacheSpy.mockImplementation(async () => toolPath); await main.run(); - let expPath = path.join(toolPath, 'bin'); + const 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(getManifestSpy).toHaveBeenCalled(); expect(logSpy).toHaveBeenCalledWith( `Attempting to download ${versionSpec}...` ); + expect(logSpy).toHaveBeenCalledWith( + 'Not found in manifest. Falling back to download directly from Node' + ); + expect(logSpy).toHaveBeenCalledWith('came here undefined'); + expect(dlSpy).toHaveBeenCalled(); + expect(exSpy).toHaveBeenCalled(); expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); }); @@ -597,165 +564,6 @@ describe('setup-node', () => { }); }); - 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(warningSpy).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}` - '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.' - ); - }); - - 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'; @@ -931,287 +739,6 @@ describe('setup-node', () => { }); }); - 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', @@ -1252,154 +779,16 @@ describe('setup-node', () => { const toolPath = path.normalize( `/cache/node/${expectedVersion.version}/x64` ); - findSpy.mockReturnValue(toolPath); + findSpy.mockImplementation(() => toolPath); // Act await main.run(); // assert - expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); expect(logSpy).toHaveBeenCalledWith('getting latest node version...'); + expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); } ); }); - - describe('setup-node v8 canary tests', () => { - // @actions/http-client - let getDistIndexJsonSpy: jest.SpyInstance; - let findAllVersionSpy: jest.SpyInstance; - - beforeEach(() => { - // @actions/http-client - getDistIndexJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson'); - getDistIndexJsonSpy.mockImplementation(() => ({ - result: nodeV8CanaryTestDist - })); - - // @actions/tool-cache - findAllVersionSpy = jest.spyOn(tc, 'findAllVersions'); - }); - - it('v8 canary setup node flow without cached', async () => { - let versionSpec = 'v20-v8-canary'; - - inputs['node-version'] = versionSpec; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - os.platform = 'linux'; - os.arch = 'x64'; - - findAllVersionSpy.mockImplementation(() => []); - - findSpy.mockImplementation(() => ''); - - dlSpy.mockImplementation(async () => '/some/temp/path'); - let toolPath = path.normalize('/cache/node/12.16.2/x64'); - exSpy.mockImplementation(async () => '/some/other/temp/path'); - cacheSpy.mockImplementation(async () => toolPath); - - await main.run(); - - expect(dbgSpy.mock.calls[0][0]).toBe('evaluating 0 versions'); - expect(dbgSpy.mock.calls[1][0]).toBe('match not found'); - expect(logSpy.mock.calls[0][0]).toBe( - `Attempting to download ${versionSpec}...` - ); - expect(dbgSpy.mock.calls[2][0]).toBe('No manifest cached'); - expect(dbgSpy.mock.calls[3][0]).toBe( - 'Getting manifest from actions/node-versions@main' - ); - expect(dbgSpy.mock.calls[4][0].slice(0, 6)).toBe('check '); - expect(dbgSpy.mock.calls[10][0].slice(0, 6)).toBe('check '); - expect(logSpy.mock.calls[1][0]).toBe( - 'Not found in manifest. Falling back to download directly from Node' - ); - expect(dbgSpy.mock.calls[12][0]).toBe('evaluating 17 versions'); - expect(dbgSpy.mock.calls[13][0]).toBe( - 'matched: v20.0.0-v8-canary20221103f7e2421e91' - ); - expect(logSpy.mock.calls[2][0]).toBe( - 'Acquiring 20.0.0-v8-canary20221103f7e2421e91 - x64 from https://nodejs.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz' - ); - - expect(dlSpy).toHaveBeenCalledTimes(1); - expect(exSpy).toHaveBeenCalledTimes(1); - expect(cacheSpy).toHaveBeenCalledTimes(1); - }); - - it('v8 canary setup node flow with cached', async () => { - let versionSpec = 'v20-v8-canary'; - - inputs['node-version'] = versionSpec; - inputs['always-auth'] = false; - inputs['token'] = 'faketoken'; - - os.platform = 'linux'; - os.arch = 'x64'; - - const versionExpected = 'v20.0.0-v8-canary20221103f7e2421e91'; - findAllVersionSpy.mockImplementation(() => [versionExpected]); - - const toolPath = path.normalize(`/cache/node/${versionExpected}/x64`); - findSpy.mockImplementation(version => toolPath); - - await main.run(); - - expect(cnSpy).toHaveBeenCalledWith( - `::add-path::${toolPath}${path.sep}bin${osm.EOL}` - ); - - expect(dlSpy).not.toHaveBeenCalled(); - expect(exSpy).not.toHaveBeenCalled(); - expect(cacheSpy).not.toHaveBeenCalled(); - }); - }); -}); - -describe('helper methods', () => { - it('is not LTS alias', async () => { - const versionSpec = 'v99.0.0-v8-canary'; - const isLtsAlias = im.isLtsAlias(versionSpec); - expect(isLtsAlias).toBeFalsy(); - }); - - it('is not isLatestSyntax', async () => { - const versionSpec = 'v99.0.0-v8-canary'; - const isLatestSyntax = im.isLatestSyntax(versionSpec); - expect(isLatestSyntax).toBeFalsy(); - }); - - describe('getNodejsDistUrl', () => { - it('dist url to be https://nodejs.org/download/v8-canary for input versionSpec', () => { - const versionSpec = 'v99.0.0-v8-canary'; - const url = im.getNodejsDistUrl(versionSpec); - expect(url).toBe('https://nodejs.org/download/v8-canary'); - }); - - it('dist url to be https://nodejs.org/download/v8-canary for full versionSpec', () => { - const versionSpec = 'v20.0.0-v8-canary20221103f7e2421e91'; - const url = im.getNodejsDistUrl(versionSpec); - expect(url).toBe('https://nodejs.org/download/v8-canary'); - }); - }); - - describe('parseNodeVersionFile', () => { - each` - contents | expected - ${'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/__tests__/rc-installer.test.ts b/__tests__/rc-installer.test.ts new file mode 100644 index 00000000..d9eae081 --- /dev/null +++ b/__tests__/rc-installer.test.ts @@ -0,0 +1,402 @@ +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 cache from '@actions/cache'; +import fs from 'fs'; +import cp from 'child_process'; +import osm from 'os'; +import path from 'path'; +import * as main from '../src/main'; +import * as auth from '../src/authutil'; +import {INodeVersion} from '../src/distibutions/base-models'; + +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 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 mkdirpSpy: jest.SpyInstance; + let execSpy: jest.SpyInstance; + let authSpy: 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'); + // getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); + + // 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'); + isCacheActionAvailable.mockImplementation(() => false); + + // disable authentication portion for installer tests + authSpy = jest.spyOn(auth, 'configAuthentication'); + authSpy.mockImplementation(() => {}); + + 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-rc.1'); + }); + + 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); + + //-------------------------------------------------- + // Found in cache tests + //-------------------------------------------------- + + it('finds version in cache with stable true', async () => { + inputs['node-version'] = '12.0.0-rc.1'; + inputs.stable = 'true'; + + let toolPath = path.normalize('/cache/node/12.0.0-rc.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.0.0-rc.1'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.0.0-rc.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.0.0-rc.1'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.0.0-rc.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.0.0-rc.1'; + + findSpy.mockImplementation(() => { + throw new Error(errMsg); + }); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL); + }); + + it('falls back to a version from node dist', async () => { + os.platform = 'linux'; + os.arch = 'x64'; + + let versionSpec = '13.0.0-rc.0'; + + inputs['node-version'] = versionSpec; + 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/13.0.0-rc.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('Extracting ...'); + expect(logSpy).toHaveBeenCalledWith('Done'); + 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-rc.1'; + inputs['node-version'] = versionSpec; + + findSpy.mockImplementation(() => ''); + await main.run(); + + 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'; + + let versionSpec = '14.7.0-rc.1'; + + inputs['node-version'] = versionSpec; + inputs['always-auth'] = false; + inputs['token'] = 'faketoken'; + + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.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: '13.4.0-rc.0', osSpec: 'win32'}, + {arch: 'x86', version: '14.15.5-rc.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 = `https://nodejs.org/download/rc/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`; + + // ... but not in the local cache + findSpy.mockImplementation(() => ''); + findAllVersionsSpy.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('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('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(cnSpy).toHaveBeenCalledWith( + `::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}` + ); + }); + }); +}); diff --git a/dist/setup/index.js b/dist/setup/index.js index fb9a9dfd..fdbb66f2 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73210,12 +73210,12 @@ const io = __importStar(__nccwpck_require__(7436)); const semver_1 = __importDefault(__nccwpck_require__(5911)); const assert = __importStar(__nccwpck_require__(9491)); const path = __importStar(__nccwpck_require__(1017)); -const os = __importStar(__nccwpck_require__(2037)); +const os_1 = __importDefault(__nccwpck_require__(2037)); const fs_1 = __importDefault(__nccwpck_require__(7147)); class BaseDistribution { constructor(nodeInfo) { this.nodeInfo = nodeInfo; - this.osPlat = os.platform(); + this.osPlat = os_1.default.platform(); this.httpClient = new hc.HttpClient('setup-node', [], { allowRetries: true, maxRetries: 3 @@ -73223,6 +73223,14 @@ class BaseDistribution { } getNodeJsInfo() { return __awaiter(this, void 0, void 0, function* () { + if (this.nodeInfo.checkLatest) { + const nodeVersions = yield this.getNodejsVersions(); + const versions = this.filterVersions(nodeVersions); + const evaluatedVersion = this.evaluateVersions(versions); + if (evaluatedVersion) { + this.nodeInfo.versionSpec = evaluatedVersion; + } + } let toolPath = this.findVersionInHoostedToolCacheDirectory(); if (toolPath) { core.info(`Found in cache @ ${toolPath}`); @@ -73231,6 +73239,9 @@ class BaseDistribution { const nodeVersions = yield this.getNodejsVersions(); const versions = this.filterVersions(nodeVersions); const evaluatedVersion = this.evaluateVersions(versions); + if (!evaluatedVersion) { + throw new Error(`Unable to find Node version '${this.nodeInfo.versionSpec}' for platform ${this.osPlat} and architecture ${this.nodeInfo.arch}.`); + } const toolName = this.getNodejsDistInfo(evaluatedVersion, this.osPlat); toolPath = yield this.downloadNodejs(toolName); } @@ -73270,6 +73281,7 @@ class BaseDistribution { downloadNodejs(info) { return __awaiter(this, void 0, void 0, function* () { let downloadPath = ''; + core.info(`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`); try { downloadPath = yield tc.downloadTool(info.downloadUrl); } @@ -73284,7 +73296,7 @@ class BaseDistribution { return toolPath; }); } - acquireNodeFromFallbackLocation(version, arch = os.arch()) { + acquireNodeFromFallbackLocation(version, arch = os_1.default.arch()) { return __awaiter(this, void 0, void 0, function* () { const initialUrl = this.getDistributionUrl(); let osArch = this.translateArchToDistUrl(arch); @@ -73443,10 +73455,8 @@ function getNodejsDistribution(installerOptions) { return new canary_builds_1.default(installerOptions); case Distributions.RC: return new rc_builds_1.default(installerOptions); - case Distributions.DEFAULT: - return new official_builds_1.default(installerOptions); default: - return null; + return new official_builds_1.default(installerOptions); } } exports.getNodejsDistribution = getNodejsDistribution; @@ -73489,6 +73499,7 @@ class NightlyNodejs extends base_distribution_1.default { } return prerelease[0].includes('nightly'); }); + localVersionPaths.sort(semver_1.default.rcompare); const localVersion = this.evaluateVersions(localVersionPaths); if (localVersion) { toolPath = tc.find('node', localVersion, this.nodeInfo.arch); @@ -73591,7 +73602,7 @@ class OfficialBuilds extends base_distribution_1.default { nodeVersions = yield this.getNodejsVersions(); const versions = this.filterVersions(nodeVersions); this.nodeInfo.versionSpec = this.evaluateVersions(versions); - core.info(`getting latest node version...`); + core.info('getting latest node version...'); } if (this.nodeInfo.checkLatest) { core.info('Attempt to resolve the latest version from manifest...'); @@ -73610,12 +73621,17 @@ class OfficialBuilds extends base_distribution_1.default { core.info(`Found in cache @ ${toolPath}`); } else { + let downloadPath = ''; try { core.info(`Attempting to download ${this.nodeInfo.versionSpec}...`); - const versionInfo = yield this.getInfoFromManifest(this.nodeInfo.versionSpec, this.nodeInfo.arch, manifest); + const osArch = this.translateArchToDistUrl(this.nodeInfo.arch); + const versionInfo = yield this.getInfoFromManifest(this.nodeInfo.versionSpec, osArch, manifest); if (versionInfo) { core.info(`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`); - toolPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.auth); + downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.auth); + if (downloadPath) { + toolPath = yield this.extractArchive(downloadPath, versionInfo); + } } else { core.info('Not found in manifest. Falling back to download directly from Node'); @@ -73633,11 +73649,17 @@ class OfficialBuilds extends base_distribution_1.default { core.debug(err.stack); core.info('Falling back to download directly from Node'); } - const nodeVersions = yield this.getNodejsVersions(); - const versions = this.filterVersions(nodeVersions); - const evaluatedVersion = this.evaluateVersions(versions); - const toolName = this.getNodejsDistInfo(evaluatedVersion, this.osPlat); - toolPath = yield this.downloadNodejs(toolName); + if (!toolPath) { + const nodeVersions = yield this.getNodejsVersions(); + core.info('came here undefined'); + const versions = this.filterVersions(nodeVersions); + const evaluatedVersion = this.evaluateVersions(versions); + if (!evaluatedVersion) { + throw new Error(`Unable to find Node version '${this.nodeInfo.versionSpec}' for platform ${this.osPlat} and architecture ${this.nodeInfo.arch}.`); + } + const toolName = this.getNodejsDistInfo(evaluatedVersion, this.osPlat); + toolPath = yield this.downloadNodejs(toolName); + } } if (this.osPlat != 'win32') { toolPath = path_1.default.join(toolPath, 'bin'); @@ -73699,7 +73721,7 @@ class OfficialBuilds extends base_distribution_1.default { core.debug(`Found LTS release '${release.version}' for Node version '${versionSpec}'`); return release.version.split('.')[0]; } - resolveVersionFromManifest(versionSpec, osArch = this.translateArchToDistUrl(os_1.default.arch()), manifest) { + resolveVersionFromManifest(versionSpec, osArch, manifest) { return __awaiter(this, void 0, void 0, function* () { try { const info = yield this.getInfoFromManifest(versionSpec, osArch, manifest); @@ -73828,6 +73850,7 @@ class CanaryBuild extends base_distribution_1.default { } return prerelease[0].includes('v8-canary'); }); + localVersionPaths.sort(semver_1.default.rcompare); const localVersion = this.evaluateVersions(localVersionPaths); if (localVersion) { toolPath = tc.find('node', localVersion, this.nodeInfo.arch); @@ -73907,7 +73930,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const exec = __importStar(__nccwpck_require__(1514)); const fs_1 = __importDefault(__nccwpck_require__(7147)); const os_1 = __importDefault(__nccwpck_require__(2037)); const auth = __importStar(__nccwpck_require__(7573)); @@ -73915,6 +73937,7 @@ const path = __importStar(__nccwpck_require__(1017)); const cache_restore_1 = __nccwpck_require__(9517); const cache_utils_1 = __nccwpck_require__(1678); const installer_factory_1 = __nccwpck_require__(1260); +const util_1 = __nccwpck_require__(2629); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -73939,19 +73962,14 @@ function run() { const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE'; const nodejsInfo = { versionSpec: version, - checkLatest: checkLatest, + checkLatest, auth, - arch: arch + arch }; const nodeDistribution = installer_factory_1.getNodejsDistribution(nodejsInfo); - if (nodeDistribution) { - yield (nodeDistribution === null || nodeDistribution === void 0 ? void 0 : nodeDistribution.getNodeJsInfo()); - } - else { - throw new Error(`Could not resolve version: ${version} for build`); - } + yield nodeDistribution.getNodeJsInfo(); } - yield printEnvDetailsAndSetOutput(); + yield util_1.printEnvDetailsAndSetOutput(); const registryUrl = core.getInput('registry-url'); const alwaysAuth = core.getInput('always-auth'); if (registryUrl) { @@ -73986,11 +74004,39 @@ function resolveVersionInput() { if (!fs_1.default.existsSync(versionFilePath)) { throw new Error(`The specified node version file at: ${versionFilePath} does not exist`); } - version = parseNodeVersionFile(fs_1.default.readFileSync(versionFilePath, 'utf8')); + version = util_1.parseNodeVersionFile(fs_1.default.readFileSync(versionFilePath, 'utf8')); core.info(`Resolved ${versionFileInput} as ${version}`); } return version; } + + +/***/ }), + +/***/ 2629: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +const core = __importStar(__nccwpck_require__(2186)); +const exec = __importStar(__nccwpck_require__(1514)); function parseNodeVersionFile(contents) { var _a, _b, _c; let nodeVersion; @@ -74019,12 +74065,15 @@ function printEnvDetailsAndSetOutput() { core.startGroup('Environment details'); const promises = ['node', 'npm', 'yarn'].map((tool) => __awaiter(this, void 0, void 0, function* () { const output = yield getToolVersion(tool, ['--version']); + return { tool, output }; + })); + const tools = yield Promise.all(promises); + tools.forEach(({ tool, output }) => { if (tool === 'node') { core.setOutput(`${tool}-version`, output); } core.info(`${tool}: ${output}`); - })); - yield Promise.all(promises); + }); core.endGroup(); }); } @@ -74037,7 +74086,7 @@ function getToolVersion(tool, options) { silent: true }); if (exitCode > 0) { - core.warning(`[warning]${stderr}`); + core.info(`[warning]${stderr}`); return ''; } return stdout.trim(); diff --git a/src/distibutions/base-distribution.ts b/src/distibutions/base-distribution.ts index 74edb47f..2b8ebfbf 100644 --- a/src/distibutions/base-distribution.ts +++ b/src/distibutions/base-distribution.ts @@ -7,7 +7,7 @@ import semver from 'semver'; import * as assert from 'assert'; import * as path from 'path'; -import * as os from 'os'; +import os from 'os'; import fs from 'fs'; import {INodejs, INodeVersion, INodeVersionInfo} from './base-models'; @@ -27,6 +27,16 @@ export default abstract class BaseDistribution { protected abstract evaluateVersions(nodeVersions: string[]): string; public async getNodeJsInfo() { + if (this.nodeInfo.checkLatest) { + const nodeVersions = await this.getNodejsVersions(); + const versions = this.filterVersions(nodeVersions); + const evaluatedVersion = this.evaluateVersions(versions); + + if (evaluatedVersion) { + this.nodeInfo.versionSpec = evaluatedVersion; + } + } + let toolPath = this.findVersionInHoostedToolCacheDirectory(); if (toolPath) { core.info(`Found in cache @ ${toolPath}`); @@ -34,6 +44,11 @@ export default abstract class BaseDistribution { const nodeVersions = await this.getNodejsVersions(); const versions = this.filterVersions(nodeVersions); const evaluatedVersion = this.evaluateVersions(versions); + if (!evaluatedVersion) { + throw new Error( + `Unable to find Node version '${this.nodeInfo.versionSpec}' for platform ${this.osPlat} and architecture ${this.nodeInfo.arch}.` + ); + } const toolName = this.getNodejsDistInfo(evaluatedVersion, this.osPlat); toolPath = await this.downloadNodejs(toolName); } @@ -79,6 +94,9 @@ export default abstract class BaseDistribution { protected async downloadNodejs(info: INodeVersionInfo) { let downloadPath = ''; + core.info( + `Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}` + ); try { downloadPath = await tc.downloadTool(info.downloadUrl); } catch (err) { diff --git a/src/distibutions/installer-factory.ts b/src/distibutions/installer-factory.ts index 4e694c05..56cc0bf8 100644 --- a/src/distibutions/installer-factory.ts +++ b/src/distibutions/installer-factory.ts @@ -29,7 +29,7 @@ function identifyDistribution(versionSpec: string) { export function getNodejsDistribution( installerOptions: INodejs -): BaseDistribution | null { +): BaseDistribution { const distributionName = identifyDistribution(installerOptions.versionSpec); switch (distributionName) { case Distributions.NIGHTLY: @@ -38,9 +38,7 @@ export function getNodejsDistribution( return new CanaryBuild(installerOptions); case Distributions.RC: return new RcBuild(installerOptions); - case Distributions.DEFAULT: - return new OfficialBuilds(installerOptions); default: - return null; + return new OfficialBuilds(installerOptions); } } diff --git a/src/distibutions/nightly/nightly_builds.ts b/src/distibutions/nightly/nightly_builds.ts index d40e930f..48f080d9 100644 --- a/src/distibutions/nightly/nightly_builds.ts +++ b/src/distibutions/nightly/nightly_builds.ts @@ -23,6 +23,7 @@ export default class NightlyNodejs extends BaseDistribution { return prerelease[0].includes('nightly'); }); + localVersionPaths.sort(semver.rcompare); const localVersion = this.evaluateVersions(localVersionPaths); if (localVersion) { toolPath = tc.find('node', localVersion, this.nodeInfo.arch); diff --git a/src/distibutions/official_builds/official_builds.ts b/src/distibutions/official_builds/official_builds.ts index f518ee1f..7ef7b0e7 100644 --- a/src/distibutions/official_builds/official_builds.ts +++ b/src/distibutions/official_builds/official_builds.ts @@ -37,7 +37,7 @@ export default class OfficialBuilds extends BaseDistribution { const versions = this.filterVersions(nodeVersions); this.nodeInfo.versionSpec = this.evaluateVersions(versions); - core.info(`getting latest node version...`); + core.info('getting latest node version...'); } if (this.nodeInfo.checkLatest) { @@ -63,22 +63,28 @@ export default class OfficialBuilds extends BaseDistribution { if (toolPath) { core.info(`Found in cache @ ${toolPath}`); } else { + let downloadPath = ''; try { core.info(`Attempting to download ${this.nodeInfo.versionSpec}...`); + const osArch = this.translateArchToDistUrl(this.nodeInfo.arch); const versionInfo = await this.getInfoFromManifest( this.nodeInfo.versionSpec, - this.nodeInfo.arch, + osArch, manifest ); if (versionInfo) { core.info( `Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}` ); - toolPath = await tc.downloadTool( + downloadPath = await tc.downloadTool( versionInfo.downloadUrl, undefined, this.nodeInfo.auth ); + + if (downloadPath) { + toolPath = await this.extractArchive(downloadPath, versionInfo); + } } else { core.info( 'Not found in manifest. Falling back to download directly from Node' @@ -100,11 +106,19 @@ export default class OfficialBuilds extends BaseDistribution { core.info('Falling back to download directly from Node'); } - const nodeVersions = await this.getNodejsVersions(); - const versions = this.filterVersions(nodeVersions); - const evaluatedVersion = this.evaluateVersions(versions); - const toolName = this.getNodejsDistInfo(evaluatedVersion, this.osPlat); - toolPath = await this.downloadNodejs(toolName); + if (!toolPath) { + const nodeVersions = await this.getNodejsVersions(); + core.info('came here undefined'); + const versions = this.filterVersions(nodeVersions); + const evaluatedVersion = this.evaluateVersions(versions); + if (!evaluatedVersion) { + throw new Error( + `Unable to find Node version '${this.nodeInfo.versionSpec}' for platform ${this.osPlat} and architecture ${this.nodeInfo.arch}.` + ); + } + const toolName = this.getNodejsDistInfo(evaluatedVersion, this.osPlat); + toolPath = await this.downloadNodejs(toolName); + } } if (this.osPlat != 'win32') { @@ -204,7 +218,7 @@ export default class OfficialBuilds extends BaseDistribution { private async resolveVersionFromManifest( versionSpec: string, - osArch: string = this.translateArchToDistUrl(os.arch()), + osArch: string, manifest: tc.IToolRelease[] | undefined ): Promise { try { diff --git a/src/distibutions/v8-canary/canary_builds.ts b/src/distibutions/v8-canary/canary_builds.ts index 9568dad1..f785456a 100644 --- a/src/distibutions/v8-canary/canary_builds.ts +++ b/src/distibutions/v8-canary/canary_builds.ts @@ -23,7 +23,7 @@ export default class CanaryBuild extends BaseDistribution { return prerelease[0].includes('v8-canary'); }); - + localVersionPaths.sort(semver.rcompare); const localVersion = this.evaluateVersions(localVersionPaths); if (localVersion) { toolPath = tc.find('node', localVersion, this.nodeInfo.arch); diff --git a/src/main.ts b/src/main.ts index e2c98579..61a2f954 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,4 @@ import * as core from '@actions/core'; -import * as exec from '@actions/exec'; import fs from 'fs'; import os from 'os'; @@ -9,6 +8,7 @@ import * as path from 'path'; import {restoreCache} from './cache-restore'; import {isCacheFeatureAvailable} from './cache-utils'; import {getNodejsDistribution} from './distibutions/installer-factory'; +import {parseNodeVersionFile, printEnvDetailsAndSetOutput} from './util'; export async function run() { try { @@ -40,16 +40,12 @@ export async function run() { (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE'; const nodejsInfo = { versionSpec: version, - checkLatest: checkLatest, + checkLatest, auth, - arch: arch + arch }; const nodeDistribution = getNodejsDistribution(nodejsInfo); - if (nodeDistribution) { - await nodeDistribution?.getNodeJsInfo(); - } else { - throw new Error(`Could not resolve version: ${version} for build`); - } + await nodeDistribution.getNodeJsInfo(); } await printEnvDetailsAndSetOutput(); @@ -111,62 +107,3 @@ function resolveVersionInput(): string { return version; } - -export function parseNodeVersionFile(contents: string): string { - let nodeVersion: string | undefined; - - // Try parsing the file as an NPM `package.json` file. - try { - nodeVersion = JSON.parse(contents).volta?.node; - if (!nodeVersion) nodeVersion = JSON.parse(contents).engines?.node; - } catch { - core.info('Node version file is not JSON file'); - } - - if (!nodeVersion) { - const found = contents.match(/^(?:nodejs\s+)?v?(?[^\s]+)$/m); - nodeVersion = found?.groups?.version; - } - - // In the case of an unknown format, - // return as is and evaluate the version separately. - if (!nodeVersion) nodeVersion = contents.trim(); - - return nodeVersion as string; -} - -export async function printEnvDetailsAndSetOutput() { - core.startGroup('Environment details'); - - const promises = ['node', 'npm', 'yarn'].map(async tool => { - const output = await getToolVersion(tool, ['--version']); - - if (tool === 'node') { - core.setOutput(`${tool}-version`, output); - } - - core.info(`${tool}: ${output}`); - }); - - await Promise.all(promises); - - core.endGroup(); -} - -async function getToolVersion(tool: string, options: string[]) { - try { - const {stdout, stderr, exitCode} = await exec.getExecOutput(tool, options, { - ignoreReturnCode: true, - silent: true - }); - - if (exitCode > 0) { - core.warning(`[warning]${stderr}`); - return ''; - } - - return stdout.trim(); - } catch (err) { - return ''; - } -} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 00000000..60f2649c --- /dev/null +++ b/src/util.ts @@ -0,0 +1,63 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; + +export function parseNodeVersionFile(contents: string): string { + let nodeVersion: string | undefined; + + // Try parsing the file as an NPM `package.json` file. + try { + nodeVersion = JSON.parse(contents).volta?.node; + if (!nodeVersion) nodeVersion = JSON.parse(contents).engines?.node; + } catch { + core.info('Node version file is not JSON file'); + } + + if (!nodeVersion) { + const found = contents.match(/^(?:nodejs\s+)?v?(?[^\s]+)$/m); + nodeVersion = found?.groups?.version; + } + + // In the case of an unknown format, + // return as is and evaluate the version separately. + if (!nodeVersion) nodeVersion = contents.trim(); + + return nodeVersion as string; +} + +export async function printEnvDetailsAndSetOutput() { + core.startGroup('Environment details'); + + const promises = ['node', 'npm', 'yarn'].map(async tool => { + const output = await getToolVersion(tool, ['--version']); + + return {tool, output}; + }); + + const tools = await Promise.all(promises); + tools.forEach(({tool, output}) => { + if (tool === 'node') { + core.setOutput(`${tool}-version`, output); + } + core.info(`${tool}: ${output}`); + }); + + core.endGroup(); +} + +async function getToolVersion(tool: string, options: string[]) { + try { + const {stdout, stderr, exitCode} = await exec.getExecOutput(tool, options, { + ignoreReturnCode: true, + silent: true + }); + + if (exitCode > 0) { + core.info(`[warning]${stderr}`); + return ''; + } + + return stdout.trim(); + } catch (err) { + return ''; + } +}