Skip to content

Commit d86ebcd

Browse files
authored
Add support for volta.extends (#921)
* Add support for `volta.extends` * Code review
1 parent b39b52d commit d86ebcd

File tree

10 files changed

+197
-179
lines changed

10 files changed

+197
-179
lines changed

.github/workflows/versions.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,6 @@ jobs:
162162
[.nvmrc, .tool-versions, .tool-versions-node, package.json]
163163
steps:
164164
- uses: actions/checkout@v4
165-
- name: Remove volta from package.json
166-
shell: bash
167-
run: cat <<< "$(jq 'del(.volta)' ./__tests__/data/package.json)" > ./__tests__/data/package.json
168165
- name: Setup node from node version file
169166
uses: ./
170167
with:
@@ -183,7 +180,22 @@ jobs:
183180
- name: Setup node from node version file
184181
uses: ./
185182
with:
186-
node-version-file: '__tests__/data/package.json'
183+
node-version-file: '__tests__/data/package-volta.json'
184+
- name: Verify node
185+
run: __tests__/verify-node.sh 16
186+
187+
version-file-volta-extends:
188+
runs-on: ${{ matrix.os }}
189+
strategy:
190+
fail-fast: false
191+
matrix:
192+
os: [ubuntu-latest, windows-latest, macos-latest]
193+
steps:
194+
- uses: actions/checkout@v4
195+
- name: Setup node from node version file
196+
uses: ./
197+
with:
198+
node-version-file: '__tests__/data/package-volta-extends.json'
187199
- name: Verify node
188200
run: __tests__/verify-node.sh 16
189201

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"volta": {
3+
"extends": "./package-volta.json"
4+
}
5+
}

__tests__/data/package-volta.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"engines": {
3+
"node": "^14.0.0"
4+
},
5+
"volta": {
6+
"node": "16.0.0"
7+
}
8+
}

__tests__/data/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
{
22
"engines": {
33
"node": "^14.0.0"
4-
},
5-
"volta": {
6-
"node": "16.0.0"
74
}
85
}

__tests__/main.test.ts

Lines changed: 53 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@ describe('main tests', () => {
2424
let startGroupSpy: jest.SpyInstance;
2525
let endGroupSpy: jest.SpyInstance;
2626

27-
let existsSpy: jest.SpyInstance;
28-
2927
let getExecOutputSpy: jest.SpyInstance;
3028

31-
let parseNodeVersionSpy: jest.SpyInstance;
29+
let getNodeVersionFromFileSpy: jest.SpyInstance;
3230
let cnSpy: jest.SpyInstance;
3331
let findSpy: jest.SpyInstance;
3432
let isCacheActionAvailable: jest.SpyInstance;
@@ -41,6 +39,7 @@ describe('main tests', () => {
4139
// node
4240
os = {};
4341
console.log('::stop-commands::stoptoken');
42+
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
4443
process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
4544
process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
4645
infoSpy = jest.spyOn(core, 'info');
@@ -62,12 +61,10 @@ describe('main tests', () => {
6261

6362
isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable');
6463

65-
existsSpy = jest.spyOn(fs, 'existsSync');
66-
6764
cnSpy = jest.spyOn(process.stdout, 'write');
6865
cnSpy.mockImplementation(line => {
6966
// uncomment to debug
70-
// process.stderr.write('write:' + line + '\n');
67+
process.stderr.write('write:' + line + '\n');
7168
});
7269

7370
setupNodeJsSpy = jest.spyOn(OfficialBuilds.prototype, 'setupNodeJs');
@@ -85,7 +82,7 @@ describe('main tests', () => {
8582
jest.restoreAllMocks();
8683
}, 100000);
8784

88-
describe('parseNodeVersionFile', () => {
85+
describe('getNodeVersionFromFile', () => {
8986
each`
9087
contents | expected
9188
${'12'} | ${'12'}
@@ -100,10 +97,27 @@ describe('main tests', () => {
10097
${'unknown format'} | ${'unknown format'}
10198
${' 14.1.0 '} | ${'14.1.0'}
10299
${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'}
100+
${'{"volta": {"extends": "./package.json"}}'}| ${'18.0.0'}
103101
${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'}
104102
${'{}'} | ${null}
105103
`.it('parses "$contents"', ({contents, expected}) => {
106-
expect(util.parseNodeVersionFile(contents)).toBe(expected);
104+
const existsSpy = jest.spyOn(fs, 'existsSync');
105+
existsSpy.mockImplementation(() => true);
106+
107+
const readFileSpy = jest.spyOn(fs, 'readFileSync');
108+
readFileSpy.mockImplementation(filePath => {
109+
if (
110+
typeof filePath === 'string' &&
111+
path.basename(filePath) === 'package.json'
112+
) {
113+
// Special case for volta.extends
114+
return '{"volta": {"node": "18.0.0"}}';
115+
}
116+
117+
return contents;
118+
});
119+
120+
expect(util.getNodeVersionFromFile('file')).toBe(expected);
107121
});
108122
});
109123

@@ -142,118 +156,72 @@ describe('main tests', () => {
142156

143157
describe('node-version-file flag', () => {
144158
beforeEach(() => {
145-
parseNodeVersionSpy = jest.spyOn(util, 'parseNodeVersionFile');
159+
delete inputs['node-version'];
160+
inputs['node-version-file'] = '.nvmrc';
161+
162+
getNodeVersionFromFileSpy = jest.spyOn(util, 'getNodeVersionFromFile');
163+
});
164+
165+
afterEach(() => {
166+
getNodeVersionFromFileSpy.mockRestore();
146167
});
147168

148-
it('not used if node-version is provided', async () => {
169+
it('does not read node-version-file if node-version is provided', async () => {
149170
// Arrange
150171
inputs['node-version'] = '12';
151172

152173
// Act
153174
await main.run();
154175

155176
// Assert
156-
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
157-
}, 10000);
158-
159-
it('not used if node-version-file not provided', async () => {
160-
// Act
161-
await main.run();
162-
163-
// Assert
164-
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
177+
expect(inputs['node-version']).toBeDefined();
178+
expect(inputs['node-version-file']).toBeDefined();
179+
expect(getNodeVersionFromFileSpy).not.toHaveBeenCalled();
180+
expect(warningSpy).toHaveBeenCalledWith(
181+
'Both node-version and node-version-file inputs are specified, only node-version will be used'
182+
);
165183
});
166184

167-
it('reads node-version-file if provided', async () => {
185+
it('does not read node-version-file if node-version-file is not provided', async () => {
168186
// Arrange
169-
const versionSpec = 'v14';
170-
const versionFile = '.nvmrc';
171-
const expectedVersionSpec = '14';
172-
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
173-
inputs['node-version-file'] = versionFile;
174-
175-
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
176-
existsSpy.mockImplementationOnce(
177-
input => input === path.join(__dirname, 'data', versionFile)
178-
);
187+
delete inputs['node-version-file'];
179188

180189
// Act
181190
await main.run();
182191

183192
// Assert
184-
expect(existsSpy).toHaveBeenCalledTimes(1);
185-
expect(existsSpy).toHaveReturnedWith(true);
186-
expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec);
187-
expect(infoSpy).toHaveBeenCalledWith(
188-
`Resolved ${versionFile} as ${expectedVersionSpec}`
189-
);
190-
}, 10000);
193+
expect(getNodeVersionFromFileSpy).not.toHaveBeenCalled();
194+
});
191195

192-
it('reads package.json as node-version-file if provided', async () => {
196+
it('reads node-version-file', async () => {
193197
// Arrange
194-
const versionSpec = fs.readFileSync(
195-
path.join(__dirname, 'data/package.json'),
196-
'utf-8'
197-
);
198-
const versionFile = 'package.json';
199198
const expectedVersionSpec = '14';
200-
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
201-
inputs['node-version-file'] = versionFile;
199+
getNodeVersionFromFileSpy.mockImplementation(() => expectedVersionSpec);
202200

203-
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
204-
existsSpy.mockImplementationOnce(
205-
input => input === path.join(__dirname, 'data', versionFile)
206-
);
207201
// Act
208202
await main.run();
209203

210204
// Assert
211-
expect(existsSpy).toHaveBeenCalledTimes(1);
212-
expect(existsSpy).toHaveReturnedWith(true);
213-
expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec);
205+
expect(getNodeVersionFromFileSpy).toHaveBeenCalled();
214206
expect(infoSpy).toHaveBeenCalledWith(
215-
`Resolved ${versionFile} as ${expectedVersionSpec}`
207+
`Resolved ${inputs['node-version-file']} as ${expectedVersionSpec}`
216208
);
217209
}, 10000);
218210

219-
it('both node-version-file and node-version are provided', async () => {
220-
inputs['node-version'] = '12';
221-
const versionSpec = 'v14';
222-
const versionFile = '.nvmrc';
223-
const expectedVersionSpec = '14';
224-
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, '..');
225-
inputs['node-version-file'] = versionFile;
226-
227-
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
228-
229-
// Act
230-
await main.run();
231-
232-
// Assert
233-
expect(existsSpy).toHaveBeenCalledTimes(0);
234-
expect(parseNodeVersionSpy).not.toHaveBeenCalled();
235-
expect(warningSpy).toHaveBeenCalledWith(
236-
'Both node-version and node-version-file inputs are specified, only node-version will be used'
237-
);
238-
});
239-
240-
it('should throw an error if node-version-file is not found', async () => {
241-
const versionFile = '.nvmrc';
242-
const versionFilePath = path.join(__dirname, '..', versionFile);
243-
inputs['node-version-file'] = versionFile;
244-
245-
inSpy.mockImplementation(name => inputs[name]);
246-
existsSpy.mockImplementationOnce(
247-
input => input === path.join(__dirname, 'data', versionFile)
211+
it('should throw an error if node-version-file is not accessible', async () => {
212+
// Arrange
213+
inputs['node-version-file'] = 'non-existing-file';
214+
const versionFilePath = path.join(
215+
__dirname,
216+
'data',
217+
inputs['node-version-file']
248218
);
249219

250220
// Act
251221
await main.run();
252222

253223
// Assert
254-
expect(existsSpy).toHaveBeenCalled();
255-
expect(existsSpy).toHaveReturnedWith(false);
256-
expect(parseNodeVersionSpy).not.toHaveBeenCalled();
224+
expect(getNodeVersionFromFileSpy).toHaveBeenCalled();
257225
expect(cnSpy).toHaveBeenCalledWith(
258226
`::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}`
259227
);

dist/cache-save/index.js

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83329,49 +83329,60 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8332983329
step((generator = generator.apply(thisArg, _arguments || [])).next());
8333083330
});
8333183331
};
83332+
var __importDefault = (this && this.__importDefault) || function (mod) {
83333+
return (mod && mod.__esModule) ? mod : { "default": mod };
83334+
};
8333283335
Object.defineProperty(exports, "__esModule", ({ value: true }));
83333-
exports.unique = exports.printEnvDetailsAndSetOutput = exports.parseNodeVersionFile = void 0;
83336+
exports.unique = exports.printEnvDetailsAndSetOutput = exports.getNodeVersionFromFile = void 0;
8333483337
const core = __importStar(__nccwpck_require__(2186));
8333583338
const exec = __importStar(__nccwpck_require__(1514));
83336-
function parseNodeVersionFile(contents) {
83337-
var _a, _b, _c;
83338-
let nodeVersion;
83339+
const fs_1 = __importDefault(__nccwpck_require__(7147));
83340+
const path_1 = __importDefault(__nccwpck_require__(1017));
83341+
function getNodeVersionFromFile(versionFilePath) {
83342+
var _a, _b, _c, _d, _e;
83343+
if (!fs_1.default.existsSync(versionFilePath)) {
83344+
throw new Error(`The specified node version file at: ${versionFilePath} does not exist`);
83345+
}
83346+
const contents = fs_1.default.readFileSync(versionFilePath, 'utf8');
8333983347
// Try parsing the file as an NPM `package.json` file.
8334083348
try {
8334183349
const manifest = JSON.parse(contents);
83342-
// JSON can parse numbers, but that's handled later
83343-
if (typeof manifest === 'object') {
83344-
nodeVersion = (_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node;
83345-
if (!nodeVersion)
83346-
nodeVersion = (_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node;
83347-
// if contents are an object, we parsed JSON
83350+
// Presume package.json file.
83351+
if (typeof manifest === 'object' && !!manifest) {
83352+
// Support Volta.
83353+
// See https://docs.volta.sh/guide/understanding#managing-your-project
83354+
if ((_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node) {
83355+
return manifest.volta.node;
83356+
}
83357+
if ((_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node) {
83358+
return manifest.engines.node;
83359+
}
83360+
// Support Volta workspaces.
83361+
// See https://docs.volta.sh/advanced/workspaces
83362+
if ((_c = manifest.volta) === null || _c === void 0 ? void 0 : _c.extends) {
83363+
const extendedFilePath = path_1.default.resolve(path_1.default.dirname(versionFilePath), manifest.volta.extends);
83364+
core.info('Resolving node version from ' + extendedFilePath);
83365+
return getNodeVersionFromFile(extendedFilePath);
83366+
}
83367+
// If contents are an object, we parsed JSON
8334883368
// this can happen if node-version-file is a package.json
8334983369
// yet contains no volta.node or engines.node
8335083370
//
83351-
// if node-version file is _not_ json, control flow
83371+
// If node-version file is _not_ JSON, control flow
8335283372
// will not have reached these lines.
8335383373
//
8335483374
// And because we've reached here, we know the contents
8335583375
// *are* JSON, so no further string parsing makes sense.
83356-
if (!nodeVersion) {
83357-
return null;
83358-
}
83376+
return null;
8335983377
}
8336083378
}
83361-
catch (_d) {
83379+
catch (_f) {
8336283380
core.info('Node version file is not JSON file');
8336383381
}
83364-
if (!nodeVersion) {
83365-
const found = contents.match(/^(?:node(js)?\s+)?v?(?<version>[^\s]+)$/m);
83366-
nodeVersion = (_c = found === null || found === void 0 ? void 0 : found.groups) === null || _c === void 0 ? void 0 : _c.version;
83367-
}
83368-
// In the case of an unknown format,
83369-
// return as is and evaluate the version separately.
83370-
if (!nodeVersion)
83371-
nodeVersion = contents.trim();
83372-
return nodeVersion;
83382+
const found = contents.match(/^(?:node(js)?\s+)?v?(?<version>[^\s]+)$/m);
83383+
return (_e = (_d = found === null || found === void 0 ? void 0 : found.groups) === null || _d === void 0 ? void 0 : _d.version) !== null && _e !== void 0 ? _e : contents.trim();
8337383384
}
83374-
exports.parseNodeVersionFile = parseNodeVersionFile;
83385+
exports.getNodeVersionFromFile = getNodeVersionFromFile;
8337583386
function printEnvDetailsAndSetOutput() {
8337683387
return __awaiter(this, void 0, void 0, function* () {
8337783388
core.startGroup('Environment details');

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy