Skip to content

Commit b431a17

Browse files
feat: support private mirrors
1 parent 8026329 commit b431a17

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
@@ -100148,9 +100148,13 @@ class BaseDistribution {
100148100148
}
100149100149
getNodeJsVersions() {
100150100150
return __awaiter(this, void 0, void 0, function* () {
100151-
const initialUrl = this.getDistributionUrl();
100151+
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
100152100152
const dataUrl = `${initialUrl}/index.json`;
100153-
const response = yield this.httpClient.getJson(dataUrl);
100153+
const headers = {};
100154+
if (this.nodeInfo.mirrorToken) {
100155+
headers['Authorization'] = `Bearer ${this.nodeInfo.mirrorToken}`;
100156+
}
100157+
const response = yield this.httpClient.getJson(dataUrl, headers);
100154100158
return response.result || [];
100155100159
});
100156100160
}
@@ -100165,7 +100169,7 @@ class BaseDistribution {
100165100169
? `${fileName}.zip`
100166100170
: `${fileName}.7z`
100167100171
: `${fileName}.tar.gz`;
100168-
const initialUrl = this.getDistributionUrl();
100172+
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
100169100173
const url = `${initialUrl}/v${version}/${urlFileName}`;
100170100174
return {
100171100175
downloadUrl: url,
@@ -100179,7 +100183,7 @@ class BaseDistribution {
100179100183
let downloadPath = '';
100180100184
core.info(`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`);
100181100185
try {
100182-
downloadPath = yield tc.downloadTool(info.downloadUrl);
100186+
downloadPath = yield tc.downloadTool(info.downloadUrl, undefined, this.nodeInfo.mirrorToken);
100183100187
}
100184100188
catch (err) {
100185100189
if (err instanceof tc.HTTPError &&
@@ -100203,7 +100207,7 @@ class BaseDistribution {
100203100207
}
100204100208
acquireWindowsNodeFromFallbackLocation(version_1) {
100205100209
return __awaiter(this, arguments, void 0, function* (version, arch = os_1.default.arch()) {
100206-
const initialUrl = this.getDistributionUrl();
100210+
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
100207100211
const osArch = this.translateArchToDistUrl(arch);
100208100212
// Create temporary folder to download to
100209100213
const tempDownloadFolder = `temp_${(0, uuid_1.v4)()}`;
@@ -100217,18 +100221,18 @@ class BaseDistribution {
100217100221
exeUrl = `${initialUrl}/v${version}/win-${osArch}/node.exe`;
100218100222
libUrl = `${initialUrl}/v${version}/win-${osArch}/node.lib`;
100219100223
core.info(`Downloading only node binary from ${exeUrl}`);
100220-
const exePath = yield tc.downloadTool(exeUrl);
100224+
const exePath = yield tc.downloadTool(exeUrl, undefined, this.nodeInfo.mirrorToken);
100221100225
yield io.cp(exePath, path.join(tempDir, 'node.exe'));
100222-
const libPath = yield tc.downloadTool(libUrl);
100226+
const libPath = yield tc.downloadTool(libUrl, undefined, this.nodeInfo.mirrorToken);
100223100227
yield io.cp(libPath, path.join(tempDir, 'node.lib'));
100224100228
}
100225100229
catch (err) {
100226100230
if (err instanceof tc.HTTPError && err.httpStatusCode == 404) {
100227100231
exeUrl = `${initialUrl}/v${version}/node.exe`;
100228100232
libUrl = `${initialUrl}/v${version}/node.lib`;
100229-
const exePath = yield tc.downloadTool(exeUrl);
100233+
const exePath = yield tc.downloadTool(exeUrl, undefined, this.nodeInfo.mirrorToken);
100230100234
yield io.cp(exePath, path.join(tempDir, 'node.exe'));
100231-
const libPath = yield tc.downloadTool(libUrl);
100235+
const libPath = yield tc.downloadTool(libUrl, undefined, this.nodeInfo.mirrorToken);
100232100236
yield io.cp(libPath, path.join(tempDir, 'node.lib'));
100233100237
}
100234100238
else {
@@ -100391,8 +100395,9 @@ class NightlyNodejs extends base_distribution_prerelease_1.default {
100391100395
super(nodeInfo);
100392100396
this.distribution = 'nightly';
100393100397
}
100394-
getDistributionUrl() {
100395-
return 'https://nodejs.org/download/nightly';
100398+
getDistributionUrl(mirror) {
100399+
const url = mirror || 'https://nodejs.org';
100400+
return `${url}/download/nightly`;
100396100401
}
100397100402
}
100398100403
exports["default"] = NightlyNodejs;
@@ -100490,7 +100495,7 @@ class OfficialBuilds extends base_distribution_1.default {
100490100495
const versionInfo = yield this.getInfoFromManifest(this.nodeInfo.versionSpec, this.nodeInfo.stable, osArch, manifest);
100491100496
if (versionInfo) {
100492100497
core.info(`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`);
100493-
downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.auth);
100498+
downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth);
100494100499
if (downloadPath) {
100495100500
toolPath = yield this.extractArchive(downloadPath, versionInfo, false);
100496100501
}
@@ -100558,12 +100563,13 @@ class OfficialBuilds extends base_distribution_1.default {
100558100563
version = super.evaluateVersions(versions);
100559100564
return version;
100560100565
}
100561-
getDistributionUrl() {
100562-
return `https://nodejs.org/dist`;
100566+
getDistributionUrl(mirror) {
100567+
const url = mirror || 'https://nodejs.org';
100568+
return `${url}/dist`;
100563100569
}
100564100570
getManifest() {
100565100571
core.debug('Getting manifest from actions/node-versions@main');
100566-
return tc.getManifestFromRepo('actions', 'node-versions', this.nodeInfo.auth, 'main');
100572+
return tc.getManifestFromRepo('actions', 'node-versions', this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth, 'main');
100567100573
}
100568100574
resolveLtsAliasFromManifest(versionSpec, stable, manifest) {
100569100575
var _a;
@@ -100646,8 +100652,9 @@ class RcBuild extends base_distribution_1.default {
100646100652
constructor(nodeInfo) {
100647100653
super(nodeInfo);
100648100654
}
100649-
getDistributionUrl() {
100650-
return 'https://nodejs.org/download/rc';
100655+
getDistributionUrl(mirror) {
100656+
const url = mirror || 'https://nodejs.org';
100657+
return `${url}/download/rc`;
100651100658
}
100652100659
}
100653100660
exports["default"] = RcBuild;
@@ -100670,8 +100677,9 @@ class CanaryBuild extends base_distribution_prerelease_1.default {
100670100677
super(nodeInfo);
100671100678
this.distribution = 'v8-canary';
100672100679
}
100673-
getDistributionUrl() {
100674-
return 'https://nodejs.org/download/v8-canary';
100680+
getDistributionUrl(mirror) {
100681+
const url = mirror || 'https://nodejs.org';
100682+
return `${url}/download/v8-canary`;
100675100683
}
100676100684
}
100677100685
exports["default"] = CanaryBuild;
@@ -100751,14 +100759,18 @@ function run() {
100751100759
if (version) {
100752100760
const token = core.getInput('token');
100753100761
const auth = !token ? undefined : `token ${token}`;
100762+
const mirror = core.getInput('mirror');
100763+
const mirrorToken = core.getInput('mirror-token');
100754100764
const stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE';
100755100765
const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE';
100756100766
const nodejsInfo = {
100757100767
versionSpec: version,
100758100768
checkLatest,
100759100769
auth,
100760100770
stable,
100761-
arch
100771+
arch,
100772+
mirror,
100773+
mirrorToken
100762100774
};
100763100775
const nodeDistribution = (0, installer_factory_1.getNodejsDistribution)(nodejsInfo);
100764100776
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)