Skip to content

Commit

Permalink
fix(sys): make NodeLazyRequire complain if package versions aren't right
Browse files Browse the repository at this point in the history
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 committed Apr 26, 2022
1 parent cb8eebc commit 24eb9d2
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 32 deletions.
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
44 changes: 31 additions & 13 deletions src/sys/node/node-lazy-require.ts
Original file line number Diff line number Diff line change
@@ -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<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] }
) {}
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;
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
58 changes: 58 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,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',
});
});
});
});

0 comments on commit 24eb9d2

Please sign in to comment.