mirror actions/setup-node @ 49933ea5288caeca8642d1e84afbd3f7d6820020
e2e-cache / Test npm (Node 18, ubuntu-latest) (push) Failing after 3s
e2e-cache / Test npm (Node 20, ubuntu-latest) (push) Failing after 2s
e2e-cache / Test npm (Node 22, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test pnpm (Node 18, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test pnpm (Node 20, ubuntu-latest) (push) Failing after 2s
e2e-cache / Test pnpm (Node 22, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test yarn 1 (Node 18, ubuntu-latest) (push) Failing after 0s
e2e-cache / Test yarn 1 (Node 20, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test yarn 3 (Node 18, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test yarn 3 (Node 20, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test yarn 3 (Node 22, ubuntu-latest) (push) Failing after 1s
e2e-cache / Test yarn subprojects (18) (push) Failing after 1s
e2e-cache / Test yarn subprojects (20) (push) Failing after 1s
e2e-cache / Test yarn subprojects (22) (push) Failing after 1s
e2e-cache / Test yarn subprojects all locally managed (18) (push) Failing after 1s
e2e-cache / Test yarn subprojects all locally managed (20) (push) Failing after 0s
e2e-cache / Test yarn subprojects all locally managed (22) (push) Failing after 1s
e2e-cache / Test yarn subprojects some locally managed (18) (push) Failing after 1s
e2e-cache / Test yarn subprojects some locally managed (20) (push) Failing after 0s
e2e-cache / Test yarn subprojects some locally managed (22) (push) Failing after 1s
e2e-cache / Test yarn subprojects managed by git (18) (push) Failing after 1s
e2e-cache / Test yarn subprojects managed by git (20) (push) Failing after 1s
e2e-cache / Test yarn subprojects managed by git (22) (push) Failing after 1s
proxy / test-proxy (push) Failing after 2s
proxy / test-bypass-proxy (push) Failing after 1s
versions / local-cache (18, ubuntu-latest) (push) Failing after 1s
versions / local-cache (20, ubuntu-latest) (push) Failing after 1s
versions / local-cache (22, ubuntu-latest) (push) Failing after 1s
versions / lts-syntax (lts/*, ubuntu-latest) (push) Failing after 2s
versions / lts-syntax (lts/-1, ubuntu-latest) (push) Failing after 1s
versions / lts-syntax (lts/dubnium, ubuntu-latest) (push) Failing after 2s
versions / lts-syntax (lts/erbium, ubuntu-latest) (push) Failing after 1s
versions / lts-syntax (lts/fermium, ubuntu-latest) (push) Failing after 4s
versions / v8-canary-syntax (20-v8-canary, ubuntu-latest) (push) Failing after 3s
versions / v8-canary-syntax (20.0.0-v8-canary, ubuntu-latest) (push) Failing after 4s
versions / v8-canary-syntax (20.0.0-v8-canary20221101e50e45c9f8, ubuntu-latest) (push) Failing after 4s
versions / nightly-syntax (18.0.0-nightly, ubuntu-latest) (push) Failing after 6s
versions / nightly-syntax (20-nightly, ubuntu-latest) (push) Failing after 1s
versions / nightly-syntax (21-nightly, ubuntu-latest) (push) Failing after 1s
versions / rc-syntax (18.0.0-rc.2, ubuntu-latest) (push) Failing after 0s
versions / rc-syntax (19.0.0-rc.0, ubuntu-latest) (push) Failing after 1s
versions / rc-syntax (20.0.0-rc.1, ubuntu-latest) (push) Failing after 1s
versions / manifest (18.20.0, ubuntu-latest) (push) Failing after 1s
versions / manifest (20.10.0, ubuntu-latest) (push) Failing after 1s
versions / manifest (22.0.0, ubuntu-latest) (push) Failing after 2s
versions / local-cache (18, macos-13) (push) Canceled after 0s
versions / local-cache (18, macos-latest) (push) Canceled after 0s
versions / local-cache (18, windows-latest) (push) Canceled after 0s
versions / local-cache (20, macos-13) (push) Canceled after 0s
versions / local-cache (20, macos-latest) (push) Canceled after 0s
versions / local-cache (20, windows-latest) (push) Canceled after 0s
e2e-cache / Test npm (Node 18, macos-13) (push) Canceled after 0s
e2e-cache / Test npm (Node 18, macos-latest) (push) Canceled after 0s
versions / local-cache (22, macos-13) (push) Canceled after 0s
e2e-cache / Test npm (Node 18, windows-latest) (push) Canceled after 0s
versions / local-cache (22, macos-latest) (push) Canceled after 0s
e2e-cache / Test npm (Node 20, macos-13) (push) Canceled after 0s
e2e-cache / Test npm (Node 20, macos-latest) (push) Canceled after 0s
versions / local-cache (22, windows-latest) (push) Canceled after 0s
e2e-cache / Test npm (Node 20, windows-latest) (push) Canceled after 0s
e2e-cache / Test npm (Node 22, macos-13) (push) Canceled after 0s
versions / lts-syntax (lts/-1, macos-13) (push) Canceled after 0s
e2e-cache / Test npm (Node 22, macos-latest) (push) Canceled after 0s
versions / lts-syntax (lts/-1, windows-latest) (push) Canceled after 0s
e2e-cache / Test npm (Node 22, windows-latest) (push) Canceled after 0s
versions / lts-syntax (lts/*, macos-13) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 18, macos-13) (push) Canceled after 0s
versions / lts-syntax (lts/*, windows-latest) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 18, macos-latest) (push) Canceled after 0s
versions / lts-syntax (lts/dubnium, macos-13) (push) Canceled after 0s
versions / lts-syntax (lts/dubnium, windows-latest) (push) Canceled after 0s
versions / lts-syntax (lts/erbium, macos-13) (push) Canceled after 0s
versions / lts-syntax (lts/erbium, windows-latest) (push) Canceled after 0s
versions / lts-syntax (lts/fermium, macos-13) (push) Canceled after 0s
versions / lts-syntax (lts/fermium, windows-latest) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 18, windows-latest) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 20, macos-13) (push) Canceled after 0s
versions / v8-canary-syntax (20-v8-canary, macos-13) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 20, macos-latest) (push) Canceled after 0s
versions / v8-canary-syntax (20-v8-canary, macos-latest) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 20, windows-latest) (push) Canceled after 0s
versions / v8-canary-syntax (20-v8-canary, windows-latest) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 22, macos-13) (push) Canceled after 0s
versions / v8-canary-syntax (20.0.0-v8-canary20221101e50e45c9f8, macos-13) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 22, macos-latest) (push) Canceled after 0s
versions / v8-canary-syntax (20.0.0-v8-canary20221101e50e45c9f8, macos-latest) (push) Canceled after 0s
e2e-cache / Test pnpm (Node 22, windows-latest) (push) Canceled after 0s
versions / v8-canary-syntax (20.0.0-v8-canary20221101e50e45c9f8, windows-latest) (push) Canceled after 0s
e2e-cache / Test yarn 1 (Node 18, macos-13) (push) Canceled after 0s
versions / v8-canary-syntax (20.0.0-v8-canary, macos-13) (push) Canceled after 0s
e2e-cache / Test yarn 1 (Node 18, macos-latest) (push) Canceled after 0s
versions / v8-canary-syntax (20.0.0-v8-canary, macos-latest) (push) Canceled after 0s
e2e-cache / Test yarn 1 (Node 18, windows-latest) (push) Canceled after 0s
versions / v8-canary-syntax (20.0.0-v8-canary, windows-latest) (push) Canceled after 0s
e2e-cache / Test yarn 1 (Node 20, macos-13) (push) Canceled after 0s
versions / nightly-syntax (18.0.0-nightly, macos-13) (push) Canceled after 0s
e2e-cache / Test yarn 1 (Node 20, macos-latest) (push) Canceled after 0s
versions / nightly-syntax (18.0.0-nightly, macos-latest) (push) Canceled after 0s
versions / nightly-syntax (18.0.0-nightly, windows-latest) (push) Canceled after 0s
e2e-cache / Test yarn 1 (Node 20, windows-latest) (push) Canceled after 0s
versions / nightly-syntax (20-nightly, macos-13) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 18, macos-13) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 18, macos-latest) (push) Canceled after 0s
versions / nightly-syntax (20-nightly, macos-latest) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 18, windows-latest) (push) Canceled after 0s
versions / nightly-syntax (20-nightly, windows-latest) (push) Canceled after 0s
versions / nightly-syntax (21-nightly, macos-13) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 20, macos-13) (push) Canceled after 0s
versions / nightly-syntax (21-nightly, macos-latest) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 20, macos-latest) (push) Canceled after 0s
versions / nightly-syntax (21-nightly, windows-latest) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 20, windows-latest) (push) Canceled after 0s
versions / rc-syntax (18.0.0-rc.2, macos-13) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 22, macos-13) (push) Canceled after 0s
versions / rc-syntax (18.0.0-rc.2, macos-latest) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 22, macos-latest) (push) Canceled after 0s
versions / rc-syntax (19.0.0-rc.0, macos-13) (push) Canceled after 0s
versions / rc-syntax (18.0.0-rc.2, windows-latest) (push) Canceled after 0s
versions / rc-syntax (19.0.0-rc.0, macos-latest) (push) Canceled after 0s
e2e-cache / Test yarn 3 (Node 22, windows-latest) (push) Canceled after 0s
versions / rc-syntax (19.0.0-rc.0, windows-latest) (push) Canceled after 0s
versions / rc-syntax (20.0.0-rc.1, macos-13) (push) Canceled after 0s
versions / rc-syntax (20.0.0-rc.1, macos-latest) (push) Canceled after 0s
versions / rc-syntax (20.0.0-rc.1, windows-latest) (push) Canceled after 0s
versions / manifest (18.20.0, macos-13) (push) Canceled after 0s
versions / manifest (18.20.0, macos-latest) (push) Canceled after 0s
versions / manifest (18.20.0, windows-latest) (push) Canceled after 0s
versions / manifest (20.10.0, macos-13) (push) Canceled after 0s
versions / manifest (20.10.0, macos-latest) (push) Canceled after 0s
versions / manifest (20.10.0, windows-latest) (push) Canceled after 0s
versions / manifest (22.0.0, macos-13) (push) Canceled after 0s
versions / manifest (22.0.0, macos-latest) (push) Canceled after 0s
versions / manifest (22.0.0, windows-latest) (push) Canceled after 0s
versions / check-latest (18, macos-13) (push) Canceled after 0s
versions / check-latest (18, macos-latest) (push) Canceled after 0s
versions / check-latest (18, ubuntu-latest) (push) Failing after 1s
versions / check-latest (18, windows-latest) (push) Canceled after 0s
versions / check-latest (20, macos-13) (push) Canceled after 0s
versions / check-latest (20, macos-latest) (push) Canceled after 0s
versions / check-latest (20, windows-latest) (push) Canceled after 0s
versions / check-latest (22, macos-13) (push) Canceled after 0s
versions / check-latest (22, macos-latest) (push) Canceled after 0s
versions / check-latest (22, ubuntu-latest) (push) Canceled after 0s
versions / check-latest (22, windows-latest) (push) Canceled after 0s
versions / version-file (.nvmrc, macos-latest) (push) Canceled after 0s
versions / version-file (.nvmrc, macos-13) (push) Canceled after 0s
versions / version-file (.nvmrc, ubuntu-latest) (push) Canceled after 0s
versions / version-file (.nvmrc, windows-latest) (push) Canceled after 0s
versions / version-file (.tool-versions, macos-13) (push) Canceled after 0s
versions / version-file (.tool-versions-node, macos-13) (push) Canceled after 0s
versions / version-file (.tool-versions, macos-latest) (push) Canceled after 0s
versions / version-file (.tool-versions-node, macos-latest) (push) Canceled after 0s
versions / version-file (.tool-versions, ubuntu-latest) (push) Canceled after 0s
versions / version-file (.tool-versions-node, ubuntu-latest) (push) Canceled after 0s
versions / version-file (.tool-versions, windows-latest) (push) Canceled after 0s
versions / version-file (.tool-versions-node, windows-latest) (push) Canceled after 0s
versions / version-file (package.json, macos-13) (push) Canceled after 0s
versions / version-file (package.json, macos-latest) (push) Canceled after 0s
versions / version-file (package.json, windows-latest) (push) Canceled after 0s
versions / version-file (package.json, ubuntu-latest) (push) Canceled after 0s
versions / version-file-volta (macos-13) (push) Canceled after 0s
versions / version-file-volta (ubuntu-latest) (push) Canceled after 0s
versions / version-file-volta (windows-latest) (push) Canceled after 0s
versions / version-file-volta-extends (macos-13) (push) Canceled after 0s
versions / version-file-volta (macos-latest) (push) Canceled after 0s
versions / version-file-volta-extends (macos-latest) (push) Canceled after 0s
versions / version-file-volta-extends (ubuntu-latest) (push) Canceled after 0s
versions / version-file-volta-extends (windows-latest) (push) Canceled after 0s
versions / node-dist (17, macos-13) (push) Canceled after 0s
versions / node-dist (17, macos-latest) (push) Canceled after 0s
versions / node-dist (17, ubuntu-latest) (push) Canceled after 0s
versions / node-dist (17, windows-latest) (push) Canceled after 0s
versions / node-dist (19, macos-13) (push) Canceled after 0s
versions / node-dist (19, macos-latest) (push) Canceled after 0s
versions / node-dist (19, ubuntu-latest) (push) Canceled after 0s
versions / node-dist (19, windows-latest) (push) Canceled after 0s
versions / old-versions (macos-13) (push) Canceled after 0s
versions / old-versions (ubuntu-latest) (push) Canceled after 0s
versions / old-versions (windows-latest) (push) Canceled after 0s
versions / arch (push) Canceled after 0s
versions / node-latest-aliases (current, macos-13) (push) Canceled after 0s
versions / node-latest-aliases (current, macos-latest) (push) Canceled after 0s
versions / node-latest-aliases (current, ubuntu-latest) (push) Canceled after 0s
versions / node-latest-aliases (current, windows-latest) (push) Canceled after 0s
versions / node-latest-aliases (latest, macos-13) (push) Canceled after 0s
versions / node-latest-aliases (latest, macos-latest) (push) Canceled after 0s
versions / node-latest-aliases (latest, ubuntu-latest) (push) Canceled after 0s
versions / node-latest-aliases (latest, windows-latest) (push) Canceled after 0s
versions / node-latest-aliases (node, macos-13) (push) Canceled after 0s
versions / node-latest-aliases (node, macos-latest) (push) Canceled after 0s
versions / node-latest-aliases (node, windows-latest) (push) Canceled after 0s
versions / node-latest-aliases (node, ubuntu-latest) (push) Canceled after 0s
versions / check-latest (20, ubuntu-latest) (push) Failing after 1s

This commit is contained in:
George Berkshire
2026-08-16 19:45:43 +08:00
commit 90c75d071a
165 changed files with 212682 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as core from '@actions/core';
import * as github from '@actions/github';
export function configAuthentication(registryUrl: string, alwaysAuth: string) {
const npmrc: string = path.resolve(
process.env['RUNNER_TEMP'] || process.cwd(),
'.npmrc'
);
if (!registryUrl.endsWith('/')) {
registryUrl += '/';
}
writeRegistryToFile(registryUrl, npmrc, alwaysAuth);
}
function writeRegistryToFile(
registryUrl: string,
fileLocation: string,
alwaysAuth: string
) {
let scope: string = core.getInput('scope');
if (!scope && registryUrl.indexOf('npm.pkg.github.com') > -1) {
scope = github.context.repo.owner;
}
if (scope && scope[0] != '@') {
scope = '@' + scope;
}
if (scope) {
scope = scope.toLowerCase() + ':';
}
core.debug(`Setting auth in ${fileLocation}`);
let newContents = '';
if (fs.existsSync(fileLocation)) {
const curContents: string = fs.readFileSync(fileLocation, 'utf8');
curContents.split(os.EOL).forEach((line: string) => {
// Add current contents unless they are setting the registry
if (!line.toLowerCase().startsWith(`${scope}registry`)) {
newContents += line + os.EOL;
}
});
}
// Remove http: or https: from front of registry.
const authString: string =
registryUrl.replace(/(^\w+:|^)/, '') + ':_authToken=${NODE_AUTH_TOKEN}';
const registryString = `${scope}registry=${registryUrl}`;
const alwaysAuthString = `always-auth=${alwaysAuth}`;
newContents += `${authString}${os.EOL}${registryString}${os.EOL}${alwaysAuthString}`;
fs.writeFileSync(fileLocation, newContents);
core.exportVariable('NPM_CONFIG_USERCONFIG', fileLocation);
// Export empty node_auth_token if didn't exist so npm doesn't complain about not being able to find it
core.exportVariable(
'NODE_AUTH_TOKEN',
process.env.NODE_AUTH_TOKEN || 'XXXXX-XXXXX-XXXXX-XXXXX'
);
}
+88
View File
@@ -0,0 +1,88 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as glob from '@actions/glob';
import path from 'path';
import fs from 'fs';
import os from 'os';
import {State} from './constants';
import {
getCacheDirectories,
getPackageManagerInfo,
repoHasYarnBerryManagedDependencies,
PackageManagerInfo
} from './cache-utils';
export const restoreCache = async (
packageManager: string,
cacheDependencyPath: string
) => {
const packageManagerInfo = await getPackageManagerInfo(packageManager);
if (!packageManagerInfo) {
throw new Error(`Caching for '${packageManager}' is not supported`);
}
const platform = process.env.RUNNER_OS;
const arch = os.arch();
const cachePaths = await getCacheDirectories(
packageManagerInfo,
cacheDependencyPath
);
core.saveState(State.CachePaths, cachePaths);
const lockFilePath = cacheDependencyPath
? cacheDependencyPath
: findLockFile(packageManagerInfo);
const fileHash = await glob.hashFiles(lockFilePath);
if (!fileHash) {
throw new Error(
'Some specified paths were not resolved, unable to cache dependencies.'
);
}
const keyPrefix = `node-cache-${platform}-${arch}-${packageManager}`;
const primaryKey = `${keyPrefix}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`);
core.saveState(State.CachePrimaryKey, primaryKey);
const isManagedByYarnBerry = await repoHasYarnBerryManagedDependencies(
packageManagerInfo,
cacheDependencyPath
);
let cacheKey: string | undefined;
if (isManagedByYarnBerry) {
core.info(
'All dependencies are managed locally by yarn3, the previous cache can be used'
);
cacheKey = await cache.restoreCache(cachePaths, primaryKey, [keyPrefix]);
} else {
cacheKey = await cache.restoreCache(cachePaths, primaryKey);
}
core.setOutput('cache-hit', Boolean(cacheKey));
if (!cacheKey) {
core.info(`${packageManager} cache is not found`);
return;
}
core.saveState(State.CacheMatchedKey, cacheKey);
core.info(`Cache restored from key: ${cacheKey}`);
};
const findLockFile = (packageManager: PackageManagerInfo) => {
const lockFiles = packageManager.lockFilePatterns;
const workspace = process.env.GITHUB_WORKSPACE!;
const rootContent = fs.readdirSync(workspace);
const lockFile = lockFiles.find(item => rootContent.includes(item));
if (!lockFile) {
throw new Error(
`Dependencies lock file is not found in ${workspace}. Supported file patterns: ${lockFiles.toString()}`
);
}
return path.join(workspace, lockFile);
};
+71
View File
@@ -0,0 +1,71 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import {State} from './constants';
import {getPackageManagerInfo} from './cache-utils';
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
// throw an uncaught exception. Instead of failing this action, just warn.
process.on('uncaughtException', e => {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${e.message}`);
});
// Added early exit to resolve issue with slow post action step:
export async function run(earlyExit?: boolean) {
try {
const cacheLock = core.getState(State.CachePackageManager);
if (cacheLock) {
await cachePackages(cacheLock);
if (earlyExit) {
process.exit(0);
}
} else {
core.debug(`Caching for '${cacheLock}' is not supported`);
}
} catch (error) {
core.setFailed((error as Error).message);
}
}
const cachePackages = async (packageManager: string) => {
const state = core.getState(State.CacheMatchedKey);
const primaryKey = core.getState(State.CachePrimaryKey);
const cachePaths = JSON.parse(
core.getState(State.CachePaths) || '[]'
) as string[];
const packageManagerInfo = await getPackageManagerInfo(packageManager);
if (!packageManagerInfo) {
core.debug(`Caching for '${packageManager}' is not supported`);
return;
}
if (!cachePaths.length) {
// TODO: core.getInput has a bug - it can return undefined despite its definition (tests only?)
// export declare function getInput(name: string, options?: InputOptions): string;
const cacheDependencyPath = core.getInput('cache-dependency-path') || '';
throw new Error(
`Cache folder paths are not retrieved for ${packageManager} with cache-dependency-path = ${cacheDependencyPath}`
);
}
if (primaryKey === state) {
core.info(
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
);
return;
}
const cacheId = await cache.saveCache(cachePaths, primaryKey);
if (cacheId == -1) {
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
};
run(true);
+322
View File
@@ -0,0 +1,322 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as cache from '@actions/cache';
import * as glob from '@actions/glob';
import path from 'path';
import fs from 'fs';
import {unique} from './util';
export interface PackageManagerInfo {
name: string;
lockFilePatterns: Array<string>;
getCacheFolderPath: (projectDir?: string) => Promise<string>;
}
interface SupportedPackageManagers {
npm: PackageManagerInfo;
pnpm: PackageManagerInfo;
yarn: PackageManagerInfo;
}
export const supportedPackageManagers: SupportedPackageManagers = {
npm: {
name: 'npm',
lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'],
getCacheFolderPath: () =>
getCommandOutputNotEmpty(
'npm config get cache',
'Could not get npm cache folder path'
)
},
pnpm: {
name: 'pnpm',
lockFilePatterns: ['pnpm-lock.yaml'],
getCacheFolderPath: () =>
getCommandOutputNotEmpty(
'pnpm store path --silent',
'Could not get pnpm cache folder path'
)
},
yarn: {
name: 'yarn',
lockFilePatterns: ['yarn.lock'],
getCacheFolderPath: async projectDir => {
const yarnVersion = await getCommandOutputNotEmpty(
`yarn --version`,
'Could not retrieve version of yarn',
projectDir
);
core.debug(
`Consumed yarn version is ${yarnVersion} (working dir: "${
projectDir || ''
}")`
);
const stdOut = yarnVersion.startsWith('1.')
? await getCommandOutput('yarn cache dir', projectDir)
: await getCommandOutput('yarn config get cacheFolder', projectDir);
if (!stdOut) {
throw new Error(
`Could not get yarn cache folder path for ${projectDir}`
);
}
return stdOut;
}
}
};
export const getCommandOutput = async (
toolCommand: string,
cwd?: string
): Promise<string> => {
let {stdout, stderr, exitCode} = await exec.getExecOutput(
toolCommand,
undefined,
{ignoreReturnCode: true, ...(cwd && {cwd})}
);
if (exitCode) {
stderr = !stderr.trim()
? `The '${toolCommand}' command failed with exit code: ${exitCode}`
: stderr;
throw new Error(stderr);
}
return stdout.trim();
};
export const getCommandOutputNotEmpty = async (
toolCommand: string,
error: string,
cwd?: string
): Promise<string> => {
const stdOut = getCommandOutput(toolCommand, cwd);
if (!stdOut) {
throw new Error(error);
}
return stdOut;
};
export const getPackageManagerInfo = async (packageManager: string) => {
if (packageManager === 'npm') {
return supportedPackageManagers.npm;
} else if (packageManager === 'pnpm') {
return supportedPackageManagers.pnpm;
} else if (packageManager === 'yarn') {
return supportedPackageManagers.yarn;
} else {
return null;
}
};
/**
* getProjectDirectoriesFromCacheDependencyPath is called twice during `restoreCache`
* - first through `getCacheDirectories`
* - second from `repoHasYarn3ManagedCache`
*
* it contains expensive IO operation and thus should be memoized
*/
let projectDirectoriesMemoized: string[] | null = null;
/**
* unit test must reset memoized variables
*/
export const resetProjectDirectoriesMemoized = () =>
(projectDirectoriesMemoized = null);
/**
* Expands (converts) the string input `cache-dependency-path` to list of directories that
* may be project roots
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return list of directories and possible
*/
const getProjectDirectoriesFromCacheDependencyPath = async (
cacheDependencyPath: string
): Promise<string[]> => {
if (projectDirectoriesMemoized !== null) {
return projectDirectoriesMemoized;
}
const globber = await glob.create(cacheDependencyPath);
const cacheDependenciesPaths = await globber.glob();
const existingDirectories: string[] = cacheDependenciesPaths
.map(path.dirname)
.filter(unique())
.map(dirName => fs.realpathSync(dirName))
.filter(directory => fs.lstatSync(directory).isDirectory());
if (!existingDirectories.length)
core.warning(
`No existing directories found containing cache-dependency-path="${cacheDependencyPath}"`
);
projectDirectoriesMemoized = existingDirectories;
return existingDirectories;
};
/**
* Finds the cache directories configured for the repo if cache-dependency-path is not empty
* @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return list of files on which the cache depends
*/
const getCacheDirectoriesFromCacheDependencyPath = async (
packageManagerInfo: PackageManagerInfo,
cacheDependencyPath: string
): Promise<string[]> => {
const projectDirectories = await getProjectDirectoriesFromCacheDependencyPath(
cacheDependencyPath
);
const cacheFoldersPaths = await Promise.all(
projectDirectories.map(async projectDirectory => {
const cacheFolderPath = await packageManagerInfo.getCacheFolderPath(
projectDirectory
);
core.debug(
`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`
);
return cacheFolderPath;
})
);
// uniq in order to do not cache the same directories twice
return cacheFoldersPaths.filter(unique());
};
/**
* Finds the cache directories configured for the repo ignoring cache-dependency-path
* @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
* @return list of files on which the cache depends
*/
const getCacheDirectoriesForRootProject = async (
packageManagerInfo: PackageManagerInfo
): Promise<string[]> => {
const cacheFolderPath = await packageManagerInfo.getCacheFolderPath();
core.debug(
`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the root directory`
);
return [cacheFolderPath];
};
/**
* A function to find the cache directories configured for the repo
* currently it handles only the case of PM=yarn && cacheDependencyPath is not empty
* @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return list of files on which the cache depends
*/
export const getCacheDirectories = async (
packageManagerInfo: PackageManagerInfo,
cacheDependencyPath: string
): Promise<string[]> => {
// For yarn, if cacheDependencyPath is set, ask information about cache folders in each project
// folder satisfied by cacheDependencyPath https://github.com/actions/setup-node/issues/488
if (packageManagerInfo.name === 'yarn' && cacheDependencyPath) {
return getCacheDirectoriesFromCacheDependencyPath(
packageManagerInfo,
cacheDependencyPath
);
}
return getCacheDirectoriesForRootProject(packageManagerInfo);
};
/**
* A function to check if the directory is a yarn project configured to manage
* obsolete dependencies in the local cache
* @param directory - a path to the folder
* @return - true if the directory's project is yarn managed
* - if there's .yarn/cache folder do not mess with the dependencies kept in the repo, return false
* - global cache is not managed by yarn @see https://yarnpkg.com/features/offline-cache, return false
* - if local cache is not explicitly enabled (not yarn3), return false
* - return true otherwise
*/
const projectHasYarnBerryManagedDependencies = async (
directory: string
): Promise<boolean> => {
const workDir = directory || process.env.GITHUB_WORKSPACE || '.';
core.debug(`check if "${workDir}" has locally managed yarn3 dependencies`);
// if .yarn/cache directory exists the cache is managed by version control system
const yarnCacheFile = path.join(workDir, '.yarn', 'cache');
if (
fs.existsSync(yarnCacheFile) &&
fs.lstatSync(yarnCacheFile).isDirectory()
) {
core.debug(
`"${workDir}" has .yarn/cache - dependencies are kept in the repository`
);
return Promise.resolve(false);
}
// NOTE: yarn1 returns 'undefined' with return code = 0
const enableGlobalCache = await getCommandOutput(
'yarn config get enableGlobalCache',
workDir
);
// only local cache is not managed by yarn
const managed = enableGlobalCache.includes('false');
if (managed) {
core.debug(`"${workDir}" dependencies are managed by yarn 3 locally`);
return true;
} else {
core.debug(`"${workDir}" dependencies are not managed by yarn 3 locally`);
return false;
}
};
/**
* A function to report the repo contains Yarn managed projects
* @param packageManagerInfo - used to make sure current package manager is yarn
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return - true if all project directories configured to be Yarn managed
*/
export const repoHasYarnBerryManagedDependencies = async (
packageManagerInfo: PackageManagerInfo,
cacheDependencyPath: string
): Promise<boolean> => {
if (packageManagerInfo.name !== 'yarn') return false;
const yarnDirs = cacheDependencyPath
? await getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath)
: [''];
const isManagedList = await Promise.all(
yarnDirs.map(projectHasYarnBerryManagedDependencies)
);
return isManagedList.every(Boolean);
};
export function isGhes(): boolean {
const ghUrl = new URL(
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
);
const hostname = ghUrl.hostname.trimEnd().toUpperCase();
const isGitHubHost = hostname === 'GITHUB.COM';
const isGitHubEnterpriseCloudHost = hostname.endsWith('.GHE.COM');
const isLocalHost = hostname.endsWith('.LOCALHOST');
return !isGitHubHost && !isGitHubEnterpriseCloudHost && !isLocalHost;
}
export function isCacheFeatureAvailable(): boolean {
if (cache.isFeatureAvailable()) return true;
if (isGhes()) {
core.warning(
'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.'
);
return false;
}
core.warning(
'The runner was not able to contact the cache service. Caching will be skipped'
);
return false;
}
+16
View File
@@ -0,0 +1,16 @@
export enum LockType {
Npm = 'npm',
Pnpm = 'pnpm',
Yarn = 'yarn'
}
export enum State {
CachePackageManager = 'SETUP_NODE_CACHE_PACKAGE_MANAGER',
CachePrimaryKey = 'CACHE_KEY',
CacheMatchedKey = 'CACHE_RESULT',
CachePaths = 'CACHE_PATHS'
}
export enum Outputs {
CacheHit = 'cache-hit'
}
@@ -0,0 +1,53 @@
import * as tc from '@actions/tool-cache';
import semver from 'semver';
import BaseDistribution from './base-distribution';
import {NodeInputs} from './base-models';
export default abstract class BasePrereleaseNodejs extends BaseDistribution {
protected abstract distribution: string;
constructor(nodeInfo: NodeInputs) {
super(nodeInfo);
}
protected findVersionInHostedToolCacheDirectory(): string {
let toolPath = '';
const localVersionPaths = tc
.findAllVersions('node', this.nodeInfo.arch)
.filter(i => {
const prerelease = semver.prerelease(i, {});
if (!prerelease) {
return false;
}
return prerelease[0].toString().includes(this.distribution);
});
localVersionPaths.sort(semver.rcompare);
const localVersion = this.evaluateVersions(localVersionPaths);
if (localVersion) {
toolPath = tc.find('node', localVersion, this.nodeInfo.arch);
}
return toolPath;
}
protected validRange(versionSpec: string) {
let range: string;
const [raw, prerelease] = this.splitVersionSpec(versionSpec);
const isValidVersion = semver.valid(raw);
const rawVersion = (isValidVersion ? raw : semver.coerce(raw))!;
if (prerelease !== this.distribution) {
range = versionSpec;
} else {
range = `${semver.validRange(`^${rawVersion}-${this.distribution}`)}-0`;
}
return {range, options: {includePrerelease: !isValidVersion}};
}
protected splitVersionSpec(versionSpec: string) {
return versionSpec.split(/-(.*)/s);
}
}
+345
View File
@@ -0,0 +1,345 @@
import {v4 as uuidv4} from 'uuid';
import * as tc from '@actions/tool-cache';
import * as hc from '@actions/http-client';
import * as core from '@actions/core';
import * as io from '@actions/io';
import semver from 'semver';
import * as assert from 'assert';
import * as path from 'path';
import os from 'os';
import fs from 'fs';
import {NodeInputs, INodeVersion, INodeVersionInfo} from './base-models';
export default abstract class BaseDistribution {
protected httpClient: hc.HttpClient;
protected osPlat = os.platform();
constructor(protected nodeInfo: NodeInputs) {
this.httpClient = new hc.HttpClient('setup-node', [], {
allowRetries: true,
maxRetries: 3
});
}
protected abstract getDistributionUrl(mirror: string): string;
public async setupNodeJs() {
let nodeJsVersions: INodeVersion[] | undefined;
if (this.nodeInfo.checkLatest) {
const evaluatedVersion = await this.findVersionInDist(nodeJsVersions);
this.nodeInfo.versionSpec = evaluatedVersion;
}
let toolPath = this.findVersionInHostedToolCacheDirectory();
if (toolPath) {
core.info(`Found in cache @ ${toolPath}`);
} else {
const evaluatedVersion = await this.findVersionInDist(nodeJsVersions);
const toolName = this.getNodejsDistInfo(evaluatedVersion);
toolPath = await this.downloadNodejs(toolName);
}
if (this.osPlat != 'win32') {
toolPath = path.join(toolPath, 'bin');
}
core.addPath(toolPath);
}
protected async findVersionInDist(nodeJsVersions?: INodeVersion[]) {
if (!nodeJsVersions) {
nodeJsVersions = await this.getNodeJsVersions();
}
const versions = this.filterVersions(nodeJsVersions);
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}.`
);
}
return evaluatedVersion;
}
protected evaluateVersions(versions: string[]): string {
let version = '';
const {range, options} = this.validRange(this.nodeInfo.versionSpec);
core.debug(`evaluating ${versions.length} versions`);
for (const potential of versions) {
const satisfied: boolean = semver.satisfies(potential, range, options);
if (satisfied) {
version = potential;
break;
}
}
if (version) {
core.debug(`matched: ${version}`);
} else {
core.debug('match not found');
}
return version;
}
protected findVersionInHostedToolCacheDirectory() {
return tc.find(
'node',
this.nodeInfo.versionSpec,
this.translateArchToDistUrl(this.nodeInfo.arch)
);
}
protected async getNodeJsVersions(): Promise<INodeVersion[]> {
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
const dataUrl = `${initialUrl}/index.json`;
const headers = {};
if (this.nodeInfo.mirrorToken) {
headers['Authorization'] = `Bearer ${this.nodeInfo.mirrorToken}`;
}
const response = await this.httpClient.getJson<INodeVersion[]>(
dataUrl,
headers
);
return response.result || [];
}
protected getNodejsDistInfo(version: string) {
const osArch: string = this.translateArchToDistUrl(this.nodeInfo.arch);
version = semver.clean(version) || '';
const fileName: string =
this.osPlat == 'win32'
? `node-v${version}-win-${osArch}`
: `node-v${version}-${this.osPlat}-${osArch}`;
const urlFileName: string =
this.osPlat == 'win32'
? this.nodeInfo.arch === 'arm64'
? `${fileName}.zip`
: `${fileName}.7z`
: `${fileName}.tar.gz`;
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
const url = `${initialUrl}/v${version}/${urlFileName}`;
return <INodeVersionInfo>{
downloadUrl: url,
resolvedVersion: version,
arch: osArch,
fileName: fileName
};
}
protected async downloadNodejs(info: INodeVersionInfo) {
let downloadPath = '';
core.info(
`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`
);
try {
downloadPath = await tc.downloadTool(
info.downloadUrl,
undefined,
this.nodeInfo.mirrorToken
);
} catch (err) {
if (
err instanceof tc.HTTPError &&
err.httpStatusCode == 404 &&
this.osPlat == 'win32'
) {
return await this.acquireWindowsNodeFromFallbackLocation(
info.resolvedVersion,
info.arch
);
}
throw err;
}
const toolPath = await this.extractArchive(downloadPath, info, true);
core.info('Done');
return toolPath;
}
protected validRange(versionSpec: string) {
let options: semver.RangeOptions | undefined;
const c = semver.clean(versionSpec) || '';
const valid = semver.valid(c) ?? versionSpec;
return {range: valid, options};
}
protected async acquireWindowsNodeFromFallbackLocation(
version: string,
arch: string = os.arch()
): Promise<string> {
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
const osArch: string = this.translateArchToDistUrl(arch);
// Create temporary folder to download to
const tempDownloadFolder = `temp_${uuidv4()}`;
const tempDirectory = process.env['RUNNER_TEMP'] || '';
assert.ok(tempDirectory, 'Expected RUNNER_TEMP to be defined');
const tempDir: string = path.join(tempDirectory, tempDownloadFolder);
await io.mkdirP(tempDir);
let exeUrl: string;
let libUrl: string;
try {
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 = await tc.downloadTool(
exeUrl,
undefined,
this.nodeInfo.mirrorToken
);
await io.cp(exePath, path.join(tempDir, 'node.exe'));
const libPath = await tc.downloadTool(
libUrl,
undefined,
this.nodeInfo.mirrorToken
);
await io.cp(libPath, path.join(tempDir, 'node.lib'));
} catch (err) {
if (err instanceof tc.HTTPError && err.httpStatusCode == 404) {
exeUrl = `${initialUrl}/v${version}/node.exe`;
libUrl = `${initialUrl}/v${version}/node.lib`;
const exePath = await tc.downloadTool(
exeUrl,
undefined,
this.nodeInfo.mirrorToken
);
await io.cp(exePath, path.join(tempDir, 'node.exe'));
const libPath = await tc.downloadTool(
libUrl,
undefined,
this.nodeInfo.mirrorToken
);
await io.cp(libPath, path.join(tempDir, 'node.lib'));
} else {
throw err;
}
}
const toolPath = await tc.cacheDir(tempDir, 'node', version, arch);
return toolPath;
}
protected async extractArchive(
downloadPath: string,
info: INodeVersionInfo | null,
isOfficialArchive?: boolean
) {
//
// Extract
//
core.info('Extracting ...');
let extPath: string;
info = info || ({} as INodeVersionInfo); // satisfy compiler, never null when reaches here
if (this.osPlat == 'win32') {
const extension = this.nodeInfo.arch === 'arm64' ? '.zip' : '.7z';
// Rename archive to add extension because after downloading
// archive does not contain extension type and it leads to some issues
// on Windows runners without PowerShell Core.
//
// For default PowerShell Windows it should contain extension type to unpack it.
if (extension === '.zip' && isOfficialArchive) {
const renamedArchive = `${downloadPath}.zip`;
fs.renameSync(downloadPath, renamedArchive);
extPath = await tc.extractZip(renamedArchive);
} else {
const _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe');
extPath = await tc.extract7z(downloadPath, undefined, _7zPath);
}
// 7z extracts to folder matching file name
const nestedPath = path.join(
extPath,
path.basename(info.fileName, extension)
);
if (fs.existsSync(nestedPath)) {
extPath = nestedPath;
}
} else {
extPath = await tc.extractTar(downloadPath, undefined, [
'xz',
'--strip',
'1'
]);
}
//
// Install into the local tool cache - node extracts with a root folder that matches the fileName downloaded
//
core.info('Adding to the cache ...');
const toolPath = await tc.cacheDir(
extPath,
'node',
info.resolvedVersion,
info.arch
);
return toolPath;
}
protected getDistFileName(): string {
const osArch: string = this.translateArchToDistUrl(this.nodeInfo.arch);
// node offers a json list of versions
let dataFileName: string;
switch (this.osPlat) {
case 'linux':
dataFileName = `linux-${osArch}`;
break;
case 'darwin':
dataFileName = `osx-${osArch}-tar`;
break;
case 'win32':
if (this.nodeInfo.arch === 'arm64') {
dataFileName = `win-${osArch}-zip`;
} else {
dataFileName = `win-${osArch}-exe`;
}
break;
default:
throw new Error(`Unexpected OS '${this.osPlat}'`);
}
return dataFileName;
}
protected filterVersions(nodeJsVersions: INodeVersion[]) {
const versions: string[] = [];
const dataFileName = this.getDistFileName();
nodeJsVersions.forEach((nodeVersion: INodeVersion) => {
// ensure this version supports your os and platform
if (nodeVersion.files.indexOf(dataFileName) >= 0) {
versions.push(nodeVersion.version);
}
});
return versions.sort(semver.rcompare);
}
protected translateArchToDistUrl(arch: string): string {
switch (arch) {
case 'arm':
return 'armv7l';
default:
return arch;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
export interface NodeInputs {
versionSpec: string;
arch: string;
auth?: string;
checkLatest: boolean;
stable: boolean;
mirror: string;
mirrorToken: string;
}
export interface INodeVersionInfo {
downloadUrl: string;
resolvedVersion: string;
arch: string;
fileName: string;
}
export interface INodeVersion {
version: string;
files: string[];
}
+31
View File
@@ -0,0 +1,31 @@
import BaseDistribution from './base-distribution';
import {NodeInputs} from './base-models';
import NightlyNodejs from './nightly/nightly_builds';
import OfficialBuilds from './official_builds/official_builds';
import RcBuild from './rc/rc_builds';
import CanaryBuild from './v8-canary/canary_builds';
enum Distributions {
DEFAULT = '',
CANARY = 'v8-canary',
NIGHTLY = 'nightly',
RC = 'rc'
}
export function getNodejsDistribution(
installerOptions: NodeInputs
): BaseDistribution {
const versionSpec = installerOptions.versionSpec;
let distribution: BaseDistribution;
if (versionSpec.includes(Distributions.NIGHTLY)) {
distribution = new NightlyNodejs(installerOptions);
} else if (versionSpec.includes(Distributions.CANARY)) {
distribution = new CanaryBuild(installerOptions);
} else if (versionSpec.includes(Distributions.RC)) {
distribution = new RcBuild(installerOptions);
} else {
distribution = new OfficialBuilds(installerOptions);
}
return distribution;
}
@@ -0,0 +1,14 @@
import BasePrereleaseNodejs from '../base-distribution-prerelease';
import {NodeInputs} from '../base-models';
export default class NightlyNodejs extends BasePrereleaseNodejs {
protected distribution = 'nightly';
constructor(nodeInfo: NodeInputs) {
super(nodeInfo);
}
protected getDistributionUrl(mirror: string): string {
const url = mirror || 'https://nodejs.org';
return `${url}/download/nightly`;
}
}
@@ -0,0 +1,297 @@
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import path from 'path';
import BaseDistribution from '../base-distribution';
import {NodeInputs, INodeVersion, INodeVersionInfo} from '../base-models';
interface INodeRelease extends tc.IToolRelease {
lts?: string;
}
export default class OfficialBuilds extends BaseDistribution {
constructor(nodeInfo: NodeInputs) {
super(nodeInfo);
}
public async setupNodeJs() {
let manifest: tc.IToolRelease[] | undefined;
let nodeJsVersions: INodeVersion[] | undefined;
const osArch = this.translateArchToDistUrl(this.nodeInfo.arch);
if (this.isLtsAlias(this.nodeInfo.versionSpec)) {
core.info('Attempt to resolve LTS alias from manifest...');
// No try-catch since it's not possible to resolve LTS alias without manifest
manifest = await this.getManifest();
this.nodeInfo.versionSpec = this.resolveLtsAliasFromManifest(
this.nodeInfo.versionSpec,
this.nodeInfo.stable,
manifest
);
}
if (this.isLatestSyntax(this.nodeInfo.versionSpec)) {
nodeJsVersions = await this.getNodeJsVersions();
const versions = this.filterVersions(nodeJsVersions);
this.nodeInfo.versionSpec = this.evaluateVersions(versions);
core.info('getting latest node version...');
}
if (this.nodeInfo.checkLatest) {
core.info('Attempt to resolve the latest version from manifest...');
const resolvedVersion = await this.resolveVersionFromManifest(
this.nodeInfo.versionSpec,
this.nodeInfo.stable,
osArch,
manifest
);
if (resolvedVersion) {
this.nodeInfo.versionSpec = resolvedVersion;
core.info(`Resolved as '${resolvedVersion}'`);
} else {
core.info(
`Failed to resolve version ${this.nodeInfo.versionSpec} from manifest`
);
}
}
let toolPath = this.findVersionInHostedToolCacheDirectory();
if (toolPath) {
core.info(`Found in cache @ ${toolPath}`);
this.addToolPath(toolPath);
return;
}
let downloadPath = '';
try {
core.info(`Attempting to download ${this.nodeInfo.versionSpec}...`);
const versionInfo = await this.getInfoFromManifest(
this.nodeInfo.versionSpec,
this.nodeInfo.stable,
osArch,
manifest
);
if (versionInfo) {
core.info(
`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`
);
downloadPath = await tc.downloadTool(
versionInfo.downloadUrl,
undefined,
this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth
);
if (downloadPath) {
toolPath = await this.extractArchive(
downloadPath,
versionInfo,
false
);
}
} else {
core.info(
`Not found in manifest. Falling back to download directly from ${
this.nodeInfo.mirror || 'Node'
}`
);
}
} catch (err) {
// Rate limit?
if (
err instanceof tc.HTTPError &&
(err.httpStatusCode === 403 || err.httpStatusCode === 429)
) {
core.info(
`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`
);
} else {
core.info((err as Error).message);
}
core.debug((err as Error).stack ?? 'empty stack');
core.info('Falling back to download directly from Node');
}
if (!toolPath) {
toolPath = await this.downloadDirectlyFromNode();
}
if (this.osPlat != 'win32') {
toolPath = path.join(toolPath, 'bin');
}
core.addPath(toolPath);
}
protected addToolPath(toolPath: string) {
if (this.osPlat != 'win32') {
toolPath = path.join(toolPath, 'bin');
}
core.addPath(toolPath);
}
protected async downloadDirectlyFromNode() {
const nodeJsVersions = await this.getNodeJsVersions();
const versions = this.filterVersions(nodeJsVersions);
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);
try {
const toolPath = await this.downloadNodejs(toolName);
return toolPath;
} catch (error) {
if (error instanceof tc.HTTPError && error.httpStatusCode === 404) {
core.warning(
`Node version ${this.nodeInfo.versionSpec} for platform ${this.osPlat} and architecture ${this.nodeInfo.arch} was found but failed to download. ` +
'This usually happens when downloadable binaries are not fully updated at https://nodejs.org/. ' +
'To resolve this issue you may either fall back to the older version or try again later.'
);
}
throw error;
}
}
protected evaluateVersions(versions: string[]): string {
let version = '';
if (this.isLatestSyntax(this.nodeInfo.versionSpec)) {
core.info(`getting latest node version...`);
return versions[0];
}
version = super.evaluateVersions(versions);
return version;
}
protected getDistributionUrl(mirror: string): string {
const url = mirror || 'https://nodejs.org';
return `${url}/dist`;
}
private getManifest(): Promise<tc.IToolRelease[]> {
core.debug('Getting manifest from actions/node-versions@main');
return tc.getManifestFromRepo(
'actions',
'node-versions',
this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth,
'main'
);
}
private resolveLtsAliasFromManifest(
versionSpec: string,
stable: boolean,
manifest: INodeRelease[]
): string {
const alias = versionSpec.split('lts/')[1]?.toLowerCase();
if (!alias) {
throw new Error(
`Unable to parse LTS alias for Node version '${versionSpec}'`
);
}
core.debug(`LTS alias '${alias}' for Node version '${versionSpec}'`);
// Supported formats are `lts/<alias>`, `lts/*`, and `lts/-n`. Where asterisk means highest possible LTS and -n means the nth-highest.
const n = Number(alias);
const aliases = Object.fromEntries(
manifest
.filter(x => x.lts && x.stable === stable)
.map(x => [x.lts!.toLowerCase(), x])
.reverse()
);
const numbered = Object.values(aliases);
const release =
alias === '*'
? numbered[numbered.length - 1]
: n < 0
? numbered[numbered.length - 1 + n]
: aliases[alias];
if (!release) {
throw new Error(
`Unable to find LTS release '${alias}' for Node version '${versionSpec}'.`
);
}
core.debug(
`Found LTS release '${release.version}' for Node version '${versionSpec}'`
);
return release.version.split('.')[0];
}
private async resolveVersionFromManifest(
versionSpec: string,
stable: boolean,
osArch: string,
manifest: tc.IToolRelease[] | undefined
): Promise<string | undefined> {
try {
const info = await this.getInfoFromManifest(
versionSpec,
stable,
osArch,
manifest
);
return info?.resolvedVersion;
} catch (err) {
core.info('Unable to resolve version from manifest...');
core.debug((err as Error).message);
}
}
private async getInfoFromManifest(
versionSpec: string,
stable: boolean,
osArch: string,
manifest: tc.IToolRelease[] | undefined
): Promise<INodeVersionInfo | null> {
let info: INodeVersionInfo | null = null;
if (!manifest) {
core.debug('No manifest cached');
manifest = await this.getManifest();
}
const rel = await tc.findFromManifest(
versionSpec,
stable,
manifest,
osArch
);
if (rel && rel.files.length > 0) {
info = <INodeVersionInfo>{};
info.resolvedVersion = rel.version;
info.arch = rel.files[0].arch;
info.downloadUrl = rel.files[0].download_url;
info.fileName = rel.files[0].filename;
}
return info;
}
private isLtsAlias(versionSpec: string): boolean {
return versionSpec.startsWith('lts/');
}
private isLatestSyntax(versionSpec): boolean {
return ['current', 'latest', 'node'].includes(versionSpec);
}
}
+13
View File
@@ -0,0 +1,13 @@
import BaseDistribution from '../base-distribution';
import {NodeInputs} from '../base-models';
export default class RcBuild extends BaseDistribution {
constructor(nodeInfo: NodeInputs) {
super(nodeInfo);
}
getDistributionUrl(mirror: string): string {
const url = mirror || 'https://nodejs.org';
return `${url}/download/rc`;
}
}
@@ -0,0 +1,14 @@
import BasePrereleaseNodejs from '../base-distribution-prerelease';
import {NodeInputs} from '../base-models';
export default class CanaryBuild extends BasePrereleaseNodejs {
protected distribution = 'v8-canary';
constructor(nodeInfo: NodeInputs) {
super(nodeInfo);
}
protected getDistributionUrl(mirror: string): string {
const url = mirror || 'https://nodejs.org';
return `${url}/download/v8-canary`;
}
}
+119
View File
@@ -0,0 +1,119 @@
import * as core from '@actions/core';
import os from 'os';
import * as auth from './authutil';
import * as path from 'path';
import {restoreCache} from './cache-restore';
import {isCacheFeatureAvailable} from './cache-utils';
import {getNodejsDistribution} from './distributions/installer-factory';
import {getNodeVersionFromFile, printEnvDetailsAndSetOutput} from './util';
import {State} from './constants';
export async function run() {
try {
//
// Version is optional. If supplied, install / use from the tool cache
// If not supplied then task is still used to setup proxy, auth, etc...
//
const version = resolveVersionInput();
let arch = core.getInput('architecture');
const cache = core.getInput('cache');
// if architecture supplied but node-version is not
// if we don't throw a warning, the already installed x64 node will be used which is not probably what user meant.
if (arch && !version) {
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();
}
if (version) {
const token = core.getInput('token');
const auth = !token ? undefined : `token ${token}`;
const mirror = core.getInput('mirror');
const mirrorToken = core.getInput('mirror-token');
const stable =
(core.getInput('stable') || 'true').toUpperCase() === 'TRUE';
const checkLatest =
(core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE';
const nodejsInfo = {
versionSpec: version,
checkLatest,
auth,
stable,
arch,
mirror,
mirrorToken
};
const nodeDistribution = getNodejsDistribution(nodejsInfo);
await nodeDistribution.setupNodeJs();
}
await printEnvDetailsAndSetOutput();
const registryUrl: string = core.getInput('registry-url');
const alwaysAuth: string = core.getInput('always-auth');
if (registryUrl) {
auth.configAuthentication(registryUrl, alwaysAuth);
}
if (cache && isCacheFeatureAvailable()) {
core.saveState(State.CachePackageManager, cache);
const cacheDependencyPath = core.getInput('cache-dependency-path');
await restoreCache(cache, cacheDependencyPath);
}
const matchersPath = path.join(__dirname, '../..', '.github');
core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`);
core.info(
`##[add-matcher]${path.join(matchersPath, 'eslint-stylish.json')}`
);
core.info(
`##[add-matcher]${path.join(matchersPath, 'eslint-compact.json')}`
);
} catch (err) {
core.setFailed((err as Error).message);
}
}
function resolveVersionInput(): string {
let version = core.getInput('node-version');
const versionFileInput = core.getInput('node-version-file');
if (version && versionFileInput) {
core.warning(
'Both node-version and node-version-file inputs are specified, only node-version will be used'
);
}
if (version) {
return version;
}
if (versionFileInput) {
const versionFilePath = path.join(
process.env.GITHUB_WORKSPACE!,
versionFileInput
);
const parsedVersion = getNodeVersionFromFile(versionFilePath);
if (parsedVersion) {
version = parsedVersion;
} else {
core.warning(
`Could not determine node version from ${versionFilePath}. Falling back`
);
}
core.info(`Resolved ${versionFileInput} as ${version}`);
}
return version;
}
+3
View File
@@ -0,0 +1,3 @@
import {run} from './main';
run();
+108
View File
@@ -0,0 +1,108 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import fs from 'fs';
import path from 'path';
export function getNodeVersionFromFile(versionFilePath: string): string | null {
if (!fs.existsSync(versionFilePath)) {
throw new Error(
`The specified node version file at: ${versionFilePath} does not exist`
);
}
const contents = fs.readFileSync(versionFilePath, 'utf8');
// Try parsing the file as an NPM `package.json` file.
try {
const manifest = JSON.parse(contents);
// Presume package.json file.
if (typeof manifest === 'object' && !!manifest) {
// Support Volta.
// See https://docs.volta.sh/guide/understanding#managing-your-project
if (manifest.volta?.node) {
return manifest.volta.node;
}
if (manifest.engines?.node) {
return manifest.engines.node;
}
// Support Volta workspaces.
// See https://docs.volta.sh/advanced/workspaces
if (manifest.volta?.extends) {
const extendedFilePath = path.resolve(
path.dirname(versionFilePath),
manifest.volta.extends
);
core.info('Resolving node version from ' + extendedFilePath);
return getNodeVersionFromFile(extendedFilePath);
}
// If contents are an object, we parsed JSON
// this can happen if node-version-file is a package.json
// yet contains no volta.node or engines.node
//
// If node-version file is _not_ JSON, control flow
// will not have reached these lines.
//
// And because we've reached here, we know the contents
// *are* JSON, so no further string parsing makes sense.
return null;
}
} catch {
core.info('Node version file is not JSON file');
}
const found = contents.match(/^(?:node(js)?\s+)?v?(?<version>[^\s]+)$/m);
return found?.groups?.version ?? contents.trim();
}
export async function printEnvDetailsAndSetOutput() {
core.startGroup('Environment details');
const promises = ['node', 'npm', 'yarn'].map(async tool => {
const pathTool = await io.which(tool, false);
const output = pathTool ? 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 '';
}
}
export const unique = () => {
const encountered = new Set();
return (value: unknown): boolean => {
if (encountered.has(value)) return false;
encountered.add(value);
return true;
};
};