Skip to content

Commit b7adc33

Browse files
fix(sys): make NodeLazyRequire complain if package versions aren't right (#3346)
this updates a bit of logic in `NodeLazyRequire.ensure` to check that the installed versions of packages are within the specified version range, i.e. that minVersion <= installedVersion <= maxVersion. this commit also adds tests for that module. STENCIL-391: bug: @stencil/core does not throw error when missing jest/jest-cli deps in a rush/pnpm monorepo
1 parent ad1b68a commit b7adc33

7 files changed

+182
-44
lines changed

NOTICE.md

+62-10
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,30 @@ Homepage: https://lodash.com/
14721472
14731473
--------
14741474

1475+
## `lru-cache`
1476+
1477+
License: ISC
1478+
1479+
Author: Isaac Z. Schlueter <i@izs.me>
1480+
1481+
> The ISC License
1482+
>
1483+
> Copyright (c) Isaac Z. Schlueter and Contributors
1484+
>
1485+
> Permission to use, copy, modify, and/or distribute this software for any
1486+
> purpose with or without fee is hereby granted, provided that the above
1487+
> copyright notice and this permission notice appear in all copies.
1488+
>
1489+
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1490+
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1491+
> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1492+
> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1493+
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1494+
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
1495+
> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1496+
1497+
--------
1498+
14751499
## `magic-string`
14761500

14771501
License: MIT
@@ -2822,21 +2846,25 @@ Homepage: https://rollupjs.org/
28222846
28232847
--------
28242848

2825-
## `semiver`
2849+
## `semver`
28262850

2827-
License: MIT
2828-
2829-
Author: [Luke Edwards](lukeed.com)
2851+
License: ISC
28302852

2831-
> MIT License
2832-
>
2833-
> Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
2853+
> The ISC License
28342854
>
2835-
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2855+
> Copyright (c) Isaac Z. Schlueter and Contributors
28362856
>
2837-
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2857+
> Permission to use, copy, modify, and/or distribute this software for any
2858+
> purpose with or without fee is hereby granted, provided that the above
2859+
> copyright notice and this permission notice appear in all copies.
28382860
>
2839-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2861+
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
2862+
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
2863+
> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
2864+
> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
2865+
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
2866+
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
2867+
> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
28402868
28412869
--------
28422870

@@ -3263,3 +3291,27 @@ Homepage: https://github.com/websockets/ws
32633291
> SOFTWARE.
32643292
32653293
--------
3294+
3295+
## `yallist`
3296+
3297+
License: ISC
3298+
3299+
Author: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
3300+
3301+
> The ISC License
3302+
>
3303+
> Copyright (c) Isaac Z. Schlueter and Contributors
3304+
>
3305+
> Permission to use, copy, modify, and/or distribute this software for any
3306+
> purpose with or without fee is hereby granted, provided that the above
3307+
> copyright notice and this permission notice appear in all copies.
3308+
>
3309+
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
3310+
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
3311+
> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
3312+
> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
3313+
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
3314+
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
3315+
> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3316+
3317+
--------

package-lock.json

-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@
119119
"puppeteer": "~10.0.0",
120120
"rollup": "2.42.3",
121121
"rollup-plugin-sourcemaps": "^0.6.3",
122-
"semiver": "^1.1.0",
123122
"semver": "7.3.4",
124123
"sizzle": "^2.3.6",
125124
"terser": "5.6.1",

scripts/license.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const entryDeps = [
2929
'postcss',
3030
'prompts',
3131
'rollup',
32-
'semiver',
32+
'semver',
3333
'sizzle',
3434
'source-map',
3535
'terser',

src/sys/node/node-lazy-require.ts

+49-14
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,78 @@
11
import type * as d from '../../declarations';
22
import { buildError } from '@utils';
33
import { NodeResolveModule } from './node-resolve-module';
4-
import semiver from 'semiver';
54
import fs from 'graceful-fs';
65
import path from 'path';
6+
import satisfies from 'semver/functions/satisfies';
7+
import major from 'semver/functions/major';
78

9+
/**
10+
* The version range that we support for a given package
11+
* [0] is the lower end, while [1] is the higher end.
12+
*
13+
* These strings should be standard semver strings.
14+
*/
15+
type NodeVersionRange = [string, string];
16+
17+
/**
18+
* A manifest for lazily-loaded dependencies, mapping dependency names
19+
* to version ranges.
20+
*/
21+
type LazyDependencies = Record<string, NodeVersionRange>;
22+
23+
/**
24+
* Lazy requirer for Node, with functionality for specifying version ranges
25+
* and returning diagnostic errors if requirements aren't met.
26+
*/
827
export class NodeLazyRequire implements d.LazyRequire {
928
private ensured = new Set<string>();
1029

11-
constructor(
12-
private nodeResolveModule: NodeResolveModule,
13-
private lazyDependencies: { [dep: string]: [string, string] }
14-
) {}
30+
/**
31+
* Create a NodeLazyRequire instance
32+
*
33+
* @param nodeResolveModule an object which wraps up module resolution functionality
34+
* @param lazyDependencies the dependency requirements we want to enforce here
35+
*/
36+
constructor(private nodeResolveModule: NodeResolveModule, private lazyDependencies: LazyDependencies) {}
1537

16-
async ensure(fromDir: string, ensureModuleIds: string[]) {
38+
/**
39+
* Ensure that a dependency within our supported range is installed in the current
40+
* environment. This function will check all the dependency requirements passed in when
41+
* the class is instantiated and return diagnostics if there are any issues.
42+
*
43+
* @param fromDir the directory from which we'll attempt to resolve the dependencies, typically
44+
* this will be project's root directory.
45+
* @param ensureModuleIds an array of module names whose versions we're going to check
46+
* @returns a Promise holding diagnostics if any of the dependencies either were not
47+
* resolved _or_ did not meet our version requirements.
48+
*/
49+
async ensure(fromDir: string, ensureModuleIds: string[]): Promise<d.Diagnostic[]> {
1750
const diagnostics: d.Diagnostic[] = [];
18-
const missingDeps: string[] = [];
51+
const problemDeps: string[] = [];
1952

2053
ensureModuleIds.forEach((ensureModuleId) => {
2154
if (!this.ensured.has(ensureModuleId)) {
22-
const [minVersion, recommendedVersion] = this.lazyDependencies[ensureModuleId];
55+
const [minVersion, maxVersion] = this.lazyDependencies[ensureModuleId];
56+
2357
try {
2458
const pkgJsonPath = this.nodeResolveModule.resolveModule(fromDir, ensureModuleId);
25-
2659
const installedPkgJson: d.PackageJsonData = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
2760

28-
if (semiver(installedPkgJson.version, minVersion) >= 0) {
61+
if (satisfies(installedPkgJson.version, `${minVersion} - ${major(maxVersion)}.x`)) {
2962
this.ensured.add(ensureModuleId);
3063
return;
3164
}
3265
} catch (e) {}
33-
missingDeps.push(`${ensureModuleId}@${recommendedVersion}`);
66+
// if we get here we didn't get to the `return` above, so either 1) there was some error
67+
// reading the package.json or 2) the version wasn't in our specified version range.
68+
problemDeps.push(`${ensureModuleId}@${maxVersion}`);
3469
}
3570
});
3671

37-
if (missingDeps.length > 0) {
72+
if (problemDeps.length > 0) {
3873
const err = buildError(diagnostics);
39-
err.header = `Please install missing dev dependencies with either npm or yarn.`;
40-
err.messageText = `npm install --save-dev ${missingDeps.join(' ')}`;
74+
err.header = `Please install supported versions of dev dependencies with either npm or yarn.`;
75+
err.messageText = `npm install --save-dev ${problemDeps.join(' ')}`;
4176
}
4277

4378
return diagnostics;

src/sys/node/node-stencil-version-checker.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Logger, PackageJsonData } from '../../declarations';
22
import { isString, noop } from '@utils';
33
import fs from 'graceful-fs';
44
import path from 'path';
5-
import semiver from 'semiver';
5+
import semverLt from 'semver/functions/lt';
66
import { tmpdir } from 'os';
77

88
const REGISTRY_URL = `https://registry.npmjs.org/@stencil/core`;
@@ -14,7 +14,7 @@ export async function checkVersion(logger: Logger, currentVersion: string): Prom
1414
const latestVersion = await getLatestCompilerVersion(logger);
1515
if (latestVersion != null) {
1616
return () => {
17-
if (semiver(currentVersion, latestVersion) < 0) {
17+
if (semverLt(currentVersion, latestVersion)) {
1818
printUpdateMessage(logger, currentVersion, latestVersion);
1919
} else {
2020
console.debug(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { NodeLazyRequire } from '../node-lazy-require';
2+
import { buildError } from '@utils';
3+
import { NodeResolveModule } from '../node-resolve-module';
4+
import fs from 'graceful-fs';
5+
6+
const mockPackageJson = (version: string) =>
7+
JSON.stringify({
8+
version,
9+
});
10+
11+
describe('node-lazy-require', () => {
12+
describe('NodeLazyRequire', () => {
13+
describe('ensure', () => {
14+
let readFSMock: jest.SpyInstance<ReturnType<typeof fs.readFileSync>, Parameters<typeof fs.readFileSync>>;
15+
16+
beforeEach(() => {
17+
readFSMock = jest.spyOn(fs, 'readFileSync').mockReturnValue(mockPackageJson('10.10.10'));
18+
});
19+
20+
afterEach(() => {
21+
readFSMock.mockClear();
22+
});
23+
24+
function setup() {
25+
const resolveModule = new NodeResolveModule();
26+
const nodeLazyRequire = new NodeLazyRequire(resolveModule, {
27+
jest: ['2.0.7', '38.0.1'],
28+
});
29+
return nodeLazyRequire;
30+
}
31+
32+
it.each(['2.0.7', '10.10.10', '38.0.1', '38.0.2', '38.5.17'])(
33+
'should not error if installed package has a suitable major version (%p)',
34+
async (testVersion) => {
35+
const nodeLazyRequire = setup();
36+
readFSMock.mockReturnValue(mockPackageJson(testVersion));
37+
let diagnostics = await nodeLazyRequire.ensure('.', ['jest']);
38+
expect(diagnostics.length).toBe(0);
39+
}
40+
);
41+
42+
it('should error if the installed version of a package is too low', async () => {
43+
const nodeLazyRequire = setup();
44+
readFSMock.mockReturnValue(mockPackageJson('1.1.1'));
45+
let [error] = await nodeLazyRequire.ensure('.', ['jest']);
46+
expect(error).toEqual({
47+
...buildError([]),
48+
header: 'Please install supported versions of dev dependencies with either npm or yarn.',
49+
messageText: 'npm install --save-dev jest@38.0.1',
50+
});
51+
});
52+
53+
it.each(['100.1.1', '38.0.1-alpha.0'])(
54+
'should error if the installed version of a package is too high (%p)',
55+
async (version) => {
56+
const nodeLazyRequire = setup();
57+
readFSMock.mockReturnValue(mockPackageJson(version));
58+
let [error] = await nodeLazyRequire.ensure('.', ['jest']);
59+
expect(error).toEqual({
60+
...buildError([]),
61+
header: 'Please install supported versions of dev dependencies with either npm or yarn.',
62+
messageText: 'npm install --save-dev jest@38.0.1',
63+
});
64+
}
65+
);
66+
});
67+
});
68+
});

0 commit comments

Comments
 (0)