Skip to content

Commit

Permalink
Make build scripts find and use the latest version of Node.js that sa…
Browse files Browse the repository at this point in the history
…tisfies `engines.node` (#3467) (#3468)

* While building distributables, Node.js runtime is downloaded to be placed in the archivea. This logicwas modified to honor a range for `engines.node` by fetching the latest release of Node.js that satisfied the range.
* Some tests covering the build, read a version from `.node-version` to compare with the results of actual function runs; these were changed to either use mocked values or honor the range and use the latest Node.js version.
* Some variable and functions referred to `engines.node` as a version; they were corrected to call it a range.

Signed-off-by: Miki <miki@amazon.com>
(cherry picked from commit f5ee06d)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 17ca299 commit 7658a92
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 46 deletions.
6 changes: 3 additions & 3 deletions src/dev/build/lib/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ describe('#getOpenSearchDashboardsPkg()', () => {
});
});

describe('#getNodeVersion()', () => {
it('returns the node version from the OpenSearch Dashboards package.json', async () => {
describe('#getNodeRange()', () => {
it('returns the node version range from the OpenSearch Dashboards package.json', async () => {
const config = await setup();
expect(config.getNodeVersion()).toEqual(pkg.engines.node);
expect(config.getNodeRange()).toEqual(pkg.engines.node);
});
});

Expand Down
6 changes: 3 additions & 3 deletions src/dev/build/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class Config {
private readonly targetAllPlatforms: boolean,
private readonly targetPlatforms: TargetPlatforms,
private readonly pkg: Package,
private readonly nodeVersion: string,
private readonly nodeRange: string,
private readonly repoRoot: string,
private readonly versionInfo: VersionInfo,
public readonly isRelease: boolean
Expand All @@ -102,8 +102,8 @@ export class Config {
/**
* Get the node version required by OpenSearch Dashboards
*/
getNodeVersion() {
return this.nodeVersion;
getNodeRange() {
return this.nodeRange;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/dev/build/tasks/create_archives_sources_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const CreateArchivesSources: Task = {

// copy node.js install
await scanCopy({
source: getNodeDownloadInfo(config, platform).extractDir,
source: (await getNodeDownloadInfo(config, platform)).extractDir,
destination: build.resolvePathForPlatform(platform, 'node'),
});

Expand Down
7 changes: 4 additions & 3 deletions src/dev/build/tasks/nodejs/download_node_builds_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@

import { download, GlobalTask } from '../../lib';
import { getNodeShasums } from './node_shasums';
import { getNodeDownloadInfo } from './node_download_info';
import { getLatestNodeVersion, getNodeDownloadInfo } from './node_download_info';

export const DownloadNodeBuilds: GlobalTask = {
global: true,
description: 'Downloading node.js builds for all platforms',
async run(config, log) {
const shasums = await getNodeShasums(log, config.getNodeVersion());
const latestNodeVersion = await getLatestNodeVersion(config);
const shasums = await getNodeShasums(log, latestNodeVersion);
await Promise.all(
config.getTargetPlatforms().map(async (platform) => {
const { url, downloadPath, downloadName } = getNodeDownloadInfo(config, platform);
const { url, downloadPath, downloadName } = await getNodeDownloadInfo(config, platform);
await download({
log,
url,
Expand Down
23 changes: 11 additions & 12 deletions src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@
* under the License.
*/

import { readFileSync } from 'fs';
import Path from 'path';

import { REPO_ROOT } from '@osd/utils';
import {
ToolingLog,
ToolingLogCollectingWriter,
Expand All @@ -41,6 +37,7 @@ import {

import { Config } from '../../lib';
import { ExtractNodeBuilds } from './extract_node_builds_task';
import { getLatestNodeVersion } from './node_download_info';

jest.mock('../../lib/fs');
jest.mock('../../lib/get_build_number');
Expand All @@ -53,14 +50,6 @@ log.setWriters([testWriter]);

expect.addSnapshotSerializer(createAbsolutePathSerializer());

const nodeVersion = readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim();
expect.addSnapshotSerializer(
createRecursiveSerializer(
(s) => typeof s === 'string' && s.includes(nodeVersion),
(s) => s.split(nodeVersion).join('<node version>')
)
);

async function setup() {
const config = await Config.create({
isRelease: true,
Expand All @@ -73,6 +62,16 @@ async function setup() {
},
});

const realNodeVersion = await getLatestNodeVersion(config);
if (realNodeVersion) {
expect.addSnapshotSerializer(
createRecursiveSerializer(
(s) => typeof s === 'string' && s.includes(realNodeVersion),
(s) => s.split(realNodeVersion).join('<node version>')
)
);
}

return { config };
}

Expand Down
2 changes: 1 addition & 1 deletion src/dev/build/tasks/nodejs/extract_node_builds_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const ExtractNodeBuilds: GlobalTask = {
async run(config) {
await Promise.all(
config.getTargetPlatforms().map(async (platform) => {
const { downloadPath, extractDir } = getNodeDownloadInfo(config, platform);
const { downloadPath, extractDir } = await getNodeDownloadInfo(config, platform);
if (platform.isWindows()) {
await unzip(downloadPath, extractDir, { strip: 1 });
} else {
Expand Down
28 changes: 26 additions & 2 deletions src/dev/build/tasks/nodejs/node_download_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@
*/

import { basename } from 'path';
import fetch from 'node-fetch';
import semver from 'semver';

import { Config, Platform } from '../../lib';

export function getNodeDownloadInfo(config: Config, platform: Platform) {
const version = config.getNodeVersion();
const NODE_RANGE_CACHE: { [key: string]: string } = {};

export async function getNodeDownloadInfo(config: Config, platform: Platform) {
const version = await getLatestNodeVersion(config);
const arch = platform.getNodeArch();

const downloadName = platform.isWindows()
Expand All @@ -52,3 +56,23 @@ export function getNodeDownloadInfo(config: Config, platform: Platform) {
version,
};
}

export async function getLatestNodeVersion(config: Config) {
const range = config.getNodeRange();
// Check cache and return if known
if (NODE_RANGE_CACHE[range]) return NODE_RANGE_CACHE[range];

const releaseDoc = await fetch('https://nodejs.org/dist/index.json');
const releaseList: [{ version: string }] = await releaseDoc.json();
const releases = releaseList.map(({ version }) => version.replace(/^v/, ''));
const maxVersion = semver.maxSatisfying(releases, range);

if (!maxVersion) {
throw new Error(`Cannot find a version of Node.js that satisfies ${range}.`);
}

// Cache it
NODE_RANGE_CACHE[range] = maxVersion;

return maxVersion;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,10 @@
* under the License.
*/

import Path from 'path';
import Fs from 'fs';

import { REPO_ROOT } from '@osd/utils';
import {
ToolingLog,
ToolingLogCollectingWriter,
createAnyInstanceSerializer,
createRecursiveSerializer,
} from '@osd/dev-utils';

import { Config, Platform } from '../../lib';
Expand All @@ -48,7 +43,7 @@ jest.mock('../../lib/fs');
jest.mock('../../lib/get_build_number');

const { getNodeShasums } = jest.requireMock('./node_shasums');
const { getNodeDownloadInfo } = jest.requireMock('./node_download_info');
const { getNodeDownloadInfo, getLatestNodeVersion } = jest.requireMock('./node_download_info');
const { getFileHash } = jest.requireMock('../../lib/fs');

const log = new ToolingLog();
Expand All @@ -58,14 +53,6 @@ log.setWriters([testWriter]);
expect.addSnapshotSerializer(createAnyInstanceSerializer(Config));
expect.addSnapshotSerializer(createAnyInstanceSerializer(ToolingLog));

const nodeVersion = Fs.readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim();
expect.addSnapshotSerializer(
createRecursiveSerializer(
(s) => typeof s === 'string' && s.includes(nodeVersion),
(s) => s.split(nodeVersion).join('<node version>')
)
);

async function setup(actualShaSums?: Record<string, string>) {
const config = await Config.create({
isRelease: true,
Expand All @@ -74,6 +61,7 @@ async function setup(actualShaSums?: Record<string, string>) {
linux: false,
linuxArm: false,
darwin: false,
windows: false,
},
});

Expand All @@ -89,9 +77,12 @@ async function setup(actualShaSums?: Record<string, string>) {
return {
downloadPath: `${platform.getName()}:${platform.getNodeArch()}:downloadPath`,
downloadName: `${platform.getName()}:${platform.getNodeArch()}:downloadName`,
version: '<node version>',
};
});

getLatestNodeVersion.mockReturnValue('<node version>');

getFileHash.mockImplementation((downloadPath: string) => {
if (actualShaSums?.[downloadPath]) {
return actualShaSums[downloadPath];
Expand Down Expand Up @@ -176,27 +167,31 @@ it('checks shasums for each downloaded node build', async () => {
"value": Object {
"downloadName": "linux:linux-x64:downloadName",
"downloadPath": "linux:linux-x64:downloadPath",
"version": "<node version>",
},
},
Object {
"type": "return",
"value": Object {
"downloadName": "linux:linux-arm64:downloadName",
"downloadPath": "linux:linux-arm64:downloadPath",
"version": "<node version>",
},
},
Object {
"type": "return",
"value": Object {
"downloadName": "darwin:darwin-x64:downloadName",
"downloadPath": "darwin:darwin-x64:downloadPath",
"version": "<node version>",
},
},
Object {
"type": "return",
"value": Object {
"downloadName": "win32:win32-x64:downloadName",
"downloadPath": "win32:win32-x64:downloadPath",
"version": "<node version>",
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,19 @@
*/

import { getFileHash, GlobalTask } from '../../lib';
import { getNodeDownloadInfo } from './node_download_info';
import { getNodeDownloadInfo, getLatestNodeVersion } from './node_download_info';
import { getNodeShasums } from './node_shasums';

export const VerifyExistingNodeBuilds: GlobalTask = {
global: true,
description: 'Verifying previously downloaded node.js build for all platforms',
async run(config, log) {
const shasums = await getNodeShasums(log, config.getNodeVersion());
const latestNodeVersion = await getLatestNodeVersion(config);
const shasums = await getNodeShasums(log, latestNodeVersion);

await Promise.all(
config.getTargetPlatforms().map(async (platform) => {
const { downloadPath, downloadName } = getNodeDownloadInfo(config, platform);
const { downloadPath, downloadName } = await getNodeDownloadInfo(config, platform);

const sha256 = await getFileHash(downloadPath, 'sha256');
if (sha256 !== shasums[downloadName]) {
Expand Down
2 changes: 1 addition & 1 deletion src/dev/build/tasks/notice_file_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const CreateNoticeFile: Task = {

log.info('Generating build notice');

const { extractDir: nodeDir, version: nodeVersion } = getNodeDownloadInfo(
const { extractDir: nodeDir, version: nodeVersion } = await getNodeDownloadInfo(
config,
config.hasSpecifiedPlatform()
? config.getPlatform(
Expand Down
9 changes: 6 additions & 3 deletions src/dev/build/tasks/verify_env_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,20 @@
* under the License.
*/

import semver from 'semver';
import { GlobalTask } from '../lib';

export const VerifyEnv: GlobalTask = {
global: true,
description: 'Verifying environment meets requirements',

async run(config, log) {
const version = `v${config.getNodeVersion()}`;
const range = config.getNodeRange();

if (version !== process.version) {
throw new Error(`Invalid nodejs version, please use ${version}`);
if (!semver.satisfies(process.version, range)) {
throw new Error(
`Invalid Node.js version (${process.version}); please use a version that satisfies ${range}.`
);
}

log.success('Node.js version verified');
Expand Down

0 comments on commit 7658a92

Please sign in to comment.