From 06981c41e35bc124ffa90eb478be66162f9eca3f Mon Sep 17 00:00:00 2001 From: Dmitry Shibanov Date: Wed, 19 Oct 2022 16:36:26 +0200 Subject: [PATCH] initial logic --- .github/workflows/versions.yml | 20 ++++++ __tests__/authutil.test.ts | 2 +- __tests__/installer.test.ts | 5 +- dist/setup/index.js | 106 ++++++++++++++++++++++++----- src/installer.ts | 117 +++++++++++++++++++++++++++++---- src/main.ts | 11 ++-- 6 files changed, 224 insertions(+), 37 deletions(-) diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 9a138ebb..4b539a68 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -51,6 +51,26 @@ jobs: __tests__/verify-node.sh "${BASH_REMATCH[1]}" shell: bash + nightly-syntax: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + node-version: [17-nightly, 18-nightly, 19-nightly] + steps: + - uses: actions/checkout@v3 + - name: Setup Node + uses: ./ + with: + node-version: ${{ matrix.node-version }} + - name: Verify node and npm + run: | + nightlyVersion="${{ matrix.node-version }}" + majorVersion=$(echo $nightlyVersion | cut -d- -f1) + __tests__/verify-node.sh "$majorVersion" + shell: bash + manifest: runs-on: ${{ matrix.os }} strategy: diff --git a/__tests__/authutil.test.ts b/__tests__/authutil.test.ts index 1ec4e1e1..594c6a13 100644 --- a/__tests__/authutil.test.ts +++ b/__tests__/authutil.test.ts @@ -1,4 +1,4 @@ -import os = require('os'); +import os from 'os'; import * as fs from 'fs'; import * as path from 'path'; import * as core from '@actions/core'; diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 28d77e7c..d359f7ab 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -6,7 +6,7 @@ import * as im from '../src/installer'; import * as cache from '@actions/cache'; import fs from 'fs'; import cp from 'child_process'; -import osm = require('os'); +import osm from 'os'; import path from 'path'; import each from 'jest-each'; import * as main from '../src/main'; @@ -138,7 +138,8 @@ describe('setup-node', () => { }); it('can mock dist versions', async () => { - let versions: im.INodeVersion[] = await im.getVersionsFromDist(); + const versionSpec = '1.2.3'; + let versions: im.INodeVersion[] = await im.getVersionsFromDist(versionSpec); expect(versions).toBeDefined(); expect(versions?.length).toBe(23); }); diff --git a/dist/setup/index.js b/dist/setup/index.js index 0daaa2f2..0be3cc24 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -73194,6 +73194,7 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) { // Store manifest data to avoid multiple calls let manifest; let nodeVersions; + let isNightly = versionSpec.includes('nightly'); let osPlat = os.platform(); let osArch = translateArchToDistUrl(arch); if (isLtsAlias(versionSpec)) { @@ -73203,11 +73204,15 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) { versionSpec = resolveLtsAliasFromManifest(versionSpec, stable, manifest); } if (isLatestSyntax(versionSpec)) { - nodeVersions = yield getVersionsFromDist(); + nodeVersions = yield getVersionsFromDist(versionSpec); versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); core.info(`getting latest node version...`); } - if (checkLatest) { + if (isNightly && checkLatest) { + nodeVersions = yield getVersionsFromDist(versionSpec); + versionSpec = yield queryDistForMatch(versionSpec, arch, nodeVersions); + } + if (checkLatest && !isNightly) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = yield resolveVersionFromManifest(versionSpec, stable, auth, osArch, manifest); if (resolvedVersion) { @@ -73220,7 +73225,13 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) { } // check cache let toolPath; - toolPath = tc.find('node', versionSpec, osArch); + if (isNightly) { + const nightlyVersion = findNightlyVersionInHostedToolcache(versionSpec, osArch); + toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); + } + else { + toolPath = tc.find('node', versionSpec, osArch); + } // If not found in cache, download if (toolPath) { core.info(`Found in cache @ ${toolPath}`); @@ -73316,6 +73327,11 @@ function getNode(versionSpec, stable, checkLatest, auth, arch = os.arch()) { }); } exports.getNode = getNode; +function findNightlyVersionInHostedToolcache(versionsSpec, osArch) { + const foundAllVersions = tc.findAllVersions('node', osArch); + const version = evaluateVersions(foundAllVersions, versionsSpec); + return version; +} function isLtsAlias(versionSpec) { return versionSpec.startsWith('lts/'); } @@ -73382,7 +73398,8 @@ function getInfoFromDist(versionSpec, arch = os.arch(), nodeVersions) { ? `node-v${version}-win-${osArch}` : `node-v${version}-${osPlat}-${osArch}`; let urlFileName = osPlat == 'win32' ? `${fileName}.7z` : `${fileName}.tar.gz`; - let url = `https://nodejs.org/dist/v${version}/${urlFileName}`; + const initialUrl = getNodejsDistUrl(versionSpec); + const url = `${initialUrl}/v${version}/${urlFileName}`; return { downloadUrl: url, resolvedVersion: version, @@ -73403,10 +73420,51 @@ function resolveVersionFromManifest(versionSpec, stable, auth, osArch = translat } }); } +function evaluateNightlyVersions(versions, versionSpec) { + let version = ''; + let range; + const [raw, prerelease] = versionSpec.split('-'); + const isValidVersion = semver.valid(raw); + const rawVersion = isValidVersion ? raw : semver.coerce(raw); + if (rawVersion) { + if (prerelease !== 'nightly') { + range = `${rawVersion}+${prerelease.replace('nightly', 'nightly.')}`; + } + else { + range = semver.validRange(`^${rawVersion}`); + } + } + if (range) { + versions = versions.sort((a, b) => { + if (semver.gt(a, b)) { + return 1; + } + return -1; + }); + for (let i = versions.length - 1; i >= 0; i--) { + const potential = versions[i]; + const satisfied = semver.satisfies(potential.replace('-nightly', '+nightly.'), range); + if (satisfied) { + version = potential; + break; + } + } + } + if (version) { + core.debug(`matched: ${version}`); + } + else { + core.debug('match not found'); + } + return version; +} // TODO - should we just export this from @actions/tool-cache? Lifted directly from there function evaluateVersions(versions, versionSpec) { let version = ''; core.debug(`evaluating ${versions.length} versions`); + if (versionSpec.includes('nightly')) { + return evaluateNightlyVersions(versions, versionSpec); + } versions = versions.sort((a, b) => { if (semver.gt(a, b)) { return 1; @@ -73429,6 +73487,18 @@ function evaluateVersions(versions, versionSpec) { } return version; } +function getNodejsDistUrl(version) { + const prerelease = semver.prerelease(version); + if (version.includes('nightly')) { + return 'https://nodejs.org/download/nightly'; + } + else if (!prerelease) { + return 'https://nodejs.org/dist'; + } + else { + return 'https://nodejs.org/download/rc'; + } +} function queryDistForMatch(versionSpec, arch = os.arch(), nodeVersions) { return __awaiter(this, void 0, void 0, function* () { let osPlat = os.platform(); @@ -73450,7 +73520,7 @@ function queryDistForMatch(versionSpec, arch = os.arch(), nodeVersions) { } if (!nodeVersions) { core.debug('No dist manifest cached'); - nodeVersions = yield getVersionsFromDist(); + nodeVersions = yield getVersionsFromDist(versionSpec); } let versions = []; if (isLatestSyntax(versionSpec)) { @@ -73468,9 +73538,10 @@ function queryDistForMatch(versionSpec, arch = os.arch(), nodeVersions) { return version; }); } -function getVersionsFromDist() { +function getVersionsFromDist(versionSpec) { return __awaiter(this, void 0, void 0, function* () { - let dataUrl = 'https://nodejs.org/dist/index.json'; + const initialUrl = getNodejsDistUrl(versionSpec); + const dataUrl = `${initialUrl}/index.json`; let httpClient = new hc.HttpClient('setup-node', [], { allowRetries: true, maxRetries: 3 @@ -73494,6 +73565,7 @@ exports.getVersionsFromDist = getVersionsFromDist; // and lib file in a folder, not zipped. function acquireNodeFromFallbackLocation(version, arch = os.arch()) { return __awaiter(this, void 0, void 0, function* () { + const initialUrl = getNodejsDistUrl(version); let osPlat = os.platform(); let osArch = translateArchToDistUrl(arch); // Create temporary folder to download in to @@ -73505,8 +73577,8 @@ function acquireNodeFromFallbackLocation(version, arch = os.arch()) { let exeUrl; let libUrl; try { - exeUrl = `https://nodejs.org/dist/v${version}/win-${osArch}/node.exe`; - libUrl = `https://nodejs.org/dist/v${version}/win-${osArch}/node.lib`; + exeUrl = `${initialUrl}/v${version}/win-${osArch}/node.exe`; + libUrl = `${initialUrl}/v${version}/win-${osArch}/node.lib`; core.info(`Downloading only node binary from ${exeUrl}`); const exePath = yield tc.downloadTool(exeUrl); yield io.cp(exePath, path.join(tempDir, 'node.exe')); @@ -73515,8 +73587,8 @@ function acquireNodeFromFallbackLocation(version, arch = os.arch()) { } catch (err) { if (err instanceof tc.HTTPError && err.httpStatusCode == 404) { - exeUrl = `https://nodejs.org/dist/v${version}/node.exe`; - libUrl = `https://nodejs.org/dist/v${version}/node.lib`; + exeUrl = `${initialUrl}/v${version}/node.exe`; + libUrl = `${initialUrl}/v${version}/node.lib`; const exePath = yield tc.downloadTool(exeUrl); yield io.cp(exePath, path.join(tempDir, 'node.exe')); const libPath = yield tc.downloadTool(libUrl); @@ -73607,7 +73679,7 @@ const auth = __importStar(__nccwpck_require__(7573)); const path = __importStar(__nccwpck_require__(1017)); const cache_restore_1 = __nccwpck_require__(9517); const cache_utils_1 = __nccwpck_require__(1678); -const os = __nccwpck_require__(2037); +const os_1 = __importDefault(__nccwpck_require__(2037)); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -73615,7 +73687,7 @@ function run() { // Version is optional. If supplied, install / use from the tool cache // If not supplied then task is still used to setup proxy, auth, etc... // - let version = resolveVersionInput(); + const version = resolveVersionInput(); let arch = core.getInput('architecture'); const cache = core.getInput('cache'); // if architecture supplied but node-version is not @@ -73624,12 +73696,12 @@ function run() { core.warning('`architecture` is provided but `node-version` is missing. In this configuration, the version/architecture of Node will not be changed. To fix this, provide `architecture` in combination with `node-version`'); } if (!arch) { - arch = os.arch(); + arch = os_1.default.arch(); } if (version) { - let token = core.getInput('token'); - let auth = !token || cache_utils_1.isGhes() ? undefined : `token ${token}`; - let stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; + const token = core.getInput('token'); + const auth = !token || cache_utils_1.isGhes() ? undefined : `token ${token}`; + const stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE'; yield installer.getNode(version, stable, checkLatest, auth, arch); } diff --git a/src/installer.ts b/src/installer.ts index 83a43d85..53ace18e 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -11,6 +11,8 @@ import fs = require('fs'); // // Node versions interface // see https://nodejs.org/dist/index.json +// for nightly https://nodejs.org/download/nightly/index.json +// for rc https://nodejs.org/download/rc/index.json // export interface INodeVersion { version: string; @@ -38,6 +40,7 @@ export async function getNode( // Store manifest data to avoid multiple calls let manifest: INodeRelease[] | undefined; let nodeVersions: INodeVersion[] | undefined; + let isNightly = versionSpec.includes('nightly'); let osPlat: string = os.platform(); let osArch: string = translateArchToDistUrl(arch); @@ -51,12 +54,17 @@ export async function getNode( } if (isLatestSyntax(versionSpec)) { - nodeVersions = await getVersionsFromDist(); + nodeVersions = await getVersionsFromDist(versionSpec); versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); core.info(`getting latest node version...`); } - if (checkLatest) { + if (isNightly && checkLatest) { + nodeVersions = await getVersionsFromDist(versionSpec); + versionSpec = await queryDistForMatch(versionSpec, arch, nodeVersions); + } + + if (checkLatest && !isNightly) { core.info('Attempt to resolve the latest version from manifest...'); const resolvedVersion = await resolveVersionFromManifest( versionSpec, @@ -75,7 +83,15 @@ export async function getNode( // check cache let toolPath: string; - toolPath = tc.find('node', versionSpec, osArch); + if (isNightly) { + const nightlyVersion = findNightlyVersionInHostedToolcache( + versionSpec, + osArch + ); + toolPath = nightlyVersion && tc.find('node', nightlyVersion, osArch); + } else { + toolPath = tc.find('node', versionSpec, osArch); + } // If not found in cache, download if (toolPath) { @@ -199,6 +215,16 @@ export async function getNode( core.addPath(toolPath); } +function findNightlyVersionInHostedToolcache( + versionsSpec: string, + osArch: string +) { + const foundAllVersions = tc.findAllVersions('node', osArch); + const version = evaluateVersions(foundAllVersions, versionsSpec); + + return version; +} + function isLtsAlias(versionSpec: string): boolean { return versionSpec.startsWith('lts/'); } @@ -306,7 +332,8 @@ async function getInfoFromDist( : `node-v${version}-${osPlat}-${osArch}`; let urlFileName: string = osPlat == 'win32' ? `${fileName}.7z` : `${fileName}.tar.gz`; - let url = `https://nodejs.org/dist/v${version}/${urlFileName}`; + const initialUrl = getNodejsDistUrl(versionSpec); + const url = `${initialUrl}/v${version}/${urlFileName}`; return { downloadUrl: url, @@ -338,10 +365,61 @@ async function resolveVersionFromManifest( } } +function evaluateNightlyVersions( + versions: string[], + versionSpec: string +): string { + let version = ''; + let range: string | null | undefined; + const [raw, prerelease] = versionSpec.split('-'); + const isValidVersion = semver.valid(raw); + const rawVersion = isValidVersion ? raw : semver.coerce(raw); + if (rawVersion) { + if (prerelease !== 'nightly') { + range = `${rawVersion}+${prerelease.replace('nightly', 'nightly.')}`; + } else { + range = semver.validRange(`^${rawVersion}`); + } + } + + if (range) { + versions = versions.sort((a, b) => { + if (semver.gt(a, b)) { + return 1; + } + return -1; + }); + for (let i = versions.length - 1; i >= 0; i--) { + const potential: string = versions[i]; + const satisfied: boolean = semver.satisfies( + potential.replace('-nightly', '+nightly.'), + range + ); + if (satisfied) { + version = potential; + break; + } + } + } + + if (version) { + core.debug(`matched: ${version}`); + } else { + core.debug('match not found'); + } + + return version; +} + // TODO - should we just export this from @actions/tool-cache? Lifted directly from there function evaluateVersions(versions: string[], versionSpec: string): string { let version = ''; core.debug(`evaluating ${versions.length} versions`); + + if (versionSpec.includes('nightly')) { + return evaluateNightlyVersions(versions, versionSpec); + } + versions = versions.sort((a, b) => { if (semver.gt(a, b)) { return 1; @@ -366,6 +444,17 @@ function evaluateVersions(versions: string[], versionSpec: string): string { return version; } +function getNodejsDistUrl(version: string) { + const prerelease = semver.prerelease(version); + if (version.includes('nightly')) { + return 'https://nodejs.org/download/nightly'; + } else if (!prerelease) { + return 'https://nodejs.org/dist'; + } else { + return 'https://nodejs.org/download/rc'; + } +} + async function queryDistForMatch( versionSpec: string, arch: string = os.arch(), @@ -392,7 +481,7 @@ async function queryDistForMatch( if (!nodeVersions) { core.debug('No dist manifest cached'); - nodeVersions = await getVersionsFromDist(); + nodeVersions = await getVersionsFromDist(versionSpec); } let versions: string[] = []; @@ -410,12 +499,15 @@ async function queryDistForMatch( }); // get the latest version that matches the version spec - let version: string = evaluateVersions(versions, versionSpec); + let version = evaluateVersions(versions, versionSpec); return version; } -export async function getVersionsFromDist(): Promise { - let dataUrl = 'https://nodejs.org/dist/index.json'; +export async function getVersionsFromDist( + versionSpec: string +): Promise { + const initialUrl = getNodejsDistUrl(versionSpec); + const dataUrl = `${initialUrl}/index.json`; let httpClient = new hc.HttpClient('setup-node', [], { allowRetries: true, maxRetries: 3 @@ -440,6 +532,7 @@ async function acquireNodeFromFallbackLocation( version: string, arch: string = os.arch() ): Promise { + const initialUrl = getNodejsDistUrl(version); let osPlat: string = os.platform(); let osArch: string = translateArchToDistUrl(arch); @@ -453,8 +546,8 @@ async function acquireNodeFromFallbackLocation( let exeUrl: string; let libUrl: string; try { - exeUrl = `https://nodejs.org/dist/v${version}/win-${osArch}/node.exe`; - libUrl = `https://nodejs.org/dist/v${version}/win-${osArch}/node.lib`; + exeUrl = `${initialUrl}/v${version}/win-${osArch}/node.exe`; + libUrl = `${initialUrl}/v${version}/win-${osArch}/node.lib`; core.info(`Downloading only node binary from ${exeUrl}`); @@ -464,8 +557,8 @@ async function acquireNodeFromFallbackLocation( await io.cp(libPath, path.join(tempDir, 'node.lib')); } catch (err) { if (err instanceof tc.HTTPError && err.httpStatusCode == 404) { - exeUrl = `https://nodejs.org/dist/v${version}/node.exe`; - libUrl = `https://nodejs.org/dist/v${version}/node.lib`; + exeUrl = `${initialUrl}/v${version}/node.exe`; + libUrl = `${initialUrl}/v${version}/node.lib`; const exePath = await tc.downloadTool(exeUrl); await io.cp(exePath, path.join(tempDir, 'node.exe')); diff --git a/src/main.ts b/src/main.ts index 6a980a0d..3faf67c7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,7 +6,7 @@ import * as auth from './authutil'; import * as path from 'path'; import {restoreCache} from './cache-restore'; import {isGhes, isCacheFeatureAvailable} from './cache-utils'; -import os = require('os'); +import os from 'os'; export async function run() { try { @@ -14,7 +14,7 @@ export async function run() { // Version is optional. If supplied, install / use from the tool cache // If not supplied then task is still used to setup proxy, auth, etc... // - let version = resolveVersionInput(); + const version = resolveVersionInput(); let arch = core.getInput('architecture'); const cache = core.getInput('cache'); @@ -32,9 +32,10 @@ export async function run() { } if (version) { - let token = core.getInput('token'); - let auth = !token || isGhes() ? undefined : `token ${token}`; - let stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; + const token = core.getInput('token'); + const auth = !token || isGhes() ? undefined : `token ${token}`; + const stable = + (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE'; await installer.getNode(version, stable, checkLatest, auth, arch);