Skip to content

Commit

Permalink
fix(sys): make NodeLazyRequire complain if package versions aren't ri…
Browse files Browse the repository at this point in the history
…ght (#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
  • Loading branch information
alicewriteswrongs authored Apr 28, 2022
1 parent ad1b68a commit b7adc33
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 44 deletions.
72 changes: 62 additions & 10 deletions NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,30 @@ Homepage: https://lodash.com/
--------

## `lru-cache`

License: ISC

Author: Isaac Z. Schlueter <i@izs.me>

> The ISC License
>
> Copyright (c) Isaac Z. Schlueter and Contributors
>
> Permission to use, copy, modify, and/or distribute this software for any
> purpose with or without fee is hereby granted, provided that the above
> copyright notice and this permission notice appear in all copies.
>
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------

## `magic-string`

License: MIT
Expand Down Expand Up @@ -2822,21 +2846,25 @@ Homepage: https://rollupjs.org/
--------

## `semiver`
## `semver`

License: MIT

Author: [Luke Edwards](lukeed.com)
License: ISC

> MIT License
>
> Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
> The ISC License
>
> 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:
> Copyright (c) Isaac Z. Schlueter and Contributors
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
> Permission to use, copy, modify, and/or distribute this software for any
> purpose with or without fee is hereby granted, provided that the above
> copyright notice and this permission notice appear in all copies.
>
> 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.
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------

Expand Down Expand Up @@ -3263,3 +3291,27 @@ Homepage: https://github.com/websockets/ws
> SOFTWARE.
--------

## `yallist`

License: ISC

Author: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)

> The ISC License
>
> Copyright (c) Isaac Z. Schlueter and Contributors
>
> Permission to use, copy, modify, and/or distribute this software for any
> purpose with or without fee is hereby granted, provided that the above
> copyright notice and this permission notice appear in all copies.
>
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------
16 changes: 0 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion scripts/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const entryDeps = [
'postcss',
'prompts',
'rollup',
'semiver',
'semver',
'sizzle',
'source-map',
'terser',
Expand Down
63 changes: 49 additions & 14 deletions src/sys/node/node-lazy-require.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,78 @@
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<string, NodeVersionRange>;

/**
* 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<string>();

constructor(
private nodeResolveModule: NodeResolveModule,
private lazyDependencies: { [dep: string]: [string, string] }
) {}
/**
* Create a NodeLazyRequire instance
*
* @param nodeResolveModule an object which wraps up module resolution functionality
* @param lazyDependencies the dependency requirements we want to enforce here
*/
constructor(private nodeResolveModule: NodeResolveModule, private lazyDependencies: LazyDependencies) {}

async ensure(fromDir: string, ensureModuleIds: string[]) {
/**
* Ensure that a dependency within our supported range is installed in the current
* environment. This function will check all the dependency requirements passed in when
* the class is instantiated and return diagnostics if there are any issues.
*
* @param fromDir the directory from which we'll attempt to resolve the dependencies, typically
* this will be project's root directory.
* @param ensureModuleIds an array of module names whose versions we're going to check
* @returns a Promise holding diagnostics if any of the dependencies either were not
* resolved _or_ did not meet our version requirements.
*/
async ensure(fromDir: string, ensureModuleIds: string[]): Promise<d.Diagnostic[]> {
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;
Expand Down
4 changes: 2 additions & 2 deletions src/sys/node/node-stencil-version-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand All @@ -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(
Expand Down
68 changes: 68 additions & 0 deletions src/sys/node/test/node-lazy-require.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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', () => {
describe('ensure', () => {
let readFSMock: jest.SpyInstance<ReturnType<typeof fs.readFileSync>, Parameters<typeof fs.readFileSync>>;

beforeEach(() => {
readFSMock = jest.spyOn(fs, 'readFileSync').mockReturnValue(mockPackageJson('10.10.10'));
});

afterEach(() => {
readFSMock.mockClear();
});

function setup() {
const resolveModule = new NodeResolveModule();
const nodeLazyRequire = new NodeLazyRequire(resolveModule, {
jest: ['2.0.7', '38.0.1'],
});
return nodeLazyRequire;
}

it.each(['2.0.7', '10.10.10', '38.0.1', '38.0.2', '38.5.17'])(
'should not error if installed package has a suitable major version (%p)',
async (testVersion) => {
const nodeLazyRequire = 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 = 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.each(['100.1.1', '38.0.1-alpha.0'])(
'should error if the installed version of a package is too high (%p)',
async (version) => {
const nodeLazyRequire = setup();
readFSMock.mockReturnValue(mockPackageJson(version));
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',
});
}
);
});
});
});

0 comments on commit b7adc33

Please sign in to comment.