Skip to content

Commit e3ce749

Browse files
feat: support private mirrors (#1240)
* feat: support private mirrors * chore: change fallback message with mirrors
1 parent 40337cb commit e3ce749

14 files changed

+322
-44
lines changed

README.md

+15
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

+64
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

+49-1
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

+79
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,43 @@ describe('setup-node', () => {
282282
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
283283
});
284284

285+
it('falls back to a version from node dist from mirror', async () => {
286+
os.platform = 'linux';
287+
os.arch = 'x64';
288+
289+
// a version which is not in the manifest but is in node dist
290+
const versionSpec = '11.15.0';
291+
const mirror = 'https://my_mirror_url';
292+
inputs['node-version'] = versionSpec;
293+
inputs['always-auth'] = false;
294+
inputs['token'] = 'faketoken';
295+
inputs['mirror'] = mirror;
296+
inputs['mirror-token'] = 'faketoken';
297+
298+
// ... but not in the local cache
299+
findSpy.mockImplementation(() => '');
300+
301+
dlSpy.mockImplementation(async () => '/some/temp/path');
302+
const toolPath = path.normalize('/cache/node/11.15.0/x64');
303+
exSpy.mockImplementation(async () => '/some/other/temp/path');
304+
cacheSpy.mockImplementation(async () => toolPath);
305+
306+
await main.run();
307+
308+
const expPath = path.join(toolPath, 'bin');
309+
310+
expect(getManifestSpy).toHaveBeenCalled();
311+
expect(logSpy).toHaveBeenCalledWith(
312+
`Attempting to download ${versionSpec}...`
313+
);
314+
expect(logSpy).toHaveBeenCalledWith(
315+
`Not found in manifest. Falling back to download directly from ${mirror}`
316+
);
317+
expect(dlSpy).toHaveBeenCalled();
318+
expect(exSpy).toHaveBeenCalled();
319+
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
320+
});
321+
285322
it('falls back to a version from node dist', async () => {
286323
os.platform = 'linux';
287324
os.arch = 'x64';
@@ -828,4 +865,46 @@ describe('setup-node', () => {
828865
}
829866
);
830867
});
868+
869+
it('acquires specified architecture of node from mirror', async () => {
870+
for (const {arch, version, osSpec} of [
871+
{arch: 'x86', version: '12.16.2', osSpec: 'win32'},
872+
{arch: 'x86', version: '14.0.0', osSpec: 'win32'}
873+
]) {
874+
os.platform = osSpec;
875+
os.arch = arch;
876+
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
877+
const platform = {
878+
linux: 'linux',
879+
darwin: 'darwin',
880+
win32: 'win'
881+
}[os.platform];
882+
883+
inputs['node-version'] = version;
884+
inputs['architecture'] = arch;
885+
inputs['always-auth'] = false;
886+
inputs['token'] = 'faketoken';
887+
inputs['mirror'] = 'https://my_mirror_url';
888+
inputs['mirror-token'] = 'faketoken';
889+
890+
const expectedUrl =
891+
arch === 'x64'
892+
? `https://github.com/actions/node-versions/releases/download/${version}/node-${version}-${platform}-${arch}.zip`
893+
: `https://my_mirror_url/dist/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
894+
895+
// ... but not in the local cache
896+
findSpy.mockImplementation(() => '');
897+
898+
dlSpy.mockImplementation(async () => '/some/temp/path');
899+
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
900+
exSpy.mockImplementation(async () => '/some/other/temp/path');
901+
cacheSpy.mockImplementation(async () => toolPath);
902+
903+
await main.run();
904+
expect(dlSpy).toHaveBeenCalled();
905+
expect(logSpy).toHaveBeenCalledWith(
906+
`Acquiring ${version} - ${arch} from ${expectedUrl}`
907+
);
908+
}
909+
}, 100000);
831910
});

action.yml

+4
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:

0 commit comments

Comments
 (0)