Skip to content

Commit 2b91e81

Browse files
committed
fix(plugin-npm): limit implicit node-gyp checks to build scripts
1 parent fe04180 commit 2b91e81

3 files changed

Lines changed: 78 additions & 5 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
releases:
2+
"@yarnpkg/plugin-npm": patch
3+
4+
declined:
5+
- "@yarnpkg/cli"
6+
- "@yarnpkg/core"
7+
- "@yarnpkg/plugin-compat"
8+
- "@yarnpkg/plugin-npm-cli"

packages/plugin-npm/sources/NpmSemverResolver.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as npmHttpUtils
1111

1212
const NODE_GYP_IDENT = structUtils.makeIdent(null, `node-gyp`);
1313
const NODE_GYP_MATCH = /\b(node-gyp|prebuild-install)\b/;
14+
const NODE_GYP_BUILD_SCRIPTS = [`preinstall`, `install`, `postinstall`];
1415

1516
export class NpmSemverResolver implements Resolver {
1617
supportsDescriptor(descriptor: Descriptor, opts: MinimalResolveOptions) {
@@ -152,10 +153,12 @@ export class NpmSemverResolver implements Resolver {
152153
// Manually add node-gyp dependency if there is a script using it and not already set
153154
// This is because the npm registry will automatically add a `node-gyp rebuild` install script
154155
// in the metadata if there is not already an install script and a binding.gyp file exists.
155-
// Also, node-gyp is not always set as a dependency in packages, so it will also be added if used in scripts.
156+
// Also, node-gyp is not always set as a dependency in packages, so it will also be added if used in build scripts.
156157
if (!manifest.dependencies.has(NODE_GYP_IDENT.identHash) && !manifest.peerDependencies.has(NODE_GYP_IDENT.identHash)) {
157-
for (const value of manifest.scripts.values()) {
158-
if (value.match(NODE_GYP_MATCH)) {
158+
for (const scriptName of NODE_GYP_BUILD_SCRIPTS) {
159+
const value = manifest.scripts.get(scriptName);
160+
161+
if (typeof value !== `undefined` && NODE_GYP_MATCH.test(value)) {
159162
manifest.dependencies.set(NODE_GYP_IDENT.identHash, structUtils.makeDescriptor(NODE_GYP_IDENT, `latest`));
160163
break;
161164
}

packages/plugin-npm/tests/NpmSemverResolver.test.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
import {structUtils} from '@yarnpkg/core';
1+
jest.mock(`../sources/npmHttpUtils`, () => ({
2+
getPackageMetadata: jest.fn(),
3+
}));
24

3-
import {NpmSemverResolver} from '../sources/NpmSemverResolver';
5+
import {structUtils} from '@yarnpkg/core';
6+
7+
const {NpmSemverResolver}: typeof import('../sources/NpmSemverResolver') = require(`../sources/NpmSemverResolver`);
8+
const npmHttpUtils: typeof import('../sources/npmHttpUtils') = require(`../sources/npmHttpUtils`);
9+
10+
afterEach(() => {
11+
jest.clearAllMocks();
12+
});
413

514
describe(`NpmSemverResolver`, () => {
615
describe(`getSatisfying`, () => {
@@ -22,4 +31,57 @@ describe(`NpmSemverResolver`, () => {
2231
expect(results.locators[0].locatorHash).toEqual(locator.locatorHash);
2332
});
2433
});
34+
35+
describe(`resolve`, () => {
36+
const ident = structUtils.makeIdent(null, `native-package`);
37+
const nodeGypIdent = structUtils.makeIdent(null, `node-gyp`);
38+
39+
const makeResolveOptions = () => ({
40+
project: {
41+
configuration: {
42+
normalizeDependencyMap: (dependencies: Map<any, any>) => dependencies,
43+
},
44+
},
45+
} as any);
46+
47+
const mockPackageMetadata = (scripts: Record<string, string>) => {
48+
const getPackageMetadata = npmHttpUtils.getPackageMetadata as jest.MockedFunction<typeof npmHttpUtils.getPackageMetadata>;
49+
50+
getPackageMetadata.mockResolvedValue({
51+
versions: {
52+
[`1.0.0`]: {
53+
name: structUtils.stringifyIdent(ident),
54+
version: `1.0.0`,
55+
scripts,
56+
},
57+
},
58+
} as any);
59+
};
60+
61+
it(`shouldn't inject node-gyp when only a non-build script uses it`, async () => {
62+
mockPackageMetadata({
63+
test: `node-gyp rebuild`,
64+
});
65+
66+
const resolver = new NpmSemverResolver();
67+
const locator = structUtils.makeLocator(ident, `npm:1.0.0`);
68+
69+
const pkg = await resolver.resolve(locator, makeResolveOptions());
70+
71+
expect(pkg.dependencies.has(nodeGypIdent.identHash)).toEqual(false);
72+
});
73+
74+
it(`should inject node-gyp when an install script uses it`, async () => {
75+
mockPackageMetadata({
76+
install: `node-gyp rebuild`,
77+
});
78+
79+
const resolver = new NpmSemverResolver();
80+
const locator = structUtils.makeLocator(ident, `npm:1.0.0`);
81+
82+
const pkg = await resolver.resolve(locator, makeResolveOptions());
83+
84+
expect(pkg.dependencies.has(nodeGypIdent.identHash)).toEqual(true);
85+
});
86+
});
2587
});

0 commit comments

Comments
 (0)