diff --git a/package-lock.json b/package-lock.json index 5d9d8afb8c7..528f4bfd9e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,6 @@ "puppeteer": "~10.0.0", "rollup": "2.42.3", "rollup-plugin-sourcemaps": "^0.6.3", - "semiver": "^1.1.0", "semver": "7.3.4", "sizzle": "^2.3.6", "terser": "5.6.1", @@ -9979,15 +9978,6 @@ "node": ">= 4" } }, - "node_modules/semiver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", - "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -20161,12 +20151,6 @@ "ajv-keywords": "^3.1.0" } }, - "semiver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", - "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", - "dev": true - }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", diff --git a/package.json b/package.json index d16ad868491..31bc28464ca 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,6 @@ "puppeteer": "~10.0.0", "rollup": "2.42.3", "rollup-plugin-sourcemaps": "^0.6.3", - "semiver": "^1.1.0", "semver": "7.3.4", "sizzle": "^2.3.6", "terser": "5.6.1", diff --git a/src/sys/node/node-lazy-require.ts b/src/sys/node/node-lazy-require.ts index 865084fab91..ce408b0f63e 100644 --- a/src/sys/node/node-lazy-require.ts +++ b/src/sys/node/node-lazy-require.ts @@ -1,43 +1,61 @@ import type * as d from '../../declarations'; import { buildError } from '@utils'; import { NodeResolveModule } from './node-resolve-module'; -import semiver from 'semiver'; import fs from 'graceful-fs'; import path from 'path'; +import satisfies from 'semver/functions/satisfies'; +import major from 'semver/functions/major'; +/** + * The version range that we support for a given package + * [0] is the lower end, while [1] is the higher end. + * + * These strings should be standard semver strings. + */ +type NodeVersionRange = [string, string]; + +/** + * A manifest for lazily-loaded dependencies, mapping dependency names + * to version ranges. + */ +type LazyDependencies = Record; + +/** + * Lazy requirer for Node, with functionality for specifying version ranges + * and returning diagnostic errors if requirements aren't met. + */ export class NodeLazyRequire implements d.LazyRequire { private ensured = new Set(); - constructor( - private nodeResolveModule: NodeResolveModule, - private lazyDependencies: { [dep: string]: [string, string] } - ) {} + constructor(private nodeResolveModule: NodeResolveModule, private lazyDependencies: LazyDependencies) {} async ensure(fromDir: string, ensureModuleIds: string[]) { const diagnostics: d.Diagnostic[] = []; - const missingDeps: string[] = []; + const problemDeps: string[] = []; ensureModuleIds.forEach((ensureModuleId) => { if (!this.ensured.has(ensureModuleId)) { - const [minVersion, recommendedVersion] = this.lazyDependencies[ensureModuleId]; + const [minVersion, maxVersion] = this.lazyDependencies[ensureModuleId]; + try { const pkgJsonPath = this.nodeResolveModule.resolveModule(fromDir, ensureModuleId); - const installedPkgJson: d.PackageJsonData = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); - if (semiver(installedPkgJson.version, minVersion) >= 0) { + if (satisfies(installedPkgJson.version, `${minVersion} - ${major(maxVersion)}.x`)) { this.ensured.add(ensureModuleId); return; } } catch (e) {} - missingDeps.push(`${ensureModuleId}@${recommendedVersion}`); + // if we get here we didn't get to the `return` above, so either 1) there was some error + // reading the package.json or 2) the version wasn't in our specified version range. + problemDeps.push(`${ensureModuleId}@${maxVersion}`); } }); - if (missingDeps.length > 0) { + if (problemDeps.length > 0) { const err = buildError(diagnostics); - err.header = `Please install missing dev dependencies with either npm or yarn.`; - err.messageText = `npm install --save-dev ${missingDeps.join(' ')}`; + err.header = `Please install supported versions of dev dependencies with either npm or yarn.`; + err.messageText = `npm install --save-dev ${problemDeps.join(' ')}`; } return diagnostics; diff --git a/src/sys/node/node-stencil-version-checker.ts b/src/sys/node/node-stencil-version-checker.ts index 0ddb70bd172..903b6022b31 100644 --- a/src/sys/node/node-stencil-version-checker.ts +++ b/src/sys/node/node-stencil-version-checker.ts @@ -2,7 +2,7 @@ import type { Logger, PackageJsonData } from '../../declarations'; import { isString, noop } from '@utils'; import fs from 'graceful-fs'; import path from 'path'; -import semiver from 'semiver'; +import semverLt from 'semver/functions/lt'; import { tmpdir } from 'os'; const REGISTRY_URL = `https://registry.npmjs.org/@stencil/core`; @@ -14,7 +14,7 @@ export async function checkVersion(logger: Logger, currentVersion: string): Prom const latestVersion = await getLatestCompilerVersion(logger); if (latestVersion != null) { return () => { - if (semiver(currentVersion, latestVersion) < 0) { + if (semverLt(currentVersion, latestVersion)) { printUpdateMessage(logger, currentVersion, latestVersion); } else { console.debug( diff --git a/src/sys/node/test/node-lazy-require.spec.ts b/src/sys/node/test/node-lazy-require.spec.ts new file mode 100644 index 00000000000..01da8eaa978 --- /dev/null +++ b/src/sys/node/test/node-lazy-require.spec.ts @@ -0,0 +1,58 @@ +import { NodeLazyRequire } from '../node-lazy-require'; +import { buildError } from '@utils'; +import { NodeResolveModule } from '../node-resolve-module'; +import fs from 'graceful-fs'; + +const mockPackageJson = (version: string) => + JSON.stringify({ + version, + }); + +describe('node-lazy-require', () => { + describe('NodeLazyRequire', () => { + function setup() { + const resolveModule = new NodeResolveModule(); + const readFSMock = jest.spyOn(fs, 'readFileSync').mockReturnValue(mockPackageJson('10.10.10')); + + const nodeLazyRequire = new NodeLazyRequire(resolveModule, { + jest: ['2.0.7', '38.0.1'], + }); + return { + nodeLazyRequire, + readFSMock, + }; + } + + it.each(['2.0.7', '10.10.10', '38.0.1'])( + 'should not error if a package of suitable version (%p) is installed', + async (testVersion) => { + const { nodeLazyRequire, readFSMock } = setup(); + readFSMock.mockReturnValue(mockPackageJson(testVersion)); + let diagnostics = await nodeLazyRequire.ensure('.', ['jest']); + expect(diagnostics.length).toBe(0); + } + ); + + it('should error if the installed version of a package is too low', async () => { + const { nodeLazyRequire, readFSMock } = setup(); + readFSMock.mockReturnValue(mockPackageJson('1.1.1')); + let [error] = await nodeLazyRequire.ensure('.', ['jest']); + expect(error).toEqual({ + ...buildError([]), + header: 'Please install supported versions of dev dependencies with either npm or yarn.', + messageText: 'npm install --save-dev jest@38.0.1', + }); + }); + + it('should error if the installed version of a package is too high', async () => { + const { nodeLazyRequire, readFSMock } = setup(); + readFSMock.mockReturnValue(mockPackageJson('100.1.1')); + let [error] = await nodeLazyRequire.ensure('.', ['jest']); + expect(error).toEqual({ + ...buildError([]), + header: 'Please install supported versions of dev dependencies with either npm or yarn.', + messageText: 'npm install --save-dev jest@38.0.1', + }); + }); + }); +});