Skip to content

Commit 8f2ef77

Browse files
feat: support private mirrors
1 parent 40337cb commit 8f2ef77

File tree

14 files changed

+281
-42
lines changed

14 files changed

+281
-42
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ See [action.yml](action.yml)
7676
# Set always-auth option in npmrc file.
7777
# Default: ''
7878
always-auth: ''
79+
80+
# Optional mirror to download binaries from.
81+
# Artifacts need to match the official Node.js
82+
# Example:
83+
# V8 Canaray Build: <mirror_url>/download/v8-canary
84+
# RC Build: <mirror_url>/download/rc
85+
# Official: Build <mirror_url>/dist
86+
# Nightly build: <mirror_url>/download/nightly
87+
# Default: ''
88+
mirror: ''
89+
90+
# Optional mirror token.
91+
# The token will be used as a bearer token in the Authorization header
92+
# Default: ''
93+
mirror-token: ''
7994
```
8095
<!-- end usage -->
8196

__tests__/canary-installer.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,70 @@ describe('setup-node', () => {
498498
);
499499
}
500500
);
501+
502+
it.each([
503+
[
504+
'20.0.0-v8-canary',
505+
'20.0.0-v8-canary20221103f7e2421e91',
506+
'20.0.0-v8-canary20221030fefe1c0879',
507+
'https://my_mirror.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz'
508+
],
509+
[
510+
'20-v8-canary',
511+
'20.0.0-v8-canary20221103f7e2421e91',
512+
'20.0.0-v8-canary20221030fefe1c0879',
513+
'https://my_mirror.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz'
514+
],
515+
[
516+
'19.0.0-v8-canary',
517+
'19.0.0-v8-canary202210187d6960f23f',
518+
'19.0.0-v8-canary202210172ec229fc56',
519+
'https://my_mirror.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz'
520+
],
521+
[
522+
'19-v8-canary',
523+
'19.0.0-v8-canary202210187d6960f23f',
524+
'19.0.0-v8-canary202210172ec229fc56',
525+
'https://my_mirror.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz'
526+
]
527+
])(
528+
'get %s version from dist if check-latest is true',
529+
async (input, expectedVersion, foundVersion, expectedUrl) => {
530+
const foundToolPath = path.normalize(`/cache/node/${foundVersion}/x64`);
531+
const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`);
532+
533+
inputs['node-version'] = input;
534+
inputs['check-latest'] = 'true';
535+
os['arch'] = 'x64';
536+
os['platform'] = 'linux';
537+
inputs['mirror'] = 'https://my_mirror.org';
538+
inputs['mirror-token'] = 'faketoken';
539+
540+
findSpy.mockReturnValue(foundToolPath);
541+
findAllVersionsSpy.mockReturnValue([
542+
'20.0.0-v8-canary20221030fefe1c0879',
543+
'19.0.0-v8-canary202210172ec229fc56',
544+
'20.0.0-v8-canary2022102310ff1e5a8d'
545+
]);
546+
dlSpy.mockImplementation(async () => '/some/temp/path');
547+
exSpy.mockImplementation(async () => '/some/other/temp/path');
548+
cacheSpy.mockImplementation(async () => toolPath);
549+
550+
// act
551+
await main.run();
552+
553+
// assert
554+
expect(findAllVersionsSpy).toHaveBeenCalled();
555+
expect(logSpy).toHaveBeenCalledWith(
556+
`Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}`
557+
);
558+
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
559+
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
560+
expect(cnSpy).toHaveBeenCalledWith(
561+
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
562+
);
563+
}
564+
);
501565
});
502566

503567
describe('setup-node v8 canary tests', () => {

__tests__/nightly-installer.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ describe('setup-node', () => {
315315
await main.run();
316316

317317
workingUrls.forEach(url => {
318-
expect(dlSpy).toHaveBeenCalledWith(url);
318+
expect(dlSpy).toHaveBeenCalledWith(url, undefined, undefined);
319319
});
320320
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${toolPath}${osm.EOL}`);
321321
});
@@ -449,6 +449,54 @@ describe('setup-node', () => {
449449
}
450450
}, 100000);
451451

452+
it('acquires specified architecture of node from mirror', async () => {
453+
for (const {arch, version, osSpec} of [
454+
{
455+
arch: 'x86',
456+
version: '18.0.0-nightly202110204cb3e06ed8',
457+
osSpec: 'win32'
458+
},
459+
{
460+
arch: 'x86',
461+
version: '20.0.0-nightly2022101987cdf7d412',
462+
osSpec: 'win32'
463+
}
464+
]) {
465+
os.platform = osSpec;
466+
os.arch = arch;
467+
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
468+
const platform = {
469+
linux: 'linux',
470+
darwin: 'darwin',
471+
win32: 'win'
472+
}[os.platform];
473+
474+
inputs['node-version'] = version;
475+
inputs['architecture'] = arch;
476+
inputs['always-auth'] = false;
477+
inputs['token'] = 'faketoken';
478+
inputs['mirror'] = 'https://my-mirror.org';
479+
inputs['mirror-token'] = 'my-mirror-token';
480+
481+
const expectedUrl = `https://my-mirror.org/download/nightly/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
482+
483+
// ... but not in the local cache
484+
findSpy.mockImplementation(() => '');
485+
findAllVersionsSpy.mockImplementation(() => []);
486+
487+
dlSpy.mockImplementation(async () => '/some/temp/path');
488+
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
489+
exSpy.mockImplementation(async () => '/some/other/temp/path');
490+
cacheSpy.mockImplementation(async () => toolPath);
491+
492+
await main.run();
493+
expect(dlSpy).toHaveBeenCalled();
494+
expect(logSpy).toHaveBeenCalledWith(
495+
`Acquiring ${version} - ${arch} from ${expectedUrl}`
496+
);
497+
}
498+
}, 100000);
499+
452500
describe('nightly versions', () => {
453501
it.each([
454502
[

__tests__/official-installer.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,4 +828,46 @@ describe('setup-node', () => {
828828
}
829829
);
830830
});
831+
832+
it('acquires specified architecture of node from mirror', async () => {
833+
for (const {arch, version, osSpec} of [
834+
{arch: 'x86', version: '12.16.2', osSpec: 'win32'},
835+
{arch: 'x86', version: '14.0.0', osSpec: 'win32'}
836+
]) {
837+
os.platform = osSpec;
838+
os.arch = arch;
839+
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
840+
const platform = {
841+
linux: 'linux',
842+
darwin: 'darwin',
843+
win32: 'win'
844+
}[os.platform];
845+
846+
inputs['node-version'] = version;
847+
inputs['architecture'] = arch;
848+
inputs['always-auth'] = false;
849+
inputs['token'] = 'faketoken';
850+
inputs['mirror'] = 'https://my_mirror_url';
851+
inputs['mirror-token'] = 'faketoken';
852+
853+
const expectedUrl =
854+
arch === 'x64'
855+
? `https://github.com/actions/node-versions/releases/download/${version}/node-${version}-${platform}-${arch}.zip`
856+
: `https://my_mirror_url/dist/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
857+
858+
// ... but not in the local cache
859+
findSpy.mockImplementation(() => '');
860+
861+
dlSpy.mockImplementation(async () => '/some/temp/path');
862+
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
863+
exSpy.mockImplementation(async () => '/some/other/temp/path');
864+
cacheSpy.mockImplementation(async () => toolPath);
865+
866+
await main.run();
867+
expect(dlSpy).toHaveBeenCalled();
868+
expect(logSpy).toHaveBeenCalledWith(
869+
`Acquiring ${version} - ${arch} from ${expectedUrl}`
870+
);
871+
}
872+
}, 100000);
831873
});

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ inputs:
2525
description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.'
2626
cache-dependency-path:
2727
description: 'Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc. Supports wildcards or a list of file names for caching multiple dependencies.'
28+
mirror:
29+
description: 'Used to specify an alternative mirror to downlooad Node.js binaries from'
30+
mirror-token:
31+
description: 'The token used as Authorization header when fetching from the mirror'
2832
# TODO: add input to control forcing to pull from cloud or dist.
2933
# escape valve for someone having issues or needing the absolute latest which isn't cached yet
3034
outputs:

dist/setup/index.js

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97211,9 +97211,13 @@ class BaseDistribution {
9721197211
}
9721297212
getNodeJsVersions() {
9721397213
return __awaiter(this, void 0, void 0, function* () {
97214-
const initialUrl = this.getDistributionUrl();
97214+
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
9721597215
const dataUrl = `${initialUrl}/index.json`;
97216-
const response = yield this.httpClient.getJson(dataUrl);
97216+
const headers = {};
97217+
if (this.nodeInfo.mirrorToken) {
97218+
headers['Authorization'] = `Bearer ${this.nodeInfo.mirrorToken}`;
97219+
}
97220+
const response = yield this.httpClient.getJson(dataUrl, headers);
9721797221
return response.result || [];
9721897222
});
9721997223
}
@@ -97228,7 +97232,7 @@ class BaseDistribution {
9722897232
? `${fileName}.zip`
9722997233
: `${fileName}.7z`
9723097234
: `${fileName}.tar.gz`;
97231-
const initialUrl = this.getDistributionUrl();
97235+
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
9723297236
const url = `${initialUrl}/v${version}/${urlFileName}`;
9723397237
return {
9723497238
downloadUrl: url,
@@ -97242,7 +97246,7 @@ class BaseDistribution {
9724297246
let downloadPath = '';
9724397247
core.info(`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`);
9724497248
try {
97245-
downloadPath = yield tc.downloadTool(info.downloadUrl);
97249+
downloadPath = yield tc.downloadTool(info.downloadUrl, undefined, this.nodeInfo.mirrorToken);
9724697250
}
9724797251
catch (err) {
9724897252
if (err instanceof tc.HTTPError &&
@@ -97266,7 +97270,7 @@ class BaseDistribution {
9726697270
}
9726797271
acquireWindowsNodeFromFallbackLocation(version_1) {
9726897272
return __awaiter(this, arguments, void 0, function* (version, arch = os_1.default.arch()) {
97269-
const initialUrl = this.getDistributionUrl();
97273+
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
9727097274
const osArch = this.translateArchToDistUrl(arch);
9727197275
// Create temporary folder to download to
9727297276
const tempDownloadFolder = `temp_${(0, uuid_1.v4)()}`;
@@ -97280,18 +97284,18 @@ class BaseDistribution {
9728097284
exeUrl = `${initialUrl}/v${version}/win-${osArch}/node.exe`;
9728197285
libUrl = `${initialUrl}/v${version}/win-${osArch}/node.lib`;
9728297286
core.info(`Downloading only node binary from ${exeUrl}`);
97283-
const exePath = yield tc.downloadTool(exeUrl);
97287+
const exePath = yield tc.downloadTool(exeUrl, undefined, this.nodeInfo.mirrorToken);
9728497288
yield io.cp(exePath, path.join(tempDir, 'node.exe'));
97285-
const libPath = yield tc.downloadTool(libUrl);
97289+
const libPath = yield tc.downloadTool(libUrl, undefined, this.nodeInfo.mirrorToken);
9728697290
yield io.cp(libPath, path.join(tempDir, 'node.lib'));
9728797291
}
9728897292
catch (err) {
9728997293
if (err instanceof tc.HTTPError && err.httpStatusCode == 404) {
9729097294
exeUrl = `${initialUrl}/v${version}/node.exe`;
9729197295
libUrl = `${initialUrl}/v${version}/node.lib`;
97292-
const exePath = yield tc.downloadTool(exeUrl);
97296+
const exePath = yield tc.downloadTool(exeUrl, undefined, this.nodeInfo.mirrorToken);
9729397297
yield io.cp(exePath, path.join(tempDir, 'node.exe'));
97294-
const libPath = yield tc.downloadTool(libUrl);
97298+
const libPath = yield tc.downloadTool(libUrl, undefined, this.nodeInfo.mirrorToken);
9729597299
yield io.cp(libPath, path.join(tempDir, 'node.lib'));
9729697300
}
9729797301
else {
@@ -97454,8 +97458,9 @@ class NightlyNodejs extends base_distribution_prerelease_1.default {
9745497458
super(nodeInfo);
9745597459
this.distribution = 'nightly';
9745697460
}
97457-
getDistributionUrl() {
97458-
return 'https://nodejs.org/download/nightly';
97461+
getDistributionUrl(mirror) {
97462+
const url = mirror || 'https://nodejs.org';
97463+
return `${url}/download/nightly`;
9745997464
}
9746097465
}
9746197466
exports["default"] = NightlyNodejs;
@@ -97553,7 +97558,7 @@ class OfficialBuilds extends base_distribution_1.default {
9755397558
const versionInfo = yield this.getInfoFromManifest(this.nodeInfo.versionSpec, this.nodeInfo.stable, osArch, manifest);
9755497559
if (versionInfo) {
9755597560
core.info(`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`);
97556-
downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.auth);
97561+
downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth);
9755797562
if (downloadPath) {
9755897563
toolPath = yield this.extractArchive(downloadPath, versionInfo, false);
9755997564
}
@@ -97621,12 +97626,13 @@ class OfficialBuilds extends base_distribution_1.default {
9762197626
version = super.evaluateVersions(versions);
9762297627
return version;
9762397628
}
97624-
getDistributionUrl() {
97625-
return `https://nodejs.org/dist`;
97629+
getDistributionUrl(mirror) {
97630+
const url = mirror || 'https://nodejs.org';
97631+
return `${url}/dist`;
9762697632
}
9762797633
getManifest() {
9762897634
core.debug('Getting manifest from actions/node-versions@main');
97629-
return tc.getManifestFromRepo('actions', 'node-versions', this.nodeInfo.auth, 'main');
97635+
return tc.getManifestFromRepo('actions', 'node-versions', this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth, 'main');
9763097636
}
9763197637
resolveLtsAliasFromManifest(versionSpec, stable, manifest) {
9763297638
var _a;
@@ -97709,8 +97715,9 @@ class RcBuild extends base_distribution_1.default {
9770997715
constructor(nodeInfo) {
9771097716
super(nodeInfo);
9771197717
}
97712-
getDistributionUrl() {
97713-
return 'https://nodejs.org/download/rc';
97718+
getDistributionUrl(mirror) {
97719+
const url = mirror || 'https://nodejs.org';
97720+
return `${url}/download/rc`;
9771497721
}
9771597722
}
9771697723
exports["default"] = RcBuild;
@@ -97733,8 +97740,9 @@ class CanaryBuild extends base_distribution_prerelease_1.default {
9773397740
super(nodeInfo);
9773497741
this.distribution = 'v8-canary';
9773597742
}
97736-
getDistributionUrl() {
97737-
return 'https://nodejs.org/download/v8-canary';
97743+
getDistributionUrl(mirror) {
97744+
const url = mirror || 'https://nodejs.org';
97745+
return `${url}/download/v8-canary`;
9773897746
}
9773997747
}
9774097748
exports["default"] = CanaryBuild;
@@ -97814,14 +97822,18 @@ function run() {
9781497822
if (version) {
9781597823
const token = core.getInput('token');
9781697824
const auth = !token ? undefined : `token ${token}`;
97825+
const mirror = core.getInput('mirror');
97826+
const mirrorToken = core.getInput('mirror-token');
9781797827
const stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE';
9781897828
const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE';
9781997829
const nodejsInfo = {
9782097830
versionSpec: version,
9782197831
checkLatest,
9782297832
auth,
9782397833
stable,
97824-
arch
97834+
arch,
97835+
mirror,
97836+
mirrorToken
9782597837
};
9782697838
const nodeDistribution = (0, installer_factory_1.getNodejsDistribution)(nodejsInfo);
9782797839
yield nodeDistribution.setupNodeJs();

docs/advanced-usage.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,18 @@ Please refer to the [Ensuring workflow access to your package - Configuring a pa
418418

419419
### always-auth input
420420
The always-auth input sets `always-auth=true` in .npmrc file. With this option set [npm](https://docs.npmjs.com/cli/v6/using-npm/config#always-auth)/yarn sends the authentication credentials when making a request to the registries.
421+
422+
## Use private mirror
423+
424+
It is possible to use a private mirror hosting Node.js binaries. This mirror must be a full mirror of the official Node.js distribution.
425+
The mirror URL can be set using the `mirror` input.
426+
It is possible to specify a token to authenticate with the mirror using the `mirror-token` input.
427+
The token will be passed as a bearer token in the `Authorization` header.
428+
429+
```yaml
430+
- uses: actions/setup-node@v4
431+
with:
432+
node-version: '14.x'
433+
mirror: 'https://nodejs.org/dist'
434+
mirror-token: 'your-mirror-token'
435+
```

0 commit comments

Comments
 (0)