Merge 07c11e9db09b3ce2094ba3bd71cfc34540fe1402 into db0fb3a98ed314b69026b6e8c9be26a7e2fe65a8

This commit is contained in:
Ruben Bridgewater 2026-09-16 21:25:43 -04:00 committed by GitHub
commit 86f345fa55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 370 additions and 56 deletions

View File

@ -14,6 +14,9 @@ import osm from 'os';
import path from 'path'; import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const initialRunnerTemp = process.env['RUNNER_TEMP'];
const nodeVersionsManifestUrl =
'https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json';
// Mock @actions modules before importing anything that depends on them // Mock @actions modules before importing anything that depends on them
jest.unstable_mockModule('@actions/core', () => ({ jest.unstable_mockModule('@actions/core', () => ({
@ -164,6 +167,7 @@ describe('setup-node', () => {
console.log('::stop-commands::stoptoken'); // Disable executing of runner commands when running tests in actions 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_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 process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
delete process.env['RUNNER_TEMP'];
inputs = {}; inputs = {};
inSpy = core.getInput as jest.Mock; inSpy = core.getInput as jest.Mock;
inSpy.mockImplementation((name: any) => inputs[name]); inSpy.mockImplementation((name: any) => inputs[name]);
@ -233,7 +237,9 @@ describe('setup-node', () => {
getJsonSpy.mockImplementation((url: any) => { getJsonSpy.mockImplementation((url: any) => {
let res: any; let res: any;
if (url.includes('/rc')) { if (url === nodeVersionsManifestUrl) {
throw new Error('Unable to download raw manifest');
} else if (url.includes('/rc')) {
res = <INodeVersion[]>nodeTestDistRc; res = <INodeVersion[]>nodeTestDistRc;
} else if (url.includes('/nightly')) { } else if (url.includes('/nightly')) {
res = <INodeVersion[]>nodeTestDistNightly; res = <INodeVersion[]>nodeTestDistNightly;
@ -273,6 +279,9 @@ describe('setup-node', () => {
afterAll(async () => { afterAll(async () => {
console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions
if (initialRunnerTemp !== undefined) {
process.env['RUNNER_TEMP'] = initialRunnerTemp;
}
jest.restoreAllMocks(); jest.restoreAllMocks();
}, 100000); }, 100000);
@ -649,7 +658,7 @@ describe('setup-node', () => {
expect(logSpy).toHaveBeenCalledWith( expect(logSpy).toHaveBeenCalledWith(
'Attempt to resolve the latest version from manifest...' 'Attempt to resolve the latest version from manifest...'
); );
expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached');
expect(dbgSpy).toHaveBeenCalledWith( expect(dbgSpy).toHaveBeenCalledWith(
'Getting manifest from actions/node-versions@main' 'Getting manifest from actions/node-versions@main'
); );
@ -677,7 +686,7 @@ describe('setup-node', () => {
expect(logSpy).toHaveBeenCalledWith( expect(logSpy).toHaveBeenCalledWith(
'Attempt to resolve the latest version from manifest...' 'Attempt to resolve the latest version from manifest...'
); );
expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached');
expect(dbgSpy).toHaveBeenCalledWith( expect(dbgSpy).toHaveBeenCalledWith(
'Getting manifest from actions/node-versions@main' 'Getting manifest from actions/node-versions@main'
); );
@ -716,7 +725,7 @@ describe('setup-node', () => {
expect(logSpy).toHaveBeenCalledWith( expect(logSpy).toHaveBeenCalledWith(
'Attempt to resolve the latest version from manifest...' 'Attempt to resolve the latest version from manifest...'
); );
expect(dbgSpy).toHaveBeenCalledWith('No manifest cached'); expect(dbgSpy).not.toHaveBeenCalledWith('No manifest cached');
expect(dbgSpy).toHaveBeenCalledWith( expect(dbgSpy).toHaveBeenCalledWith(
'Getting manifest from actions/node-versions@main' 'Getting manifest from actions/node-versions@main'
); );
@ -1089,6 +1098,166 @@ describe('setup-node', () => {
}, 10000); }, 10000);
}); });
describe('manifest retrieval', () => {
let runnerTemp: string;
beforeEach(() => {
runnerTemp = fs.mkdtempSync(path.join(osm.tmpdir(), 'setup-node-test-'));
process.env['RUNNER_TEMP'] = runnerTemp;
os.platform = 'linux';
os.arch = 'x64';
inputs['node-version'] = 'lts/erbium';
findSpy.mockImplementation(() => '');
getJsonSpy.mockImplementation((url: string) => ({
result:
url === nodeVersionsManifestUrl ? nodeTestManifest : nodeTestDist
}));
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/node/12.16.2/x64');
exSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
getExecOutputSpy.mockImplementation(async () => ({
stdout: 'v12.16.2\n',
stderr: '',
exitCode: 0
}));
});
afterEach(() => {
fs.rmSync(runnerTemp, {recursive: true, force: true});
delete process.env['RUNNER_TEMP'];
});
it('fetches the raw manifest without a GitHub API call', async () => {
await main.run();
expect(getJsonSpy).toHaveBeenCalledWith(nodeVersionsManifestUrl);
expect(getManifestSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('reuses the manifest across action invocations', async () => {
await main.run();
getJsonSpy.mockClear();
getManifestSpy.mockClear();
await main.run();
expect(getJsonSpy).not.toHaveBeenCalled();
expect(getManifestSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('refreshes the manifest when check-latest is enabled', async () => {
let installedVersion = '12.16.2';
let manifest = <IToolRelease[]>nodeTestManifest;
getJsonSpy.mockImplementation((url: string) => ({
result: url === nodeVersionsManifestUrl ? manifest : nodeTestDist
}));
cacheSpy.mockImplementation(
async (
_sourceDirectory: string,
_tool: string,
version: string,
arch: string
) => {
installedVersion = version;
return path.normalize(`/cache/node/${version}/${arch}`);
}
);
getExecOutputSpy.mockImplementation(async () => ({
stdout: `v${installedVersion}\n`,
stderr: '',
exitCode: 0
}));
inputs['node-version'] = '12';
await main.run();
const latestVersion = '12.17.0';
manifest = [
{
version: latestVersion,
stable: true,
release_url: `https://github.com/actions/node-versions/releases/tag/${latestVersion}`,
files: [
{
filename: `node-${latestVersion}-linux-x64.tar.gz`,
arch: 'x64',
platform: 'linux',
download_url: `https://github.com/actions/node-versions/releases/download/${latestVersion}/node-${latestVersion}-linux-x64.tar.gz`
}
]
},
...nodeTestManifest
];
inputs['check-latest'] = 'true';
await main.run();
expect(getJsonSpy).toHaveBeenCalledTimes(2);
expect(logSpy).toHaveBeenCalledWith(`Resolved as '${latestVersion}'`);
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('reuses the LTS manifest when check-latest is enabled', async () => {
inputs['check-latest'] = 'true';
await main.run();
expect(getJsonSpy).toHaveBeenCalledTimes(1);
expect(getManifestSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it.each([
['empty', '[]'],
['malformed', '{']
])('replaces an %s cached manifest', async (_, cachedManifest) => {
await main.run();
const [manifestFile] = fs.readdirSync(runnerTemp);
fs.writeFileSync(path.join(runnerTemp, manifestFile), cachedManifest);
getJsonSpy.mockClear();
getManifestSpy.mockClear();
await main.run();
expect(getJsonSpy).toHaveBeenCalledWith(nodeVersionsManifestUrl);
expect(getManifestSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
getJsonSpy.mockClear();
await main.run();
expect(getJsonSpy).not.toHaveBeenCalled();
});
it('continues when the manifest cannot be cached', async () => {
fs.rmSync(runnerTemp, {recursive: true, force: true});
await main.run();
expect(getJsonSpy).toHaveBeenCalledWith(nodeVersionsManifestUrl);
expect(getManifestSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it.each([
['fails', () => Promise.reject(new Error('Unable to download manifest'))],
['is invalid', () => Promise.resolve({result: []})]
])(
'falls back to the GitHub API when the raw manifest %s',
async (_, result) => {
getJsonSpy.mockImplementationOnce(result);
await main.run();
expect(getJsonSpy).toHaveBeenCalledWith(nodeVersionsManifestUrl);
expect(getManifestSpy).toHaveBeenCalledTimes(1);
expect(setFailedSpy).not.toHaveBeenCalled();
}
);
});
describe('node version verification', () => { describe('node version verification', () => {
beforeEach(() => { beforeEach(() => {
os.platform = 'linux'; os.platform = 'linux';

105
dist/setup/index.js vendored
View File

@ -85937,6 +85937,7 @@ class Batch {
//# sourceMappingURL=Batch.js.map //# sourceMappingURL=Batch.js.map
;// CONCATENATED MODULE: external "node:fs" ;// CONCATENATED MODULE: external "node:fs"
const external_node_fs_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:fs"); const external_node_fs_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:fs");
var external_node_fs_default = /*#__PURE__*/__nccwpck_require__.n(external_node_fs_namespaceObject);
;// CONCATENATED MODULE: ./node_modules/@azure/storage-blob/dist/esm/utils/utils.js ;// CONCATENATED MODULE: ./node_modules/@azure/storage-blob/dist/esm/utils/utils.js
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
@ -100979,12 +100980,23 @@ class NightlyNodejs extends BasePrereleaseNodejs {
} }
} }
;// CONCATENATED MODULE: external "node:path"
const external_node_path_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:path");
var external_node_path_default = /*#__PURE__*/__nccwpck_require__.n(external_node_path_namespaceObject);
;// CONCATENATED MODULE: ./src/distributions/official_builds/official_builds.ts ;// CONCATENATED MODULE: ./src/distributions/official_builds/official_builds.ts
const nodeVersionsManifestFile = 'setup-node-versions-manifest.json';
const nodeVersionsManifestUrl = 'https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json';
const invalidManifestMessage = 'The manifest fetched is empty, truncated, or does not contain any valid tool release entries.';
/** @param {unknown} manifest */
function isValidManifest(manifest) {
return Array.isArray(manifest) && manifest.length > 0;
}
class OfficialBuilds extends BaseDistribution { class OfficialBuilds extends BaseDistribution {
constructor(nodeInfo) { constructor(nodeInfo) {
super(nodeInfo); super(nodeInfo);
@ -101007,7 +101019,16 @@ class OfficialBuilds extends BaseDistribution {
} }
if (this.nodeInfo.checkLatest) { if (this.nodeInfo.checkLatest) {
core_info('Attempt to resolve the latest version from manifest...'); core_info('Attempt to resolve the latest version from manifest...');
const resolvedVersion = await this.resolveVersionFromManifest(this.nodeInfo.versionSpec, this.nodeInfo.stable, osArch, manifest); let resolvedVersion;
try {
manifest ??= await this.getManifest();
const info = await this.getInfoFromManifest(this.nodeInfo.versionSpec, this.nodeInfo.stable, osArch, manifest);
resolvedVersion = info?.resolvedVersion;
}
catch (error) {
core_info('Unable to resolve version from manifest...');
core_debug(error.message);
}
if (resolvedVersion) { if (resolvedVersion) {
this.nodeInfo.versionSpec = resolvedVersion; this.nodeInfo.versionSpec = resolvedVersion;
core_info(`Resolved as '${resolvedVersion}'`); core_info(`Resolved as '${resolvedVersion}'`);
@ -101058,14 +101079,14 @@ class OfficialBuilds extends BaseDistribution {
} }
const installedDir = toolPath; const installedDir = toolPath;
if (this.osPlat != 'win32') { if (this.osPlat != 'win32') {
toolPath = external_path_default().join(toolPath, 'bin'); toolPath = external_node_path_default().join(toolPath, 'bin');
} }
addPath(toolPath); addPath(toolPath);
await this.verifyNodeVersion(installedDir); await this.verifyNodeVersion(installedDir);
} }
addToolPath(toolPath) { addToolPath(toolPath) {
if (this.osPlat != 'win32') { if (this.osPlat != 'win32') {
toolPath = external_path_default().join(toolPath, 'bin'); toolPath = external_node_path_default().join(toolPath, 'bin');
} }
addPath(toolPath); addPath(toolPath);
} }
@ -101104,6 +101125,28 @@ class OfficialBuilds extends BaseDistribution {
return `${url}/dist`; return `${url}/dist`;
} }
async getManifest() { async getManifest() {
const runnerTemp = process.env['RUNNER_TEMP'];
const manifestPath = runnerTemp
? external_node_path_default().join(runnerTemp, nodeVersionsManifestFile)
: undefined;
const cachedManifest = this.nodeInfo.checkLatest
? undefined
: this.getCachedManifest(manifestPath);
if (cachedManifest) {
return cachedManifest;
}
core_debug(`Getting manifest from ${nodeVersionsManifestUrl}`);
try {
const { result } = await this.httpClient.getJson(nodeVersionsManifestUrl);
if (!isValidManifest(result)) {
throw new Error(invalidManifestMessage);
}
this.cacheManifest(manifestPath, result);
return result;
}
catch (error) {
core_debug(`Unable to get manifest from ${nodeVersionsManifestUrl}: ${error instanceof Error ? error.message : String(error)}`);
}
let lastError; let lastError;
const maxAttempts = 3; const maxAttempts = 3;
core_debug(`Getting manifest from actions/node-versions@main`); core_debug(`Getting manifest from actions/node-versions@main`);
@ -101112,13 +101155,14 @@ class OfficialBuilds extends BaseDistribution {
const manifest = await getManifestFromRepo('actions', 'node-versions', this.nodeInfo.mirror && this.nodeInfo.mirrorToken const manifest = await getManifestFromRepo('actions', 'node-versions', this.nodeInfo.mirror && this.nodeInfo.mirrorToken
? this.nodeInfo.mirrorToken ? this.nodeInfo.mirrorToken
: this.nodeInfo.auth, 'main'); : this.nodeInfo.auth, 'main');
if (Array.isArray(manifest) && manifest.length > 0) { if (isValidManifest(manifest)) {
this.cacheManifest(manifestPath, manifest);
return manifest; return manifest;
} }
lastError = new Error(`The manifest fetched is empty, truncated, or does not contain any valid tool release entries.`); lastError = new Error(invalidManifestMessage);
} }
catch (err) { catch (error) {
lastError = err instanceof Error ? err : new Error(String(err)); lastError = error instanceof Error ? error : new Error(String(error));
} }
core_debug(`Attempt ${attempt}/${maxAttempts} to fetch the manifest failed: ${lastError.message}`); core_debug(`Attempt ${attempt}/${maxAttempts} to fetch the manifest failed: ${lastError.message}`);
if (attempt < maxAttempts) { if (attempt < maxAttempts) {
@ -101128,6 +101172,41 @@ class OfficialBuilds extends BaseDistribution {
} }
throw new Error(`Failed to fetch a valid manifest after ${maxAttempts} attempts. Last error: ${lastError?.message}`); throw new Error(`Failed to fetch a valid manifest after ${maxAttempts} attempts. Last error: ${lastError?.message}`);
} }
/** @param {string | undefined} manifestPath */
getCachedManifest(manifestPath) {
if (!manifestPath) {
return undefined;
}
try {
const manifest = JSON.parse(external_node_fs_default().readFileSync(manifestPath, 'utf8'));
if (isValidManifest(manifest)) {
core_debug(`Found manifest in ${manifestPath}`);
return manifest;
}
core_debug(`Ignoring invalid manifest in ${manifestPath}`);
}
catch (error) {
if (error.code !== 'ENOENT') {
core_debug(`Unable to read manifest from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
}
}
return undefined;
}
/**
* @param {string | undefined} manifestPath
* @param {tc.IToolRelease[]} manifest
*/
cacheManifest(manifestPath, manifest) {
if (!manifestPath) {
return;
}
try {
external_node_fs_default().writeFileSync(manifestPath, JSON.stringify(manifest));
}
catch (error) {
core_debug(`Unable to cache manifest in ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
}
}
resolveLtsAliasFromManifest(versionSpec, stable, manifest) { resolveLtsAliasFromManifest(versionSpec, stable, manifest) {
const alias = versionSpec.split('lts/')[1]?.toLowerCase(); const alias = versionSpec.split('lts/')[1]?.toLowerCase();
if (!alias) { if (!alias) {
@ -101152,16 +101231,6 @@ class OfficialBuilds extends BaseDistribution {
core_debug(`Found LTS release '${release.version}' for Node version '${versionSpec}'`); core_debug(`Found LTS release '${release.version}' for Node version '${versionSpec}'`);
return release.version.split('.')[0]; return release.version.split('.')[0];
} }
async resolveVersionFromManifest(versionSpec, stable, osArch, manifest) {
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.message);
}
}
async getInfoFromManifest(versionSpec, stable, osArch, manifest) { async getInfoFromManifest(versionSpec, stable, osArch, manifest) {
let info = null; let info = null;
if (!manifest) { if (!manifest) {
@ -101186,7 +101255,7 @@ class OfficialBuilds extends BaseDistribution {
} }
async verifyNodeVersion(installedDir) { async verifyNodeVersion(installedDir) {
// tool-cache layout: <root>/node/<version>/<arch> // tool-cache layout: <root>/node/<version>/<arch>
const expectedVersion = 'v' + external_path_default().basename(external_path_default().dirname(installedDir)); const expectedVersion = 'v' + external_node_path_default().basename(external_node_path_default().dirname(installedDir));
let actualVersion = ''; let actualVersion = '';
try { try {
const { stdout } = await getExecOutput('node', ['--version'], { const { stdout } = await getExecOutput('node', ['--version'], {

View File

@ -1,7 +1,9 @@
import fs from 'node:fs';
import path from 'node:path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import path from 'path';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as tc from '@actions/tool-cache';
import BaseDistribution from '../base-distribution.js'; import BaseDistribution from '../base-distribution.js';
import {NodeInputs, INodeVersion, INodeVersionInfo} from '../base-models.js'; import {NodeInputs, INodeVersion, INodeVersionInfo} from '../base-models.js';
@ -10,6 +12,17 @@ interface INodeRelease extends tc.IToolRelease {
lts?: string; lts?: string;
} }
const nodeVersionsManifestFile = 'setup-node-versions-manifest.json';
const nodeVersionsManifestUrl =
'https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json';
const invalidManifestMessage =
'The manifest fetched is empty, truncated, or does not contain any valid tool release entries.';
/** @param {unknown} manifest */
function isValidManifest(manifest: unknown): manifest is tc.IToolRelease[] {
return Array.isArray(manifest) && manifest.length > 0;
}
export default class OfficialBuilds extends BaseDistribution { export default class OfficialBuilds extends BaseDistribution {
constructor(nodeInfo: NodeInputs) { constructor(nodeInfo: NodeInputs) {
super(nodeInfo); super(nodeInfo);
@ -43,12 +56,20 @@ export default class OfficialBuilds extends BaseDistribution {
if (this.nodeInfo.checkLatest) { if (this.nodeInfo.checkLatest) {
core.info('Attempt to resolve the latest version from manifest...'); core.info('Attempt to resolve the latest version from manifest...');
const resolvedVersion = await this.resolveVersionFromManifest( let resolvedVersion: string | undefined;
try {
manifest ??= await this.getManifest();
const info = await this.getInfoFromManifest(
this.nodeInfo.versionSpec, this.nodeInfo.versionSpec,
this.nodeInfo.stable, this.nodeInfo.stable,
osArch, osArch,
manifest manifest
); );
resolvedVersion = info?.resolvedVersion;
} catch (error) {
core.info('Unable to resolve version from manifest...');
core.debug((error as Error).message);
}
if (resolvedVersion) { if (resolvedVersion) {
this.nodeInfo.versionSpec = resolvedVersion; this.nodeInfo.versionSpec = resolvedVersion;
core.info(`Resolved as '${resolvedVersion}'`); core.info(`Resolved as '${resolvedVersion}'`);
@ -191,6 +212,33 @@ export default class OfficialBuilds extends BaseDistribution {
} }
private async getManifest(): Promise<tc.IToolRelease[]> { private async getManifest(): Promise<tc.IToolRelease[]> {
const runnerTemp = process.env['RUNNER_TEMP'];
const manifestPath = runnerTemp
? path.join(runnerTemp, nodeVersionsManifestFile)
: undefined;
const cachedManifest = this.nodeInfo.checkLatest
? undefined
: this.getCachedManifest(manifestPath);
if (cachedManifest) {
return cachedManifest;
}
core.debug(`Getting manifest from ${nodeVersionsManifestUrl}`);
try {
const {result} = await this.httpClient.getJson<tc.IToolRelease[]>(
nodeVersionsManifestUrl
);
if (!isValidManifest(result)) {
throw new Error(invalidManifestMessage);
}
this.cacheManifest(manifestPath, result);
return result;
} catch (error) {
core.debug(
`Unable to get manifest from ${nodeVersionsManifestUrl}: ${error instanceof Error ? error.message : String(error)}`
);
}
let lastError: Error | undefined; let lastError: Error | undefined;
const maxAttempts = 3; const maxAttempts = 3;
core.debug(`Getting manifest from actions/node-versions@main`); core.debug(`Getting manifest from actions/node-versions@main`);
@ -204,14 +252,13 @@ export default class OfficialBuilds extends BaseDistribution {
: this.nodeInfo.auth, : this.nodeInfo.auth,
'main' 'main'
); );
if (Array.isArray(manifest) && manifest.length > 0) { if (isValidManifest(manifest)) {
this.cacheManifest(manifestPath, manifest);
return manifest; return manifest;
} }
lastError = new Error( lastError = new Error(invalidManifestMessage);
`The manifest fetched is empty, truncated, or does not contain any valid tool release entries.` } catch (error) {
); lastError = error instanceof Error ? error : new Error(String(error));
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
} }
core.debug( core.debug(
`Attempt ${attempt}/${maxAttempts} to fetch the manifest failed: ${lastError.message}` `Attempt ${attempt}/${maxAttempts} to fetch the manifest failed: ${lastError.message}`
@ -228,6 +275,55 @@ export default class OfficialBuilds extends BaseDistribution {
); );
} }
/** @param {string | undefined} manifestPath */
private getCachedManifest(
manifestPath: string | undefined
): tc.IToolRelease[] | undefined {
if (!manifestPath) {
return undefined;
}
try {
const manifest: unknown = JSON.parse(
fs.readFileSync(manifestPath, 'utf8')
);
if (isValidManifest(manifest)) {
core.debug(`Found manifest in ${manifestPath}`);
return manifest;
}
core.debug(`Ignoring invalid manifest in ${manifestPath}`);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
core.debug(
`Unable to read manifest from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
return undefined;
}
/**
* @param {string | undefined} manifestPath
* @param {tc.IToolRelease[]} manifest
*/
private cacheManifest(
manifestPath: string | undefined,
manifest: tc.IToolRelease[]
): void {
if (!manifestPath) {
return;
}
try {
fs.writeFileSync(manifestPath, JSON.stringify(manifest));
} catch (error) {
core.debug(
`Unable to cache manifest in ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
private resolveLtsAliasFromManifest( private resolveLtsAliasFromManifest(
versionSpec: string, versionSpec: string,
stable: boolean, stable: boolean,
@ -272,26 +368,6 @@ export default class OfficialBuilds extends BaseDistribution {
return release.version.split('.')[0]; 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( private async getInfoFromManifest(
versionSpec: string, versionSpec: string,
stable: boolean, stable: boolean,