Expand node version file support to include lts based codenames

This commit is contained in:
Taylor McCarthy 2020-11-18 22:04:38 -05:00
parent c3812bd36a
commit a0d376d3fa
7 changed files with 311 additions and 43 deletions

View file

@ -6,7 +6,8 @@ import cp from 'child_process';
import osm = require('os');
import path from 'path';
import * as main from '../src/main';
import * as im from '../src/installer';
import * as nv from '../src/node-version';
import * as nvf from '../src/node-version-file';
import * as auth from '../src/authutil';
import {context} from '@actions/github';
@ -40,6 +41,7 @@ describe('setup-node', () => {
let mkdirpSpy: jest.SpyInstance;
let execSpy: jest.SpyInstance;
let authSpy: jest.SpyInstance;
let parseNodeVersionSpy: jest.SpyInstance;
beforeEach(() => {
// @actions/core
@ -63,7 +65,8 @@ describe('setup-node', () => {
exSpy = jest.spyOn(tc, 'extractTar');
cacheSpy = jest.spyOn(tc, 'cacheDir');
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
getDistSpy = jest.spyOn(im, 'getVersionsFromDist');
getDistSpy = jest.spyOn(nv, 'getVersionsFromDist');
parseNodeVersionSpy = jest.spyOn(nvf, 'parseNodeVersionFile');
// io
whichSpy = jest.spyOn(io, 'which');
@ -79,7 +82,7 @@ describe('setup-node', () => {
getManifestSpy.mockImplementation(
() => <tc.IToolRelease[]>nodeTestManifest
);
getDistSpy.mockImplementation(() => <im.INodeVersion>nodeTestDist);
getDistSpy.mockImplementation(() => <nv.INodeVersion>nodeTestDist);
// writes
cnSpy = jest.spyOn(process.stdout, 'write');
@ -124,7 +127,7 @@ describe('setup-node', () => {
});
it('can mock dist versions', async () => {
let versions: im.INodeVersion[] = await im.getVersionsFromDist();
let versions: nv.INodeVersion[] = await nv.getVersionsFromDist();
expect(versions).toBeDefined();
expect(versions?.length).toBe(23);
});
@ -517,10 +520,12 @@ describe('setup-node', () => {
// Arrange
const versionSpec = 'v12';
const versionFile = '.nvmrc';
const expectedVersionSpec = '12';
inputs['node-version-file'] = versionFile;
readFileSyncSpy.mockImplementation(() => versionSpec);
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
// Act
await main.run();
@ -531,8 +536,9 @@ describe('setup-node', () => {
path.join(__dirname, '..', versionFile),
'utf8'
);
expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec);
expect(logSpy).toHaveBeenCalledWith(
`Resolved ${versionFile} as ${versionSpec}`
`Resolved ${versionFile} as ${expectedVersionSpec}`
);
});
});

View file

@ -0,0 +1,88 @@
import * as nv from '../src/node-version';
import * as nvf from '../src/node-version-file';
let nodeTestDist = require('./data/node-dist-index.json');
describe('node-version-file', () => {
let getVersionsFromDist: jest.SpyInstance;
beforeEach(() => {
// @actions/core
console.log('::stop-commands::stoptoken'); // Disable executing of runner commands when running tests in actions
getVersionsFromDist = jest.spyOn(nv, 'getVersionsFromDist');
// gets
getVersionsFromDist.mockImplementation(() => <nv.INodeVersion>nodeTestDist);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
//jest.restoreAllMocks();
});
afterAll(async () => {
console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions
}, 100000);
//--------------------------------------------------
// Manifest find tests
//--------------------------------------------------
describe('parseNodeVersionFile', () => {
it('without `v` prefix', async () => {
// Arrange
const versionSpec = '12';
// Act
const result = await nvf.parseNodeVersionFile(versionSpec);
// Assert
expect(result).toBe(versionSpec);
});
it('lts/*', async () => {
// Arrange
const versionSpec = 'lts/*';
// Act
const result = await nvf.parseNodeVersionFile(versionSpec);
// Assert
expect(result).toMatch(/^\d+\.\d+\.\d+$/);
});
it('lts/erbium', async () => {
// Arrange
const versionSpec = 'lts/*';
// Act
const result = await nvf.parseNodeVersionFile(versionSpec);
// Assert
expect(result).toMatch(/\d\.\d\.\d/);
});
it('partial syntax like 12', async () => {
// Arrange
const versionSpec = '12';
// Act
const result = await nvf.parseNodeVersionFile(versionSpec);
// Assert
expect(result).toBe(versionSpec);
});
it('partial syntax like 12.16', async () => {
// Arrange
const versionSpec = '12.16';
// Act
const result = await nvf.parseNodeVersionFile(versionSpec);
// Assert
expect(result).toBe(versionSpec);
});
});
});

136
dist/index.js vendored
View file

@ -3028,6 +3028,82 @@ const windowsRelease = release => {
module.exports = windowsRelease;
/***/ }),
/***/ 74:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const semvar = __importStar(__webpack_require__(280));
const node_version_1 = __webpack_require__(708);
function parseNodeVersionFile(contents) {
return __awaiter(this, void 0, void 0, function* () {
contents = contents.trim();
if (/^v\d/.test(contents)) {
contents = contents.substring(1);
}
const nodeVersions = yield node_version_1.getVersionsFromDist();
let nodeVersion;
if (contents.startsWith('lts/')) {
nodeVersion = findLatestLts(nodeVersions, contents).version;
}
else if (semvar.valid(contents) || isPartialMatch(contents)) {
nodeVersion = contents;
}
else {
throw new Error(`Couldn't resolve node version: '${contents}'`);
}
return stripVPrefix(nodeVersion);
});
}
exports.parseNodeVersionFile = parseNodeVersionFile;
function findLatestLts(nodeVersions, codename) {
let nodeVersion;
if (codename === 'lts/*') {
nodeVersion = nodeVersions.reduce((latest, nodeVersion) => {
if (!nodeVersion.lts) {
return latest;
}
return semvar.gt(nodeVersion.version, latest.version)
? nodeVersion
: latest;
});
}
else {
codename = codename.replace('lts/', '').toLowerCase();
nodeVersion = nodeVersions.find(nodeVersion => `${nodeVersion.lts}`.toLowerCase() === codename);
}
if (!nodeVersion) {
throw new Error(`Couldn't find matching release for codename: '${codename}'`);
}
return nodeVersion;
}
function isPartialMatch(version) {
return /^\d+(\.\d+(\.\d+)?)?$/.test(version);
}
function stripVPrefix(version) {
return /^v\d/.test(version) ? version.substring(1) : version;
}
//# sourceMappingURL=node-version-file.js.map
/***/ }),
/***/ 82:
@ -4695,6 +4771,7 @@ const auth = __importStar(__webpack_require__(202));
const fs = __webpack_require__(747);
const path = __importStar(__webpack_require__(622));
const url_1 = __webpack_require__(835);
const node_version_file_1 = __webpack_require__(74);
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
@ -4710,7 +4787,7 @@ function run() {
const versionFile = core.getInput('node-version-file');
if (!!versionFile) {
const versionFilePath = path.join(__dirname, '..', versionFile);
version = fs.readFileSync(versionFilePath, 'utf8');
version = yield node_version_file_1.parseNodeVersionFile(fs.readFileSync(versionFilePath, 'utf8'));
core.info(`Resolved ${versionFile} as ${version}`);
}
}
@ -12969,6 +13046,45 @@ module.exports = {"activity":{"checkStarringRepo":{"method":"GET","params":{"own
/***/ }),
/***/ 708:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const hc = __importStar(__webpack_require__(539));
function getVersionsFromDist() {
return __awaiter(this, void 0, void 0, function* () {
let dataUrl = 'https://nodejs.org/dist/index.json';
let httpClient = new hc.HttpClient('setup-node', [], {
allowRetries: true,
maxRetries: 3
});
let response = yield httpClient.getJson(dataUrl);
return response.result || [];
});
}
exports.getVersionsFromDist = getVersionsFromDist;
//# sourceMappingURL=node-version.js.map
/***/ }),
/***/ 722:
/***/ (function(module) {
@ -13072,7 +13188,7 @@ module.exports = require("fs");
/***/ }),
/***/ 749:
/***/ (function(module, exports, __webpack_require__) {
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
@ -13096,12 +13212,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
const os = __webpack_require__(87);
const assert = __importStar(__webpack_require__(357));
const core = __importStar(__webpack_require__(470));
const hc = __importStar(__webpack_require__(539));
const io = __importStar(__webpack_require__(1));
const tc = __importStar(__webpack_require__(533));
const path = __importStar(__webpack_require__(622));
const semver = __importStar(__webpack_require__(280));
const fs = __webpack_require__(747);
const node_version_1 = __webpack_require__(708);
function getNode(versionSpec, stable, checkLatest, auth) {
return __awaiter(this, void 0, void 0, function* () {
let osPlat = os.platform();
@ -13312,7 +13428,7 @@ function queryDistForMatch(versionSpec) {
throw new Error(`Unexpected OS '${osPlat}'`);
}
let versions = [];
let nodeVersions = yield module.exports.getVersionsFromDist();
let nodeVersions = yield node_version_1.getVersionsFromDist();
nodeVersions.forEach((nodeVersion) => {
// ensure this version supports your os and platform
if (nodeVersion.files.indexOf(dataFileName) >= 0) {
@ -13324,18 +13440,6 @@ function queryDistForMatch(versionSpec) {
return version;
});
}
function getVersionsFromDist() {
return __awaiter(this, void 0, void 0, function* () {
let dataUrl = 'https://nodejs.org/dist/index.json';
let httpClient = new hc.HttpClient('setup-node', [], {
allowRetries: true,
maxRetries: 3
});
let response = yield httpClient.getJson(dataUrl);
return response.result || [];
});
}
exports.getVersionsFromDist = getVersionsFromDist;
// For non LTS versions of Node, the files we need (for Windows) are sometimes located
// in a different folder than they normally are for other versions.
// Normally the format is similar to: https://nodejs.org/dist/v5.10.1/node-v5.10.1-win-x64.7z

View file

@ -1,21 +1,12 @@
import os = require('os');
import * as assert from 'assert';
import * as core from '@actions/core';
import * as hc from '@actions/http-client';
import * as io from '@actions/io';
import * as tc from '@actions/tool-cache';
import * as path from 'path';
import * as semver from 'semver';
import fs = require('fs');
//
// Node versions interface
// see https://nodejs.org/dist/index.json
//
export interface INodeVersion {
version: string;
files: string[];
}
import {INodeVersion, getVersionsFromDist} from './node-version';
interface INodeVersionInfo {
downloadUrl: string;
@ -274,7 +265,7 @@ async function queryDistForMatch(versionSpec: string): Promise<string> {
}
let versions: string[] = [];
let nodeVersions = await module.exports.getVersionsFromDist();
let nodeVersions = await getVersionsFromDist();
nodeVersions.forEach((nodeVersion: INodeVersion) => {
// ensure this version supports your os and platform
@ -288,16 +279,6 @@ async function queryDistForMatch(versionSpec: string): Promise<string> {
return version;
}
export async function getVersionsFromDist(): Promise<INodeVersion[]> {
let dataUrl = 'https://nodejs.org/dist/index.json';
let httpClient = new hc.HttpClient('setup-node', [], {
allowRetries: true,
maxRetries: 3
});
let response = await httpClient.getJson<INodeVersion[]>(dataUrl);
return response.result || [];
}
// For non LTS versions of Node, the files we need (for Windows) are sometimes located
// in a different folder than they normally are for other versions.
// Normally the format is similar to: https://nodejs.org/dist/v5.10.1/node-v5.10.1-win-x64.7z

View file

@ -4,6 +4,7 @@ import * as auth from './authutil';
import fs = require('fs');
import * as path from 'path';
import {URL} from 'url';
import {parseNodeVersionFile} from './node-version-file';
export async function run() {
try {
@ -21,7 +22,9 @@ export async function run() {
if (!!versionFile) {
const versionFilePath = path.join(__dirname, '..', versionFile);
version = fs.readFileSync(versionFilePath, 'utf8');
version = await parseNodeVersionFile(
fs.readFileSync(versionFilePath, 'utf8')
);
core.info(`Resolved ${versionFile} as ${version}`);
}
}

65
src/node-version-file.ts Normal file
View file

@ -0,0 +1,65 @@
import * as semvar from 'semver';
import {INodeVersion, getVersionsFromDist} from './node-version';
export async function parseNodeVersionFile(contents: string): Promise<string> {
contents = contents.trim();
if (/^v\d/.test(contents)) {
contents = contents.substring(1);
}
const nodeVersions = await getVersionsFromDist();
let nodeVersion: string;
if (contents.startsWith('lts/')) {
nodeVersion = findLatestLts(nodeVersions, contents).version;
} else if (semvar.valid(contents) || isPartialMatch(contents)) {
nodeVersion = contents;
} else {
throw new Error(`Couldn't resolve node version: '${contents}'`);
}
return stripVPrefix(nodeVersion);
}
function findLatestLts(
nodeVersions: INodeVersion[],
codename: string
): INodeVersion {
let nodeVersion: INodeVersion | undefined;
if (codename === 'lts/*') {
nodeVersion = nodeVersions.reduce((latest, nodeVersion) => {
if (!nodeVersion.lts) {
return latest;
}
return semvar.gt(nodeVersion.version, latest.version)
? nodeVersion
: latest;
});
} else {
codename = codename.replace('lts/', '').toLowerCase();
nodeVersion = nodeVersions.find(
nodeVersion => `${nodeVersion.lts}`.toLowerCase() === codename
);
}
if (!nodeVersion) {
throw new Error(
`Couldn't find matching release for codename: '${codename}'`
);
}
return nodeVersion;
}
function isPartialMatch(version: string): boolean {
return /^\d+(\.\d+(\.\d+)?)?$/.test(version);
}
function stripVPrefix(version: string): string {
return /^v\d/.test(version) ? version.substring(1) : version;
}

21
src/node-version.ts Normal file
View file

@ -0,0 +1,21 @@
import * as hc from '@actions/http-client';
//
// Node versions interface
// see https://nodejs.org/dist/index.json
//
export interface INodeVersion {
version: string;
files: string[];
lts: boolean | string;
}
export async function getVersionsFromDist(): Promise<INodeVersion[]> {
let dataUrl = 'https://nodejs.org/dist/index.json';
let httpClient = new hc.HttpClient('setup-node', [], {
allowRetries: true,
maxRetries: 3
});
let response = await httpClient.getJson<INodeVersion[]>(dataUrl);
return response.result || [];
}