isolate-package 1.31.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1 -1
- package/dist/{isolate-DtNAHzfa.mjs → isolate-BRD2AgVJ.mjs} +385 -123
- package/dist/isolate-BRD2AgVJ.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/package.json +1 -1
- package/src/isolate.ts +1 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package-lock.json +82 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package.json +8 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/api/package.json +12 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/shared/package.json +12 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/utils/package.json +11 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package-lock.json +56 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package.json +8 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/api/package.json +11 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/other/package.json +11 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +243 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +604 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +417 -21
- package/src/lib/lockfile/process-lockfile.test.ts +4 -0
- package/src/lib/lockfile/process-lockfile.ts +14 -16
- package/src/lib/patches/copy-patches.test.ts +78 -0
- package/src/lib/patches/copy-patches.ts +22 -1
- package/src/lib/registry/collect-reachable-package-names.test.ts +239 -0
- package/src/lib/registry/collect-reachable-package-names.ts +60 -0
- package/src/lib/registry/index.ts +1 -0
- package/src/lib/utils/filter-patched-dependencies.test.ts +77 -0
- package/src/lib/utils/filter-patched-dependencies.ts +41 -17
- package/dist/isolate-DtNAHzfa.mjs.map +0 -1
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildIsolatedLockfileJson,
|
|
4
|
+
type ReachableNode,
|
|
5
|
+
} from "./generate-npm-lockfile";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds a fixture lockfile representing a monorepo with:
|
|
9
|
+
* - root workspace at ""
|
|
10
|
+
* - target workspace "my-app" at "packages/my-app"
|
|
11
|
+
* - internal workspace "shared" at "packages/shared"
|
|
12
|
+
* - unrelated workspace "other" at "packages/other"
|
|
13
|
+
* - hoisted external deps express@4, lodash@4
|
|
14
|
+
* - transitive dep body-parser nested under express due to a version pin
|
|
15
|
+
*/
|
|
16
|
+
function createSrcLockfile() {
|
|
17
|
+
return {
|
|
18
|
+
name: "root",
|
|
19
|
+
version: "0.0.0",
|
|
20
|
+
lockfileVersion: 3,
|
|
21
|
+
requires: true,
|
|
22
|
+
packages: {
|
|
23
|
+
"": {
|
|
24
|
+
name: "root",
|
|
25
|
+
version: "0.0.0",
|
|
26
|
+
workspaces: ["packages/*"],
|
|
27
|
+
},
|
|
28
|
+
"packages/my-app": {
|
|
29
|
+
name: "my-app",
|
|
30
|
+
version: "1.0.0",
|
|
31
|
+
dependencies: {
|
|
32
|
+
shared: "*",
|
|
33
|
+
express: "^4.0.0",
|
|
34
|
+
},
|
|
35
|
+
devDependencies: {
|
|
36
|
+
lodash: "^4.17.0",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
"packages/shared": {
|
|
40
|
+
name: "shared",
|
|
41
|
+
version: "1.0.0",
|
|
42
|
+
dependencies: {
|
|
43
|
+
lodash: "^4.17.0",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
"packages/other": {
|
|
47
|
+
name: "other",
|
|
48
|
+
version: "1.0.0",
|
|
49
|
+
},
|
|
50
|
+
"node_modules/my-app": {
|
|
51
|
+
resolved: "packages/my-app",
|
|
52
|
+
link: true,
|
|
53
|
+
},
|
|
54
|
+
"node_modules/shared": {
|
|
55
|
+
resolved: "packages/shared",
|
|
56
|
+
link: true,
|
|
57
|
+
},
|
|
58
|
+
"node_modules/other": {
|
|
59
|
+
resolved: "packages/other",
|
|
60
|
+
link: true,
|
|
61
|
+
},
|
|
62
|
+
"node_modules/express": {
|
|
63
|
+
version: "4.18.2",
|
|
64
|
+
resolved: "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
|
65
|
+
integrity: "sha512-fake-express-integrity",
|
|
66
|
+
dependencies: {
|
|
67
|
+
"body-parser": "1.20.0",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"node_modules/express/node_modules/body-parser": {
|
|
71
|
+
version: "1.20.0",
|
|
72
|
+
resolved:
|
|
73
|
+
"https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
|
|
74
|
+
integrity: "sha512-fake-body-parser-integrity",
|
|
75
|
+
},
|
|
76
|
+
"node_modules/lodash": {
|
|
77
|
+
version: "4.17.21",
|
|
78
|
+
resolved: "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
79
|
+
integrity: "sha512-fake-lodash-integrity",
|
|
80
|
+
dev: true,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Builds the reachable node set that Arborist's workspaceDependencySet would
|
|
88
|
+
* produce for the target "my-app", plus the explicit inclusion of the target's
|
|
89
|
+
* real importer Node (which workspaceDependencySet omits).
|
|
90
|
+
*/
|
|
91
|
+
function createReachable(): ReachableNode[] {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
location: "node_modules/my-app",
|
|
95
|
+
isLink: true,
|
|
96
|
+
target: { location: "packages/my-app" },
|
|
97
|
+
},
|
|
98
|
+
{ location: "packages/my-app", isLink: false },
|
|
99
|
+
{
|
|
100
|
+
location: "node_modules/shared",
|
|
101
|
+
isLink: true,
|
|
102
|
+
target: { location: "packages/shared" },
|
|
103
|
+
},
|
|
104
|
+
{ location: "packages/shared", isLink: false },
|
|
105
|
+
{ location: "node_modules/express", isLink: false },
|
|
106
|
+
{
|
|
107
|
+
location: "node_modules/express/node_modules/body-parser",
|
|
108
|
+
isLink: false,
|
|
109
|
+
},
|
|
110
|
+
{ location: "node_modules/lodash", isLink: false },
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
describe("buildIsolatedLockfileJson", () => {
|
|
115
|
+
it("maps target workspace to root and drops target self-link", () => {
|
|
116
|
+
const srcData = createSrcLockfile();
|
|
117
|
+
const out = buildIsolatedLockfileJson({
|
|
118
|
+
srcData,
|
|
119
|
+
reachable: createReachable(),
|
|
120
|
+
targetImporterLoc: "packages/my-app",
|
|
121
|
+
targetLinkLoc: "node_modules/my-app",
|
|
122
|
+
targetPackageManifest: {
|
|
123
|
+
name: "my-app",
|
|
124
|
+
version: "1.0.0",
|
|
125
|
+
dependencies: {
|
|
126
|
+
shared: "file:./packages/shared",
|
|
127
|
+
express: "^4.0.0",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(out.packages[""]).toBeDefined();
|
|
133
|
+
expect(out.packages[""]!.name).toBe("my-app");
|
|
134
|
+
expect(out.packages[""]!.version).toBe("1.0.0");
|
|
135
|
+
expect(out.packages["packages/my-app"]).toBeUndefined();
|
|
136
|
+
expect(out.packages["node_modules/my-app"]).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("preserves external package resolved/integrity verbatim", () => {
|
|
140
|
+
const srcData = createSrcLockfile();
|
|
141
|
+
const out = buildIsolatedLockfileJson({
|
|
142
|
+
srcData,
|
|
143
|
+
reachable: createReachable(),
|
|
144
|
+
targetImporterLoc: "packages/my-app",
|
|
145
|
+
targetLinkLoc: "node_modules/my-app",
|
|
146
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(out.packages["node_modules/express"]).toEqual(
|
|
150
|
+
srcData.packages["node_modules/express"],
|
|
151
|
+
);
|
|
152
|
+
expect(out.packages["node_modules/lodash"]).toEqual(
|
|
153
|
+
srcData.packages["node_modules/lodash"],
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("preserves nested node_modules paths (hoisting duplicates)", () => {
|
|
158
|
+
const srcData = createSrcLockfile();
|
|
159
|
+
const out = buildIsolatedLockfileJson({
|
|
160
|
+
srcData,
|
|
161
|
+
reachable: createReachable(),
|
|
162
|
+
targetImporterLoc: "packages/my-app",
|
|
163
|
+
targetLinkLoc: "node_modules/my-app",
|
|
164
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(
|
|
168
|
+
out.packages["node_modules/express/node_modules/body-parser"],
|
|
169
|
+
).toEqual(
|
|
170
|
+
srcData.packages["node_modules/express/node_modules/body-parser"],
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("excludes unrelated workspaces and their link entries", () => {
|
|
175
|
+
const srcData = createSrcLockfile();
|
|
176
|
+
const out = buildIsolatedLockfileJson({
|
|
177
|
+
srcData,
|
|
178
|
+
reachable: createReachable(),
|
|
179
|
+
targetImporterLoc: "packages/my-app",
|
|
180
|
+
targetLinkLoc: "node_modules/my-app",
|
|
181
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(out.packages["packages/other"]).toBeUndefined();
|
|
185
|
+
expect(out.packages["node_modules/other"]).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("preserves internal workspace link entries", () => {
|
|
189
|
+
const srcData = createSrcLockfile();
|
|
190
|
+
const out = buildIsolatedLockfileJson({
|
|
191
|
+
srcData,
|
|
192
|
+
reachable: createReachable(),
|
|
193
|
+
targetImporterLoc: "packages/my-app",
|
|
194
|
+
targetLinkLoc: "node_modules/my-app",
|
|
195
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(out.packages["node_modules/shared"]).toEqual({
|
|
199
|
+
resolved: "packages/shared",
|
|
200
|
+
link: true,
|
|
201
|
+
});
|
|
202
|
+
expect(out.packages["packages/shared"]).toBeDefined();
|
|
203
|
+
expect(out.packages["packages/shared"]!.name).toBe("shared");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("overlays root entry deps from the adapted target manifest", () => {
|
|
207
|
+
const srcData = createSrcLockfile();
|
|
208
|
+
const out = buildIsolatedLockfileJson({
|
|
209
|
+
srcData,
|
|
210
|
+
reachable: createReachable(),
|
|
211
|
+
targetImporterLoc: "packages/my-app",
|
|
212
|
+
targetLinkLoc: "node_modules/my-app",
|
|
213
|
+
targetPackageManifest: {
|
|
214
|
+
name: "my-app",
|
|
215
|
+
version: "1.0.0",
|
|
216
|
+
dependencies: {
|
|
217
|
+
shared: "file:./packages/shared",
|
|
218
|
+
express: "^4.0.0",
|
|
219
|
+
},
|
|
220
|
+
devDependencies: {
|
|
221
|
+
lodash: "^4.17.0",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(out.packages[""]!.dependencies).toEqual({
|
|
227
|
+
shared: "file:./packages/shared",
|
|
228
|
+
express: "^4.0.0",
|
|
229
|
+
});
|
|
230
|
+
expect(out.packages[""]!.devDependencies).toEqual({
|
|
231
|
+
lodash: "^4.17.0",
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("strips workspaces field from root entry", () => {
|
|
236
|
+
const srcData = createSrcLockfile();
|
|
237
|
+
const out = buildIsolatedLockfileJson({
|
|
238
|
+
srcData,
|
|
239
|
+
reachable: createReachable(),
|
|
240
|
+
targetImporterLoc: "packages/my-app",
|
|
241
|
+
targetLinkLoc: "node_modules/my-app",
|
|
242
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(out.packages[""]!.workspaces).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("preserves lockfileVersion and requires from source", () => {
|
|
249
|
+
const srcData = createSrcLockfile();
|
|
250
|
+
const out = buildIsolatedLockfileJson({
|
|
251
|
+
srcData,
|
|
252
|
+
reachable: createReachable(),
|
|
253
|
+
targetImporterLoc: "packages/my-app",
|
|
254
|
+
targetLinkLoc: "node_modules/my-app",
|
|
255
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(out.lockfileVersion).toBe(3);
|
|
259
|
+
expect(out.requires).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("sets top-level name and version from target manifest", () => {
|
|
263
|
+
const srcData = createSrcLockfile();
|
|
264
|
+
const out = buildIsolatedLockfileJson({
|
|
265
|
+
srcData,
|
|
266
|
+
reachable: createReachable(),
|
|
267
|
+
targetImporterLoc: "packages/my-app",
|
|
268
|
+
targetLinkLoc: "node_modules/my-app",
|
|
269
|
+
targetPackageManifest: { name: "my-app", version: "2.3.4" },
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(out.name).toBe("my-app");
|
|
273
|
+
expect(out.version).toBe("2.3.4");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("preserves overrides from source lockfile", () => {
|
|
277
|
+
const srcData = {
|
|
278
|
+
...createSrcLockfile(),
|
|
279
|
+
overrides: { lodash: "4.17.21" },
|
|
280
|
+
};
|
|
281
|
+
const out = buildIsolatedLockfileJson({
|
|
282
|
+
srcData,
|
|
283
|
+
reachable: createReachable(),
|
|
284
|
+
targetImporterLoc: "packages/my-app",
|
|
285
|
+
targetLinkLoc: "node_modules/my-app",
|
|
286
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(out.overrides).toEqual({ lodash: "4.17.21" });
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("omits overrides when source has none", () => {
|
|
293
|
+
const srcData = createSrcLockfile();
|
|
294
|
+
const out = buildIsolatedLockfileJson({
|
|
295
|
+
srcData,
|
|
296
|
+
reachable: createReachable(),
|
|
297
|
+
targetImporterLoc: "packages/my-app",
|
|
298
|
+
targetLinkLoc: "node_modules/my-app",
|
|
299
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
expect(out.overrides).toBeUndefined();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("throws when the target importer is not in the reachable set", () => {
|
|
306
|
+
const srcData = createSrcLockfile();
|
|
307
|
+
/** Reachable set without the target importer — simulates an upstream bug. */
|
|
308
|
+
const reachable: ReachableNode[] = [
|
|
309
|
+
{ location: "node_modules/express", isLink: false },
|
|
310
|
+
];
|
|
311
|
+
expect(() =>
|
|
312
|
+
buildIsolatedLockfileJson({
|
|
313
|
+
srcData,
|
|
314
|
+
reachable,
|
|
315
|
+
targetImporterLoc: "packages/my-app",
|
|
316
|
+
targetLinkLoc: "node_modules/my-app",
|
|
317
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
318
|
+
}),
|
|
319
|
+
).toThrow(/was not present in the reachable node set/);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("does not emit a `requires` field when the source omitted it", () => {
|
|
323
|
+
const srcData = createSrcLockfile();
|
|
324
|
+
/** Source lockfile without `requires` (some npm versions omit it). */
|
|
325
|
+
delete (srcData as { requires?: boolean }).requires;
|
|
326
|
+
|
|
327
|
+
const out = buildIsolatedLockfileJson({
|
|
328
|
+
srcData,
|
|
329
|
+
reachable: createReachable(),
|
|
330
|
+
targetImporterLoc: "packages/my-app",
|
|
331
|
+
targetLinkLoc: "node_modules/my-app",
|
|
332
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect("requires" in out).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("preserves `requires` when the source has it", () => {
|
|
339
|
+
const srcData = createSrcLockfile();
|
|
340
|
+
expect(srcData.requires).toBe(true);
|
|
341
|
+
|
|
342
|
+
const out = buildIsolatedLockfileJson({
|
|
343
|
+
srcData,
|
|
344
|
+
reachable: createReachable(),
|
|
345
|
+
targetImporterLoc: "packages/my-app",
|
|
346
|
+
targetLinkLoc: "node_modules/my-app",
|
|
347
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(out.requires).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("does not remap non-node_modules paths under the target", () => {
|
|
354
|
+
/**
|
|
355
|
+
* A target importer at `packages/my-app` with a sibling importer
|
|
356
|
+
* nested inside it (`packages/my-app/lib/core`) must keep its source
|
|
357
|
+
* path — remapping it would break both the output lockfile's install
|
|
358
|
+
* paths and the internal-dep overlay lookup.
|
|
359
|
+
*/
|
|
360
|
+
const srcData: Parameters<typeof buildIsolatedLockfileJson>[0]["srcData"] =
|
|
361
|
+
{
|
|
362
|
+
name: "root",
|
|
363
|
+
version: "0.0.0",
|
|
364
|
+
lockfileVersion: 3,
|
|
365
|
+
requires: true,
|
|
366
|
+
packages: {
|
|
367
|
+
"": { name: "root", version: "0.0.0" },
|
|
368
|
+
"packages/my-app": { name: "my-app", version: "1.0.0" },
|
|
369
|
+
"packages/my-app/lib/core": { name: "core", version: "1.0.0" },
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const reachable: ReachableNode[] = [
|
|
374
|
+
{ location: "packages/my-app", isLink: false },
|
|
375
|
+
{ location: "packages/my-app/lib/core", isLink: false },
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
const out = buildIsolatedLockfileJson({
|
|
379
|
+
srcData,
|
|
380
|
+
reachable,
|
|
381
|
+
targetImporterLoc: "packages/my-app",
|
|
382
|
+
targetLinkLoc: "node_modules/my-app",
|
|
383
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
/** Nested importer stays at its source path. */
|
|
387
|
+
expect(out.packages["packages/my-app/lib/core"]).toBeDefined();
|
|
388
|
+
/** It must NOT be remapped to "lib/core". */
|
|
389
|
+
expect(out.packages["lib/core"]).toBeUndefined();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("removes dep fields from root entry when adapted manifest omits them", () => {
|
|
393
|
+
const srcData = createSrcLockfile();
|
|
394
|
+
const out = buildIsolatedLockfileJson({
|
|
395
|
+
srcData,
|
|
396
|
+
reachable: createReachable(),
|
|
397
|
+
targetImporterLoc: "packages/my-app",
|
|
398
|
+
targetLinkLoc: "node_modules/my-app",
|
|
399
|
+
/** Adapted manifest with no deps */
|
|
400
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
expect(out.packages[""]!.dependencies).toBeUndefined();
|
|
404
|
+
expect(out.packages[""]!.devDependencies).toBeUndefined();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* When the target's nested entry remaps onto the same path as a hoisted
|
|
409
|
+
* entry still needed by another reachable dependency, and the two
|
|
410
|
+
* entries differ in content, we must refuse to produce the lockfile
|
|
411
|
+
* rather than silently drop one version.
|
|
412
|
+
*/
|
|
413
|
+
it("throws on a remap collision with conflicting entries", () => {
|
|
414
|
+
const srcData: Parameters<typeof buildIsolatedLockfileJson>[0]["srcData"] =
|
|
415
|
+
{
|
|
416
|
+
name: "root",
|
|
417
|
+
version: "0.0.0",
|
|
418
|
+
lockfileVersion: 3,
|
|
419
|
+
requires: true,
|
|
420
|
+
packages: {
|
|
421
|
+
"": { name: "root", version: "0.0.0", workspaces: ["packages/*"] },
|
|
422
|
+
"packages/api": {
|
|
423
|
+
name: "api",
|
|
424
|
+
version: "1.0.0",
|
|
425
|
+
dependencies: { semver: "^6", shared: "*" },
|
|
426
|
+
},
|
|
427
|
+
"packages/shared": {
|
|
428
|
+
name: "shared",
|
|
429
|
+
version: "1.0.0",
|
|
430
|
+
dependencies: { semver: "^7" },
|
|
431
|
+
},
|
|
432
|
+
"node_modules/shared": { resolved: "packages/shared", link: true },
|
|
433
|
+
/** Hoisted v7 used by the internal dep. */
|
|
434
|
+
"node_modules/semver": {
|
|
435
|
+
version: "7.7.4",
|
|
436
|
+
resolved: "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
|
437
|
+
integrity: "sha512-hoisted-v7",
|
|
438
|
+
},
|
|
439
|
+
/** Nested v6 used by the target — will collide with the hoisted one. */
|
|
440
|
+
"packages/api/node_modules/semver": {
|
|
441
|
+
version: "6.3.1",
|
|
442
|
+
resolved: "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
443
|
+
integrity: "sha512-nested-v6",
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const reachable: ReachableNode[] = [
|
|
449
|
+
{ location: "packages/api", isLink: false },
|
|
450
|
+
{
|
|
451
|
+
location: "node_modules/shared",
|
|
452
|
+
isLink: true,
|
|
453
|
+
target: { location: "packages/shared" },
|
|
454
|
+
},
|
|
455
|
+
{ location: "packages/shared", isLink: false },
|
|
456
|
+
{ location: "node_modules/semver", isLink: false },
|
|
457
|
+
{ location: "packages/api/node_modules/semver", isLink: false },
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
expect(() =>
|
|
461
|
+
buildIsolatedLockfileJson({
|
|
462
|
+
srcData,
|
|
463
|
+
reachable,
|
|
464
|
+
targetImporterLoc: "packages/api",
|
|
465
|
+
targetLinkLoc: "node_modules/api",
|
|
466
|
+
targetPackageManifest: { name: "api", version: "1.0.0" },
|
|
467
|
+
}),
|
|
468
|
+
).toThrow(/Path collision at "node_modules\/semver"/);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Identical entries at colliding paths should not throw — copying the
|
|
473
|
+
* same content twice is a no-op.
|
|
474
|
+
*/
|
|
475
|
+
it("does not throw when colliding entries are identical", () => {
|
|
476
|
+
const srcData: Parameters<typeof buildIsolatedLockfileJson>[0]["srcData"] =
|
|
477
|
+
{
|
|
478
|
+
name: "root",
|
|
479
|
+
version: "0.0.0",
|
|
480
|
+
lockfileVersion: 3,
|
|
481
|
+
requires: true,
|
|
482
|
+
packages: {
|
|
483
|
+
"": { name: "root", version: "0.0.0" },
|
|
484
|
+
"packages/api": { name: "api", version: "1.0.0" },
|
|
485
|
+
"node_modules/semver": {
|
|
486
|
+
version: "7.7.4",
|
|
487
|
+
resolved: "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
|
488
|
+
integrity: "sha512-same",
|
|
489
|
+
},
|
|
490
|
+
"packages/api/node_modules/semver": {
|
|
491
|
+
version: "7.7.4",
|
|
492
|
+
resolved: "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
|
493
|
+
integrity: "sha512-same",
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const reachable: ReachableNode[] = [
|
|
499
|
+
{ location: "packages/api", isLink: false },
|
|
500
|
+
{ location: "node_modules/semver", isLink: false },
|
|
501
|
+
{ location: "packages/api/node_modules/semver", isLink: false },
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
expect(() =>
|
|
505
|
+
buildIsolatedLockfileJson({
|
|
506
|
+
srcData,
|
|
507
|
+
reachable,
|
|
508
|
+
targetImporterLoc: "packages/api",
|
|
509
|
+
targetLinkLoc: "node_modules/api",
|
|
510
|
+
targetPackageManifest: { name: "api", version: "1.0.0" },
|
|
511
|
+
}),
|
|
512
|
+
).not.toThrow();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Reproduces the scenario from issue #111 (mono-ts): the target workspace
|
|
517
|
+
* depends on a different version of a package than is hoisted at the root,
|
|
518
|
+
* so the target has its own nested node_modules entry. The isolated
|
|
519
|
+
* lockfile must surface that nested version at the root-level
|
|
520
|
+
* node_modules (because the target becomes the isolate root) and it must
|
|
521
|
+
* preserve the original resolved/integrity exactly.
|
|
522
|
+
*/
|
|
523
|
+
it("remaps nested node_modules under the target to the isolate root", () => {
|
|
524
|
+
const srcData = {
|
|
525
|
+
name: "root",
|
|
526
|
+
version: "0.0.0",
|
|
527
|
+
lockfileVersion: 3,
|
|
528
|
+
requires: true,
|
|
529
|
+
packages: {
|
|
530
|
+
"": { name: "root", version: "0.0.0", workspaces: ["services/*"] },
|
|
531
|
+
"services/api": {
|
|
532
|
+
name: "api",
|
|
533
|
+
version: "1.0.0",
|
|
534
|
+
dependencies: { "firebase-admin": "^12.0.0" },
|
|
535
|
+
},
|
|
536
|
+
"services/other": {
|
|
537
|
+
name: "other",
|
|
538
|
+
version: "1.0.0",
|
|
539
|
+
dependencies: { "firebase-admin": "^11.0.0" },
|
|
540
|
+
},
|
|
541
|
+
"node_modules/api": { resolved: "services/api", link: true },
|
|
542
|
+
"node_modules/other": { resolved: "services/other", link: true },
|
|
543
|
+
/** Hoisted old version used by the root and "other". */
|
|
544
|
+
"node_modules/firebase-admin": {
|
|
545
|
+
version: "11.11.1",
|
|
546
|
+
resolved:
|
|
547
|
+
"https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.11.1.tgz",
|
|
548
|
+
integrity: "sha512-hoisted-v11-integrity",
|
|
549
|
+
},
|
|
550
|
+
/** Nested new version used by the target "api". */
|
|
551
|
+
"services/api/node_modules/firebase-admin": {
|
|
552
|
+
version: "12.7.0",
|
|
553
|
+
resolved:
|
|
554
|
+
"https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz",
|
|
555
|
+
integrity: "sha512-nested-v12-integrity",
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const reachable: ReachableNode[] = [
|
|
561
|
+
{
|
|
562
|
+
location: "node_modules/api",
|
|
563
|
+
isLink: true,
|
|
564
|
+
target: { location: "services/api" },
|
|
565
|
+
},
|
|
566
|
+
{ location: "services/api", isLink: false },
|
|
567
|
+
/** Only the nested v12 is reachable from the target. */
|
|
568
|
+
{
|
|
569
|
+
location: "services/api/node_modules/firebase-admin",
|
|
570
|
+
isLink: false,
|
|
571
|
+
},
|
|
572
|
+
];
|
|
573
|
+
|
|
574
|
+
const out = buildIsolatedLockfileJson({
|
|
575
|
+
srcData,
|
|
576
|
+
reachable,
|
|
577
|
+
targetImporterLoc: "services/api",
|
|
578
|
+
targetLinkLoc: "node_modules/api",
|
|
579
|
+
targetPackageManifest: {
|
|
580
|
+
name: "api",
|
|
581
|
+
version: "1.0.0",
|
|
582
|
+
dependencies: { "firebase-admin": "^12.0.0" },
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
/** Nested entry is hoisted to the isolate's root node_modules. */
|
|
587
|
+
expect(out.packages["node_modules/firebase-admin"]).toEqual({
|
|
588
|
+
version: "12.7.0",
|
|
589
|
+
resolved:
|
|
590
|
+
"https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz",
|
|
591
|
+
integrity: "sha512-nested-v12-integrity",
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
/** The original nested path must not leak into the output. */
|
|
595
|
+
expect(
|
|
596
|
+
out.packages["services/api/node_modules/firebase-admin"],
|
|
597
|
+
).toBeUndefined();
|
|
598
|
+
|
|
599
|
+
/** The hoisted v11 must not leak in either. */
|
|
600
|
+
expect(out.packages["node_modules/firebase-admin"]!.version).not.toBe(
|
|
601
|
+
"11.11.1",
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
});
|