Skip to content

Commit d08cf22

Browse files
authored
Adding Node.js version file support (#338)
1 parent 360ab8b commit d08cf22

File tree

9 files changed

+221
-23
lines changed

9 files changed

+221
-23
lines changed

.github/workflows/versions.yml

+15
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,21 @@ jobs:
7878
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
7979
shell: bash
8080

81+
version-file:
82+
runs-on: ${{ matrix.os }}
83+
strategy:
84+
fail-fast: false
85+
matrix:
86+
os: [ubuntu-latest, windows-latest, macos-latest]
87+
steps:
88+
- uses: actions/checkout@v2
89+
- name: Setup node from node version file
90+
uses: ./
91+
with:
92+
node-version-file: '__tests__/data/.nvmrc'
93+
- name: Verify node
94+
run: __tests__/verify-node.sh 14
95+
8196
node-dist:
8297
runs-on: ${{ matrix.os }}
8398
strategy:

README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,13 @@ jobs:
9393
## Advanced usage
9494

9595
1. [Check latest version](docs/advanced-usage.md#check-latest-version)
96-
2. [Using different architectures](docs/advanced-usage.md#architecture)
97-
3. [Caching packages dependencies](docs/advanced-usage.md#caching-packages-dependencies)
98-
4. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures)
99-
5. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm)
100-
6. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn)
101-
7. [Using private packages](docs/advanced-usage.md#use-private-packages)
96+
2. [Using a node version file](docs/advanced-usage.md#node-version-file)
97+
3. [Using different architectures](docs/advanced-usage.md#architecture)
98+
4. [Caching packages dependencies](docs/advanced-usage.md#caching-packages-dependencies)
99+
5. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures)
100+
6. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm)
101+
7. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn)
102+
8. [Using private packages](docs/advanced-usage.md#use-private-packages)
102103

103104
# License
104105

__tests__/data/.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v14

__tests__/installer.test.ts

+97-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as core from '@actions/core';
22
import * as io from '@actions/io';
33
import * as tc from '@actions/tool-cache';
4+
import * as im from '../src/installer';
45
import fs from 'fs';
56
import cp from 'child_process';
67
import osm = require('os');
78
import path from 'path';
89
import * as main from '../src/main';
9-
import * as im from '../src/installer';
1010
import * as auth from '../src/authutil';
1111

1212
let nodeTestManifest = require('./data/versions-manifest.json');
@@ -31,9 +31,11 @@ describe('setup-node', () => {
3131
let dbgSpy: jest.SpyInstance;
3232
let whichSpy: jest.SpyInstance;
3333
let existsSpy: jest.SpyInstance;
34+
let readFileSyncSpy: jest.SpyInstance;
3435
let mkdirpSpy: jest.SpyInstance;
3536
let execSpy: jest.SpyInstance;
3637
let authSpy: jest.SpyInstance;
38+
let parseNodeVersionSpy: jest.SpyInstance;
3739

3840
beforeEach(() => {
3941
// @actions/core
@@ -58,6 +60,7 @@ describe('setup-node', () => {
5860
cacheSpy = jest.spyOn(tc, 'cacheDir');
5961
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
6062
getDistSpy = jest.spyOn(im, 'getVersionsFromDist');
63+
parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile');
6164

6265
// io
6366
whichSpy = jest.spyOn(io, 'which');
@@ -91,6 +94,10 @@ describe('setup-node', () => {
9194
// uncomment to see debug output
9295
// process.stderr.write(msg + '\n');
9396
});
97+
warningSpy.mockImplementation(msg => {
98+
// uncomment to debug
99+
// process.stderr.write('log:' + line + '\n');
100+
});
94101
});
95102

96103
afterEach(() => {
@@ -101,6 +108,7 @@ describe('setup-node', () => {
101108

102109
afterAll(async () => {
103110
console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions
111+
jest.restoreAllMocks();
104112
}, 100000);
105113

106114
//--------------------------------------------------
@@ -343,7 +351,7 @@ describe('setup-node', () => {
343351
expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`);
344352
});
345353

346-
it('Acquires specified architecture of node', async () => {
354+
it('acquires specified architecture of node', async () => {
347355
for (const {arch, version, osSpec} of [
348356
{arch: 'x86', version: '12.16.2', osSpec: 'win32'},
349357
{arch: 'x86', version: '14.0.0', osSpec: 'win32'}
@@ -549,6 +557,93 @@ describe('setup-node', () => {
549557
});
550558
});
551559

560+
describe('node-version-file flag', () => {
561+
it('not used if node-version is provided', async () => {
562+
// Arrange
563+
inputs['node-version'] = '12';
564+
565+
// Act
566+
await main.run();
567+
568+
// Assert
569+
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
570+
});
571+
572+
it('not used if node-version-file not provided', async () => {
573+
// Act
574+
await main.run();
575+
576+
// Assert
577+
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
578+
});
579+
580+
it('reads node-version-file if provided', async () => {
581+
// Arrange
582+
const versionSpec = 'v14';
583+
const versionFile = '.nvmrc';
584+
const expectedVersionSpec = '14';
585+
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
586+
inputs['node-version-file'] = versionFile;
587+
588+
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
589+
existsSpy.mockImplementationOnce(
590+
input => input === path.join(__dirname, 'data', versionFile)
591+
);
592+
// Act
593+
await main.run();
594+
595+
// Assert
596+
expect(existsSpy).toHaveBeenCalledTimes(1);
597+
expect(existsSpy).toHaveReturnedWith(true);
598+
expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec);
599+
expect(logSpy).toHaveBeenCalledWith(
600+
`Resolved ${versionFile} as ${expectedVersionSpec}`
601+
);
602+
});
603+
604+
it('both node-version-file and node-version are provided', async () => {
605+
inputs['node-version'] = '12';
606+
const versionSpec = 'v14';
607+
const versionFile = '.nvmrc';
608+
const expectedVersionSpec = '14';
609+
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, '..');
610+
inputs['node-version-file'] = versionFile;
611+
612+
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
613+
614+
// Act
615+
await main.run();
616+
617+
// Assert
618+
expect(existsSpy).toHaveBeenCalledTimes(0);
619+
expect(parseNodeVersionSpy).not.toHaveBeenCalled();
620+
expect(warningSpy).toHaveBeenCalledWith(
621+
'Both node-version and node-version-file inputs are specified, only node-version will be used'
622+
);
623+
});
624+
625+
it('should throw an error if node-version-file is not found', async () => {
626+
const versionFile = '.nvmrc';
627+
const versionFilePath = path.join(__dirname, '..', versionFile);
628+
inputs['node-version-file'] = versionFile;
629+
630+
inSpy.mockImplementation(name => inputs[name]);
631+
existsSpy.mockImplementationOnce(
632+
input => input === path.join(__dirname, 'data', versionFile)
633+
);
634+
635+
// Act
636+
await main.run();
637+
638+
// Assert
639+
expect(existsSpy).toHaveBeenCalled();
640+
expect(existsSpy).toHaveReturnedWith(false);
641+
expect(parseNodeVersionSpy).not.toHaveBeenCalled();
642+
expect(cnSpy).toHaveBeenCalledWith(
643+
`::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}`
644+
);
645+
});
646+
});
552647
describe('LTS version', () => {
553648
beforeEach(() => {
554649
os.platform = 'linux';

action.yml

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ inputs:
77
default: 'false'
88
node-version:
99
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0'
10+
node-version-file:
11+
description: 'File containing the version Spec of the version to use. Examples: .nvmrc, .node-version'
1012
architecture:
1113
description: 'Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.'
1214
check-latest:

dist/setup/index.js

+36-8
Original file line numberDiff line numberDiff line change
@@ -6938,9 +6938,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
69386938
result["default"] = mod;
69396939
return result;
69406940
};
6941+
var __importDefault = (this && this.__importDefault) || function (mod) {
6942+
return (mod && mod.__esModule) ? mod : { "default": mod };
6943+
};
69416944
Object.defineProperty(exports, "__esModule", { value: true });
69426945
const core = __importStar(__webpack_require__(470));
69436946
const installer = __importStar(__webpack_require__(923));
6947+
const fs_1 = __importDefault(__webpack_require__(747));
69446948
const auth = __importStar(__webpack_require__(749));
69456949
const path = __importStar(__webpack_require__(622));
69466950
const cache_restore_1 = __webpack_require__(409);
@@ -6953,10 +6957,7 @@ function run() {
69536957
// Version is optional. If supplied, install / use from the tool cache
69546958
// If not supplied then task is still used to setup proxy, auth, etc...
69556959
//
6956-
let version = core.getInput('node-version');
6957-
if (!version) {
6958-
version = core.getInput('version');
6959-
}
6960+
let version = resolveVersionInput();
69606961
let arch = core.getInput('architecture');
69616962
const cache = core.getInput('cache');
69626963
// if architecture supplied but node-version is not
@@ -6991,8 +6992,8 @@ function run() {
69916992
core.info(`##[add-matcher]${path.join(matchersPath, 'eslint-stylish.json')}`);
69926993
core.info(`##[add-matcher]${path.join(matchersPath, 'eslint-compact.json')}`);
69936994
}
6994-
catch (error) {
6995-
core.setFailed(error.message);
6995+
catch (err) {
6996+
core.setFailed(err.message);
69966997
}
69976998
});
69986999
}
@@ -7001,6 +7002,25 @@ function isGhes() {
70017002
const ghUrl = new url_1.URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
70027003
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
70037004
}
7005+
function resolveVersionInput() {
7006+
let version = core.getInput('node-version') || core.getInput('version');
7007+
const versionFileInput = core.getInput('node-version-file');
7008+
if (version && versionFileInput) {
7009+
core.warning('Both node-version and node-version-file inputs are specified, only node-version will be used');
7010+
}
7011+
if (version) {
7012+
return version;
7013+
}
7014+
if (versionFileInput) {
7015+
const versionFilePath = path.join(process.env.GITHUB_WORKSPACE, versionFileInput);
7016+
if (!fs_1.default.existsSync(versionFilePath)) {
7017+
throw new Error(`The specified node version file at: ${versionFilePath} does not exist`);
7018+
}
7019+
version = installer.parseNodeVersionFile(fs_1.default.readFileSync(versionFilePath, 'utf8'));
7020+
core.info(`Resolved ${versionFileInput} as ${version}`);
7021+
}
7022+
return version;
7023+
}
70047024

70057025

70067026
/***/ }),
@@ -65340,7 +65360,7 @@ exports.NOOP_TEXT_MAP_PROPAGATOR = new NoopTextMapPropagator();
6534065360
/* 921 */,
6534165361
/* 922 */,
6534265362
/* 923 */
65343-
/***/ (function(module, exports, __webpack_require__) {
65363+
/***/ (function(__unusedmodule, exports, __webpack_require__) {
6534465364

6534565365
"use strict";
6534665366

@@ -65617,7 +65637,7 @@ function queryDistForMatch(versionSpec, arch = os.arch()) {
6561765637
throw new Error(`Unexpected OS '${osPlat}'`);
6561865638
}
6561965639
let versions = [];
65620-
let nodeVersions = yield module.exports.getVersionsFromDist();
65640+
let nodeVersions = yield getVersionsFromDist();
6562165641
nodeVersions.forEach((nodeVersion) => {
6562265642
// ensure this version supports your os and platform
6562365643
if (nodeVersion.files.indexOf(dataFileName) >= 0) {
@@ -65705,6 +65725,14 @@ function translateArchToDistUrl(arch) {
6570565725
return arch;
6570665726
}
6570765727
}
65728+
function parseNodeVersionFile(contents) {
65729+
let nodeVersion = contents.trim();
65730+
if (/^v\d/.test(nodeVersion)) {
65731+
nodeVersion = nodeVersion.substring(1);
65732+
}
65733+
return nodeVersion;
65734+
}
65735+
exports.parseNodeVersionFile = parseNodeVersionFile;
6570865736

6570965737

6571065738
/***/ }),

docs/advanced-usage.md

+16
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ steps:
1919
- run: npm test
2020
```
2121
22+
## Node version file
23+
24+
The `node-version-file` input accepts a path to a file containing the version of Node.js to be used by a project, for example `.nvmrc` or `.node-version`. If both the `node-version` and the `node-version-file` inputs are provided then the `node-version` input is used.
25+
See [supported version syntax](https://github.com/actions/setup-node#supported-version-syntax)
26+
> The action will search for the node version file relative to the repository root.
27+
28+
```yaml
29+
steps:
30+
- uses: actions/checkout@v2
31+
- uses: actions/setup-node@v2
32+
with:
33+
node-version-file: '.nvmrc'
34+
- run: npm install
35+
- run: npm test
36+
```
37+
2238
## Architecture
2339

2440
You can use any of the [supported operating systems](https://docs.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners), and the compatible `architecture` can be selected using `architecture`. Values are `x86`, `x64`, `arm64`, `armv6l`, `armv7l`, `ppc64le`, `s390x` (not all of the architectures are available on all platforms).

src/installer.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ async function queryDistForMatch(
371371
}
372372

373373
let versions: string[] = [];
374-
let nodeVersions = await module.exports.getVersionsFromDist();
374+
let nodeVersions = await getVersionsFromDist();
375375

376376
nodeVersions.forEach((nodeVersion: INodeVersion) => {
377377
// ensure this version supports your os and platform
@@ -464,3 +464,12 @@ function translateArchToDistUrl(arch: string): string {
464464
return arch;
465465
}
466466
}
467+
468+
export function parseNodeVersionFile(contents: string): string {
469+
let nodeVersion = contents.trim();
470+
471+
if (/^v\d/.test(nodeVersion)) {
472+
nodeVersion = nodeVersion.substring(1);
473+
}
474+
return nodeVersion;
475+
}

0 commit comments

Comments
 (0)