Skip to content

Commit 2df6339

Browse files
jaysooFrozenPandaz
authored andcommitted
fix(js): include npm overrides in generated lockfile (#35192)
## Current Behavior When using `generateLockfile: true` (e.g. with `@nx/next:build`), the generated `package-lock.json` is missing overridden packages for two reasons: 1. `normalizePackageJson()` strips the `overrides` field, so the generated lockfile lacks overrides both at the top level and in `packages[""]`. 2. `findTarget()` uses semver satisfaction to match dependency edges, but npm overrides can force versions outside the declared range (e.g. `minimatch@^9.0.4` overridden to `10.2.1`). This causes overridden packages and their transitive deps to be dropped from the pruned graph entirely. Running `npm ci` in the output directory fails: ``` npm error Missing: [email protected] from lock file ``` Note: yarn (`resolutions`) and pnpm (`pnpm.overrides`) were already working correctly. ## Expected Behavior The generated `package-lock.json` includes `overrides` and all overridden packages. `npm ci` succeeds. This is tested in the original issue repro repo, where with the applied patch `npm ci` works from dist. <img width="1272" height="362" alt="image" src="https://github.com/user-attachments/assets/2cdbb266-71f8-45c8-8ee0-cec6dcd12705" /> The missing parts are both `overrides` in `package.json`, but also the lockfile must include the pacakges in the overrides. In this example it is like this in `package-lock.json`: ``` "node_modules/minimatch": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, ``` ## Related Issue(s) Fixes #34529 (cherry picked from commit 1851fe8)
1 parent d71dcc6 commit 2df6339

3 files changed

Lines changed: 69 additions & 3 deletions

File tree

packages/nx/src/plugins/js/lock-file/npm-parser.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,52 @@ describe('NPM lock file utility', () => {
8787
expect(Object.keys(graph.externalNodes).length).toEqual(1285);
8888
});
8989

90+
it('should include overrides in stringified lock file', async () => {
91+
const appPackageJson = {
92+
name: 'test',
93+
version: '0.0.0',
94+
dependencies: {
95+
next: rootLockFile.packages['node_modules/next'].version,
96+
},
97+
overrides: {
98+
minimatch: '10.2.1',
99+
},
100+
};
101+
102+
const prunedGraph = pruneProjectGraph(graph, appPackageJson);
103+
const result = stringifyNpmLockfile(
104+
prunedGraph,
105+
JSON.stringify(rootLockFile),
106+
appPackageJson
107+
);
108+
const parsed = JSON.parse(result);
109+
110+
// overrides should be at the top level
111+
expect(parsed.overrides).toEqual({ minimatch: '10.2.1' });
112+
// overrides should also be in the root packages entry
113+
expect(parsed.packages[''].overrides).toEqual({ minimatch: '10.2.1' });
114+
});
115+
116+
it('should not include overrides when not present', async () => {
117+
const appPackageJson = {
118+
name: 'test',
119+
version: '0.0.0',
120+
dependencies: {
121+
next: rootLockFile.packages['node_modules/next'].version,
122+
},
123+
};
124+
125+
const prunedGraph = pruneProjectGraph(graph, appPackageJson);
126+
const result = stringifyNpmLockfile(
127+
prunedGraph,
128+
JSON.stringify(rootLockFile),
129+
appPackageJson
130+
);
131+
const parsed = JSON.parse(result);
132+
133+
expect(parsed.overrides).toBeUndefined();
134+
});
135+
90136
it('should prune lock file', async () => {
91137
const appPackageJson = loadJsonFixture(
92138
joinPathFragments(

packages/nx/src/plugins/js/lock-file/npm-parser.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type NpmLockFile = {
5353
version?: string;
5454
lockfileVersion: number;
5555
requires?: boolean;
56+
overrides?: NormalizedPackageJson['overrides'];
5657
packages?: Record<string, NpmDependencyV3>;
5758
dependencies?: Record<string, NpmDependencyV1>;
5859
};
@@ -302,7 +303,12 @@ function findTarget(
302303
sourcePath: string,
303304
keyMap: Map<string, ProjectGraphExternalNode>,
304305
targetName: string,
305-
versionRange: string
306+
versionRange: string,
307+
// When a package is found at a path but its version doesn't satisfy the
308+
// range (e.g. due to npm overrides), we keep it as a fallback. npm already
309+
// resolved this dependency to that location, so it is the correct target
310+
// even though the semver check fails.
311+
fallback?: ProjectGraphExternalNode
306312
): ProjectGraphExternalNode {
307313
if (sourcePath && !sourcePath.endsWith('/')) {
308314
sourcePath = `${sourcePath}/`;
@@ -329,16 +335,21 @@ function findTarget(
329335
) {
330336
return child;
331337
}
338+
// Version mismatch — save as fallback (could be an npm override)
339+
if (!fallback) {
340+
fallback = child;
341+
}
332342
}
333343
// the hoisted package did not match, this dependency is missing
334344
if (!sourcePath) {
335-
return;
345+
return fallback;
336346
}
337347
return findTarget(
338348
sourcePath.split('node_modules/').slice(0, -1).join('node_modules/'),
339349
keyMap,
340350
targetName,
341-
versionRange
351+
versionRange,
352+
fallback
342353
);
343354
}
344355

@@ -418,6 +429,9 @@ export function stringifyNpmLockfile(
418429
if (rootLockFile.requires) {
419430
output.requires = rootLockFile.requires;
420431
}
432+
if (packageJson.overrides && Object.keys(packageJson.overrides).length > 0) {
433+
output.overrides = packageJson.overrides;
434+
}
421435
if (lockfileVersion > 1) {
422436
const packages = mapV3Snapshots(mappedPackages, packageJson);
423437
output.packages = { ...packages, ...workspaceModules };

packages/nx/src/plugins/js/lock-file/utils/package-json.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export type NormalizedPackageJson = Pick<
2727
| 'optionalDependencies'
2828
| 'packageManager'
2929
| 'resolutions'
30+
| 'overrides'
31+
| 'pnpm'
3032
>;
3133

3234
/**
@@ -46,6 +48,8 @@ export function normalizePackageJson(
4648
optionalDependencies,
4749
packageManager,
4850
resolutions,
51+
overrides,
52+
pnpm,
4953
} = packageJson;
5054

5155
return {
@@ -59,5 +63,7 @@ export function normalizePackageJson(
5963
optionalDependencies,
6064
packageManager,
6165
resolutions,
66+
overrides,
67+
pnpm,
6268
};
6369
}

0 commit comments

Comments
 (0)