Skip to content

Commit

Permalink
Fix minimum version for CLIs (#2292)
Browse files Browse the repository at this point in the history
Fixes the checks for minimum versions in the CLI. 

The minimum Node.js version we support is `18.16`, this was not
reflected properly in our CLIs. After this PR the CLIs will check the
minimum version themselves by looking in the package.json instead of
relying on a constant.
  • Loading branch information
FrederikBolding authored Mar 20, 2024
1 parent 3b94315 commit 236e90f
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 67 deletions.
1 change: 1 addition & 0 deletions packages/create-snap/.depcheckrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@lavamoat/preinstall-always-fail",
"@metamask/auto-changelog",
"@metamask/eslint-*",
"@metamask/create-snap",
"@types/*",
"@typescript-eslint/*",
"eslint-config-*",
Expand Down
2 changes: 1 addition & 1 deletion packages/create-snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
"dependencies": {
"@metamask/snaps-utils": "workspace:^",
"@metamask/utils": "^8.3.0",
"semver": "^7.5.4",
"yargs": "^17.7.1"
},
"devDependencies": {
Expand Down
24 changes: 12 additions & 12 deletions packages/create-snap/src/cmds/init/initHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
getMockSnapFiles,
getMockSnapFilesWithUpdatedChecksum,
} from '@metamask/snaps-utils/test-utils';
import * as utils from '@metamask/utils';
import { promises as fs } from 'fs';
import pathUtils from 'path';
import semver from 'semver';

import { resetFileSystem } from '../../test-utils';
import type { YargsArgs } from '../../types/yargs';
Expand All @@ -15,9 +15,9 @@ import * as initUtils from './initUtils';

jest.mock('fs');

jest.mock('@metamask/utils', () => ({
...jest.requireActual('@metamask/utils'),
satisfiesVersionRange: jest.fn(),
jest.mock('semver', () => ({
...jest.requireActual('semver'),
satisfies: jest.fn(),
}));

jest.mock('@metamask/snaps-utils', () => ({
Expand Down Expand Up @@ -47,7 +47,7 @@ describe('initialize', () => {
});

it('successfully initializes a Snap project in the current working directory', async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);

jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true);
jest.spyOn(initUtils, 'cloneTemplate').mockImplementation();
Expand Down Expand Up @@ -82,7 +82,7 @@ describe('initialize', () => {
});

it('successfully initializes a Snap project in a given directory', async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);

jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true);

Expand Down Expand Up @@ -123,7 +123,7 @@ describe('initialize', () => {
});

it("defaults to 'src/index.js' if there is no main entry in package.json", async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);
jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true);

jest.spyOn(initUtils, 'cloneTemplate').mockImplementation();
Expand Down Expand Up @@ -163,7 +163,7 @@ describe('initialize', () => {
});

it("doesn't init if it's already in a git repo", async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);
jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true);

jest.spyOn(initUtils, 'cloneTemplate').mockImplementation();
Expand Down Expand Up @@ -212,12 +212,12 @@ describe('initialize', () => {
};

await expect(initHandler({ ...getMockArgv() })).rejects.toThrow(
`Init Error: You are using an outdated version of Node (${process.version}). Please update to Node >=18.6.0.`,
`Init Error: You are using an outdated version of Node (${process.version}). Please update to Node 18.16.0 or later.`,
);
});

it('fails if git is not installed', async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);

const isGitInstalledMock = jest
.spyOn(initUtils, 'isGitInstalled')
Expand All @@ -231,7 +231,7 @@ describe('initialize', () => {
});

it('fails if it can\t clone template and clean files', async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);

jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true);

Expand All @@ -249,7 +249,7 @@ describe('initialize', () => {
});

it('fails if an error is thrown', async () => {
jest.spyOn(utils, 'satisfiesVersionRange').mockImplementation(() => true);
jest.spyOn(semver, 'satisfies').mockImplementation(() => true);

jest.spyOn(initUtils, 'isGitInstalled').mockImplementation(() => true);

Expand Down
18 changes: 9 additions & 9 deletions packages/create-snap/src/cmds/init/initHandler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import cliPackageJson from '@metamask/create-snap/package.json';
import type { NpmSnapPackageJson } from '@metamask/snaps-utils';
import {
NpmSnapFileNames,
readJsonFile,
createSnapManifest,
logInfo,
} from '@metamask/snaps-utils/node';
import type { SemVerRange, SemVerVersion } from '@metamask/utils';
import { satisfiesVersionRange } from '@metamask/utils';
import { promises as fs } from 'fs';
import pathUtils from 'path';
import type { SemVer } from 'semver';
import semver from 'semver';

import type { YargsArgs } from '../../types/yargs';
import {
Expand All @@ -22,8 +24,6 @@ import {
yarnInstall,
} from './initUtils';

const SATISFIED_VERSION = '>=18.6.0' as SemVerRange;

/**
* Creates a new snap package, based on one of the provided templates. This
* creates all the necessary files, like `package.json`, `snap.config.js`, etc.
Expand All @@ -37,14 +37,14 @@ const SATISFIED_VERSION = '>=18.6.0' as SemVerRange;
export async function initHandler(argv: YargsArgs) {
const { directory } = argv;

const isVersionSupported = satisfiesVersionRange(
process.version as SemVerVersion,
SATISFIED_VERSION,
);
const versionRange = cliPackageJson.engines.node;
const minimumVersion = (semver.minVersion(versionRange) as SemVer).format();

const isVersionSupported = semver.satisfies(process.version, versionRange);

if (!isVersionSupported) {
throw new Error(
`Init Error: You are using an outdated version of Node (${process.version}). Please update to Node ${SATISFIED_VERSION}.`,
`Init Error: You are using an outdated version of Node (${process.version}). Please update to Node ${minimumVersion} or later.`,
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/create-snap/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { default: baseConfig } = require('../../tsup.config');

const config: Options = {
name: packageJson.name,
external: ['@metamask/create-snap'],
platform: 'node',
};

Expand Down
27 changes: 4 additions & 23 deletions packages/snaps-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const getMockArgv = (...args: string[]) => {
const HELP_TEXT_REGEX = /^\s*Usage: .+ <command> \[options\]/u;

describe('checkNodeVersion', () => {
it.each(['16.17.0', '16.18.0', '18.6.0', '18.7.0', '20.0.0'])(
it.each(['18.16.0', '18.17.0', '20.0.0'])(
'does not exit if the Node version is %s',
(version) => {
const spy = jest.spyOn(process, 'exit').mockImplementation();
Expand All @@ -38,8 +38,8 @@ describe('checkNodeVersion', () => {
},
);

it.each(['14.0.0', '16.0.0', '16.16.1'])(
'logs a message and exists if the Node version is %s',
it.each(['14.0.0', '16.0.0', '16.16.1', '18.0.0', '18.5.0'])(
'logs a message and exits if the Node version is %s',
(version) => {
const spy = jest.spyOn(process, 'exit').mockImplementation();
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
Expand All @@ -51,26 +51,7 @@ describe('checkNodeVersion', () => {
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Node version ${version} is not supported. Please use Node 16.17.0 or later.`,
),
);
},
);

it.each(['18.0.0', '18.5.0'])(
'logs a message and exists if the Node version is %s',
(version) => {
const spy = jest.spyOn(process, 'exit').mockImplementation();
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();

checkNodeVersion(version);

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(1);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Node version ${version} is not supported. Please use Node 18.6.0 or later.`,
`Node version ${version} is not supported. Please use Node 18.16.0 or later.`,
),
);
},
Expand Down
28 changes: 7 additions & 21 deletions packages/snaps-cli/src/cli.ts
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import packageJson from '@metamask/snaps-cli/package.json';
import type { SemVer } from 'semver';
import semver from 'semver';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
Expand All @@ -6,9 +9,6 @@ import builders from './builders';
import { getConfigByArgv } from './config';
import { error, getYargsErrorMessage, sanitizeInputs } from './utils';

const MINIMUM_NODE_16_VERSION = '16.17.0';
const MINIMUM_NODE_18_VERSION = '18.6.0';

/**
* Check the Node version. If the Node version is less than the minimum required
* version, this logs an error and exits the process.
Expand All @@ -18,26 +18,12 @@ const MINIMUM_NODE_18_VERSION = '18.6.0';
export function checkNodeVersion(
nodeVersion: string = process.version.slice(1),
) {
const majorVersion = semver.major(nodeVersion);
const message = `Node version ${nodeVersion} is not supported. Please use Node ${MINIMUM_NODE_16_VERSION} or later.`;

if (majorVersion < 16) {
error(message);
// eslint-disable-next-line n/no-process-exit
process.exit(1);
}

// Node 16 and 18 have a different minimum version requirement, so we need to
// check for both.
if (majorVersion === 16 && semver.lt(nodeVersion, MINIMUM_NODE_16_VERSION)) {
error(message);
// eslint-disable-next-line n/no-process-exit
process.exit(1);
}
const versionRange = packageJson.engines.node;
const minimumVersion = (semver.minVersion(versionRange) as SemVer).format();

if (majorVersion === 18 && semver.lt(nodeVersion, MINIMUM_NODE_18_VERSION)) {
if (!semver.satisfies(nodeVersion, versionRange)) {
error(
`Node version ${nodeVersion} is not supported. Please use Node ${MINIMUM_NODE_18_VERSION} or later.`,
`Node version ${nodeVersion} is not supported. Please use Node ${minimumVersion} or later.`,
);
// eslint-disable-next-line n/no-process-exit
process.exit(1);
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4205,7 +4205,6 @@ __metadata:
"@metamask/eslint-config-nodejs": ^12.1.0
"@metamask/eslint-config-typescript": ^12.1.0
"@metamask/snaps-utils": "workspace:^"
"@metamask/utils": ^8.3.0
"@swc/core": 1.3.78
"@swc/jest": ^0.2.26
"@types/jest": ^27.5.1
Expand All @@ -4230,6 +4229,7 @@ __metadata:
prettier: ^2.7.1
prettier-plugin-packagejson: ^2.2.11
rimraf: ^4.1.2
semver: ^7.5.4
ts-node: ^10.9.1
tsc-watch: ^4.5.0
tsup: ^8.0.1
Expand Down

0 comments on commit 236e90f

Please sign in to comment.