isolate-package 1.29.0 → 1.30.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.
Files changed (29) hide show
  1. package/dist/index.d.mts +7 -1
  2. package/dist/index.mjs +2 -3
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/{isolate-3GcdAuUK.mjs → isolate-CJy3YyKG.mjs} +261 -64
  5. package/dist/isolate-CJy3YyKG.mjs.map +1 -0
  6. package/dist/isolate-bin.mjs +3 -5
  7. package/dist/isolate-bin.mjs.map +1 -1
  8. package/package.json +21 -20
  9. package/src/get-internal-package-names.test.ts +0 -10
  10. package/src/index.ts +6 -0
  11. package/src/isolate.ts +38 -8
  12. package/src/lib/lockfile/helpers/generate-bun-lockfile.test.ts +619 -0
  13. package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +354 -0
  14. package/src/lib/lockfile/helpers/generate-pnpm-lockfile.test.ts +387 -0
  15. package/src/lib/lockfile/helpers/index.ts +1 -0
  16. package/src/lib/lockfile/helpers/pnpm-map-importer.test.ts +183 -0
  17. package/src/lib/lockfile/process-lockfile.test.ts +238 -0
  18. package/src/lib/lockfile/process-lockfile.ts +6 -6
  19. package/src/lib/manifest/adapt-target-package-manifest.ts +6 -4
  20. package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +49 -0
  21. package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +15 -3
  22. package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +61 -0
  23. package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +42 -3
  24. package/src/lib/patches/copy-patches.test.ts +49 -11
  25. package/src/lib/patches/copy-patches.ts +38 -17
  26. package/src/lib/types.ts +5 -0
  27. package/src/lib/utils/filter-patched-dependencies.test.ts +1 -11
  28. package/src/testing/setup.ts +13 -0
  29. package/dist/isolate-3GcdAuUK.mjs.map +0 -1
@@ -0,0 +1,619 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import {
3
+ generateBunLockfile,
4
+ serializeWithTrailingCommas,
5
+ } from "./generate-bun-lockfile";
6
+
7
+ /** Mock fs-extra */
8
+ vi.mock("fs-extra", () => ({
9
+ default: {
10
+ existsSync: vi.fn(),
11
+ writeFile: vi.fn(),
12
+ },
13
+ }));
14
+
15
+ /** Mock the utils */
16
+ vi.mock("~/lib/utils", async (importOriginal) => {
17
+ const actual = await importOriginal<typeof import("~/lib/utils")>();
18
+ return {
19
+ getErrorMessage: vi.fn((err: Error) => err.message),
20
+ getPackageName: actual.getPackageName,
21
+ readTypedJsonSync: vi.fn(),
22
+ };
23
+ });
24
+
25
+ const fs = vi.mocked((await import("fs-extra")).default);
26
+ const { readTypedJsonSync } = vi.mocked(await import("~/lib/utils"));
27
+
28
+ /** Reusable packages registry fixture */
29
+ function createPackagesRegistry() {
30
+ return {
31
+ shared: {
32
+ absoluteDir: "/workspace/packages/shared",
33
+ rootRelativeDir: "packages/shared",
34
+ manifest: {
35
+ name: "shared",
36
+ version: "1.0.0",
37
+ },
38
+ },
39
+ utils: {
40
+ absoluteDir: "/workspace/packages/utils",
41
+ rootRelativeDir: "packages/utils",
42
+ manifest: {
43
+ name: "utils",
44
+ version: "1.0.0",
45
+ },
46
+ },
47
+ };
48
+ }
49
+
50
+ type BunLockfileFixture = {
51
+ lockfileVersion: number;
52
+ workspaces: Record<string, Record<string, unknown>>;
53
+ packages: Record<string, unknown[]>;
54
+ overrides?: Record<string, string>;
55
+ trustedDependencies?: string[];
56
+ patchedDependencies?: Record<string, string>;
57
+ };
58
+
59
+ /** Reusable bun lockfile fixture */
60
+ function createBunLockfile(): BunLockfileFixture {
61
+ return {
62
+ lockfileVersion: 0,
63
+ workspaces: {
64
+ "": {
65
+ name: "root",
66
+ dependencies: { lodash: "^4.17.21" },
67
+ },
68
+ "apps/my-app": {
69
+ name: "my-app",
70
+ dependencies: {
71
+ shared: "workspace:*",
72
+ express: "^4.18.0",
73
+ },
74
+ devDependencies: {
75
+ vitest: "^1.0.0",
76
+ },
77
+ },
78
+ "packages/shared": {
79
+ name: "shared",
80
+ version: "1.0.0",
81
+ dependencies: {
82
+ utils: "workspace:*",
83
+ lodash: "^4.17.21",
84
+ },
85
+ devDependencies: {
86
+ typescript: "^5.0.0",
87
+ },
88
+ },
89
+ "packages/utils": {
90
+ name: "utils",
91
+ version: "1.0.0",
92
+ dependencies: {},
93
+ },
94
+ "packages/other": {
95
+ name: "other",
96
+ version: "1.0.0",
97
+ dependencies: {
98
+ axios: "^1.0.0",
99
+ },
100
+ },
101
+ },
102
+ packages: {
103
+ express: [
104
+ "express@4.18.2",
105
+ "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
106
+ { dependencies: { "body-parser": "1.20.1" } },
107
+ "sha512-abc",
108
+ ],
109
+ "body-parser": [
110
+ "body-parser@1.20.1",
111
+ "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
112
+ {},
113
+ "sha512-def",
114
+ ],
115
+ lodash: [
116
+ "lodash@4.17.21",
117
+ "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
118
+ {},
119
+ "sha512-ghi",
120
+ ],
121
+ vitest: [
122
+ "vitest@1.0.0",
123
+ "https://registry.npmjs.org/vitest/-/vitest-1.0.0.tgz",
124
+ {},
125
+ "sha512-jkl",
126
+ ],
127
+ typescript: [
128
+ "typescript@5.3.0",
129
+ "https://registry.npmjs.org/typescript/-/typescript-5.3.0.tgz",
130
+ {},
131
+ "sha512-mno",
132
+ ],
133
+ axios: [
134
+ "axios@1.6.0",
135
+ "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
136
+ {},
137
+ "sha512-pqr",
138
+ ],
139
+ shared: ["shared@workspace:packages/shared", {}],
140
+ utils: ["utils@workspace:packages/utils", {}],
141
+ other: ["other@workspace:packages/other", {}],
142
+ },
143
+ };
144
+ }
145
+
146
+ describe("serializeWithTrailingCommas", () => {
147
+ it("should add trailing commas to JSON output", () => {
148
+ const input = { a: 1, b: [2, 3] };
149
+ const result = serializeWithTrailingCommas(input);
150
+
151
+ expect(result).toContain('"a": 1,');
152
+ expect(result).toContain("3,\n");
153
+ expect(result).toMatch(/\}$/);
154
+ });
155
+
156
+ it("should handle nested objects", () => {
157
+ const input = { a: { b: "c" } };
158
+ const result = serializeWithTrailingCommas(input);
159
+ const parsed = JSON.parse(result.replace(/,(\s*[}\]])/g, "$1"));
160
+ expect(parsed).toEqual(input);
161
+ });
162
+
163
+ it("should handle boolean and null values", () => {
164
+ const input = {
165
+ enabled: true,
166
+ disabled: false,
167
+ empty: null,
168
+ list: [true, null, 42],
169
+ };
170
+ const result = serializeWithTrailingCommas(input);
171
+ /** Trailing commas after each value type */
172
+ expect(result).toContain("true,");
173
+ expect(result).toContain("false,");
174
+ expect(result).toContain("null,");
175
+ expect(result).toContain("42,");
176
+ /** Round-trips correctly */
177
+ const parsed = JSON.parse(result.replace(/,(\s*[}\]])/g, "$1"));
178
+ expect(parsed).toEqual(input);
179
+ });
180
+
181
+ it("should produce valid content that round-trips through strip-json-comments", () => {
182
+ const input = { key: "value", arr: [1, 2] };
183
+ const result = serializeWithTrailingCommas(input);
184
+ /** The output has trailing commas, which strip-json-comments can handle */
185
+ expect(result).toMatch(/,\n\s*\]/);
186
+ expect(result).toMatch(/,\n\s*\}/);
187
+ });
188
+ });
189
+
190
+ describe("generateBunLockfile", () => {
191
+ beforeEach(() => {
192
+ vi.clearAllMocks();
193
+ });
194
+
195
+ afterEach(() => {
196
+ vi.restoreAllMocks();
197
+ });
198
+
199
+ it("should throw when bun.lock is missing", async () => {
200
+ fs.existsSync.mockReturnValue(false);
201
+
202
+ await expect(
203
+ generateBunLockfile({
204
+ workspaceRootDir: "/workspace",
205
+ targetPackageDir: "/workspace/apps/my-app",
206
+ isolateDir: "/workspace/apps/my-app/isolate",
207
+ internalDepPackageNames: ["shared"],
208
+ packagesRegistry: createPackagesRegistry(),
209
+ includeDevDependencies: false,
210
+ }),
211
+ ).rejects.toThrow("Failed to find bun.lock");
212
+ });
213
+
214
+ it("should filter workspaces to only target and internal deps", async () => {
215
+ fs.existsSync.mockReturnValue(true);
216
+ fs.writeFile.mockResolvedValue();
217
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
218
+
219
+ await generateBunLockfile({
220
+ workspaceRootDir: "/workspace",
221
+ targetPackageDir: "/workspace/apps/my-app",
222
+ isolateDir: "/workspace/apps/my-app/isolate",
223
+ internalDepPackageNames: ["shared", "utils"],
224
+ packagesRegistry: createPackagesRegistry(),
225
+
226
+ includeDevDependencies: false,
227
+ });
228
+
229
+ const writeCall = fs.writeFile.mock.calls[0]!;
230
+ const written = JSON.parse(
231
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
232
+ );
233
+
234
+ /** Target is remapped to "" */
235
+ expect(written.workspaces[""]).toBeDefined();
236
+ expect(written.workspaces[""].name).toBe("my-app");
237
+
238
+ /** Internal deps are present */
239
+ expect(written.workspaces["packages/shared"]).toBeDefined();
240
+ expect(written.workspaces["packages/utils"]).toBeDefined();
241
+
242
+ /** Root workspace and non-internal packages are excluded */
243
+ expect(written.workspaces["packages/other"]).toBeUndefined();
244
+ expect(written.workspaces["apps/my-app"]).toBeUndefined();
245
+ });
246
+
247
+ it("should remap target workspace to root key", async () => {
248
+ fs.existsSync.mockReturnValue(true);
249
+ fs.writeFile.mockResolvedValue();
250
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
251
+
252
+ await generateBunLockfile({
253
+ workspaceRootDir: "/workspace",
254
+ targetPackageDir: "/workspace/apps/my-app",
255
+ isolateDir: "/workspace/apps/my-app/isolate",
256
+ internalDepPackageNames: ["shared"],
257
+ packagesRegistry: createPackagesRegistry(),
258
+
259
+ includeDevDependencies: false,
260
+ });
261
+
262
+ const writeCall = fs.writeFile.mock.calls[0]!;
263
+ const written = JSON.parse(
264
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
265
+ );
266
+
267
+ expect(written.workspaces[""]).toBeDefined();
268
+ expect(written.workspaces["apps/my-app"]).toBeUndefined();
269
+ });
270
+
271
+ it("should prune unreferenced packages", async () => {
272
+ fs.existsSync.mockReturnValue(true);
273
+ fs.writeFile.mockResolvedValue();
274
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
275
+
276
+ await generateBunLockfile({
277
+ workspaceRootDir: "/workspace",
278
+ targetPackageDir: "/workspace/apps/my-app",
279
+ isolateDir: "/workspace/apps/my-app/isolate",
280
+ internalDepPackageNames: ["shared", "utils"],
281
+ packagesRegistry: createPackagesRegistry(),
282
+
283
+ includeDevDependencies: false,
284
+ });
285
+
286
+ const writeCall = fs.writeFile.mock.calls[0]!;
287
+ const written = JSON.parse(
288
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
289
+ );
290
+
291
+ /** express and its transitive dep body-parser should be kept */
292
+ expect(written.packages["express"]).toBeDefined();
293
+ expect(written.packages["body-parser"]).toBeDefined();
294
+
295
+ /** lodash is used by shared, should be kept */
296
+ expect(written.packages["lodash"]).toBeDefined();
297
+
298
+ /** Workspace entries for kept internal deps should be kept */
299
+ expect(written.packages["shared"]).toBeDefined();
300
+ expect(written.packages["utils"]).toBeDefined();
301
+
302
+ /** axios is only used by "other" which is not in the isolate */
303
+ expect(written.packages["axios"]).toBeUndefined();
304
+
305
+ /** Workspace entry for "other" should be removed */
306
+ expect(written.packages["other"]).toBeUndefined();
307
+ });
308
+
309
+ it("should remove workspace package entries for non-isolated packages", async () => {
310
+ fs.existsSync.mockReturnValue(true);
311
+ fs.writeFile.mockResolvedValue();
312
+
313
+ const lockfile = createBunLockfile();
314
+ /** Add a reference to "other" from the target so it's in requiredPackages */
315
+ const appWorkspace = lockfile.workspaces["apps/my-app"]!;
316
+ (appWorkspace.dependencies as Record<string, string>)["other"] =
317
+ "workspace:*";
318
+ readTypedJsonSync.mockReturnValue(lockfile);
319
+
320
+ await generateBunLockfile({
321
+ workspaceRootDir: "/workspace",
322
+ targetPackageDir: "/workspace/apps/my-app",
323
+ isolateDir: "/workspace/apps/my-app/isolate",
324
+ internalDepPackageNames: ["shared", "utils"],
325
+ packagesRegistry: createPackagesRegistry(),
326
+
327
+ includeDevDependencies: false,
328
+ });
329
+
330
+ const writeCall = fs.writeFile.mock.calls[0]!;
331
+ const written = JSON.parse(
332
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
333
+ );
334
+
335
+ /**
336
+ * "other" is referenced but not in internalDepPackageNames, so its
337
+ * workspace entry should be removed from packages
338
+ */
339
+ expect(written.packages["other"]).toBeUndefined();
340
+ });
341
+
342
+ it("should exclude devDependencies from target when includeDevDependencies is false", async () => {
343
+ fs.existsSync.mockReturnValue(true);
344
+ fs.writeFile.mockResolvedValue();
345
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
346
+
347
+ await generateBunLockfile({
348
+ workspaceRootDir: "/workspace",
349
+ targetPackageDir: "/workspace/apps/my-app",
350
+ isolateDir: "/workspace/apps/my-app/isolate",
351
+ internalDepPackageNames: ["shared"],
352
+ packagesRegistry: createPackagesRegistry(),
353
+
354
+ includeDevDependencies: false,
355
+ });
356
+
357
+ const writeCall = fs.writeFile.mock.calls[0]!;
358
+ const written = JSON.parse(
359
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
360
+ );
361
+
362
+ /** Target workspace should not have devDependencies */
363
+ expect(written.workspaces[""].devDependencies).toBeUndefined();
364
+
365
+ /** vitest (a devDep) should not be in packages */
366
+ expect(written.packages["vitest"]).toBeUndefined();
367
+ });
368
+
369
+ it("should include devDependencies from target when includeDevDependencies is true", async () => {
370
+ fs.existsSync.mockReturnValue(true);
371
+ fs.writeFile.mockResolvedValue();
372
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
373
+
374
+ await generateBunLockfile({
375
+ workspaceRootDir: "/workspace",
376
+ targetPackageDir: "/workspace/apps/my-app",
377
+ isolateDir: "/workspace/apps/my-app/isolate",
378
+ internalDepPackageNames: ["shared"],
379
+ packagesRegistry: createPackagesRegistry(),
380
+
381
+ includeDevDependencies: true,
382
+ });
383
+
384
+ const writeCall = fs.writeFile.mock.calls[0]!;
385
+ const written = JSON.parse(
386
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
387
+ );
388
+
389
+ /** Target workspace should have devDependencies */
390
+ expect(written.workspaces[""].devDependencies).toEqual({
391
+ vitest: "^1.0.0",
392
+ });
393
+
394
+ /** vitest should be in packages */
395
+ expect(written.packages["vitest"]).toBeDefined();
396
+ });
397
+
398
+ it("should always strip devDependencies from internal deps", async () => {
399
+ fs.existsSync.mockReturnValue(true);
400
+ fs.writeFile.mockResolvedValue();
401
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
402
+
403
+ await generateBunLockfile({
404
+ workspaceRootDir: "/workspace",
405
+ targetPackageDir: "/workspace/apps/my-app",
406
+ isolateDir: "/workspace/apps/my-app/isolate",
407
+ internalDepPackageNames: ["shared", "utils"],
408
+ packagesRegistry: createPackagesRegistry(),
409
+
410
+ includeDevDependencies: true,
411
+ });
412
+
413
+ const writeCall = fs.writeFile.mock.calls[0]!;
414
+ const written = JSON.parse(
415
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
416
+ );
417
+
418
+ /** Internal dep "shared" should not have devDependencies */
419
+ expect(
420
+ written.workspaces["packages/shared"].devDependencies,
421
+ ).toBeUndefined();
422
+
423
+ /** typescript (shared's devDep) should not be in packages */
424
+ expect(written.packages["typescript"]).toBeUndefined();
425
+ });
426
+
427
+ it("should preserve overrides", async () => {
428
+ fs.existsSync.mockReturnValue(true);
429
+ fs.writeFile.mockResolvedValue();
430
+
431
+ const lockfile = createBunLockfile();
432
+ lockfile.overrides = { lodash: "4.17.21" };
433
+ readTypedJsonSync.mockReturnValue(lockfile);
434
+
435
+ await generateBunLockfile({
436
+ workspaceRootDir: "/workspace",
437
+ targetPackageDir: "/workspace/apps/my-app",
438
+ isolateDir: "/workspace/apps/my-app/isolate",
439
+ internalDepPackageNames: ["shared"],
440
+ packagesRegistry: createPackagesRegistry(),
441
+
442
+ includeDevDependencies: false,
443
+ });
444
+
445
+ const writeCall = fs.writeFile.mock.calls[0]!;
446
+ const written = JSON.parse(
447
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
448
+ );
449
+
450
+ expect(written.overrides).toEqual({ lodash: "4.17.21" });
451
+ });
452
+
453
+ it("should filter trustedDependencies to only included packages", async () => {
454
+ fs.existsSync.mockReturnValue(true);
455
+ fs.writeFile.mockResolvedValue();
456
+
457
+ const lockfile = createBunLockfile();
458
+ lockfile.trustedDependencies = ["express", "axios", "lodash"];
459
+ readTypedJsonSync.mockReturnValue(lockfile);
460
+
461
+ await generateBunLockfile({
462
+ workspaceRootDir: "/workspace",
463
+ targetPackageDir: "/workspace/apps/my-app",
464
+ isolateDir: "/workspace/apps/my-app/isolate",
465
+ internalDepPackageNames: ["shared"],
466
+ packagesRegistry: createPackagesRegistry(),
467
+
468
+ includeDevDependencies: false,
469
+ });
470
+
471
+ const writeCall = fs.writeFile.mock.calls[0]!;
472
+ const written = JSON.parse(
473
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
474
+ );
475
+
476
+ /** axios is not in the output, so it should be filtered out */
477
+ expect(written.trustedDependencies).toEqual(
478
+ expect.arrayContaining(["express", "lodash"]),
479
+ );
480
+ expect(written.trustedDependencies).not.toContain("axios");
481
+ });
482
+
483
+ it("should filter patchedDependencies to only included packages", async () => {
484
+ fs.existsSync.mockReturnValue(true);
485
+ fs.writeFile.mockResolvedValue();
486
+
487
+ const lockfile = createBunLockfile();
488
+ lockfile.patchedDependencies = {
489
+ "express@4.18.2": "patches/express.patch",
490
+ "axios@1.6.0": "patches/axios.patch",
491
+ };
492
+ readTypedJsonSync.mockReturnValue(lockfile);
493
+
494
+ await generateBunLockfile({
495
+ workspaceRootDir: "/workspace",
496
+ targetPackageDir: "/workspace/apps/my-app",
497
+ isolateDir: "/workspace/apps/my-app/isolate",
498
+ internalDepPackageNames: ["shared"],
499
+ packagesRegistry: createPackagesRegistry(),
500
+
501
+ includeDevDependencies: false,
502
+ });
503
+
504
+ const writeCall = fs.writeFile.mock.calls[0]!;
505
+ const written = JSON.parse(
506
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
507
+ );
508
+
509
+ expect(written.patchedDependencies).toEqual({
510
+ "express@4.18.2": "patches/express.patch",
511
+ });
512
+ /** axios is not in the output */
513
+ expect(written.patchedDependencies["axios@1.6.0"]).toBeUndefined();
514
+ });
515
+
516
+ it("should write output with trailing commas", async () => {
517
+ fs.existsSync.mockReturnValue(true);
518
+ fs.writeFile.mockResolvedValue();
519
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
520
+
521
+ await generateBunLockfile({
522
+ workspaceRootDir: "/workspace",
523
+ targetPackageDir: "/workspace/apps/my-app",
524
+ isolateDir: "/workspace/apps/my-app/isolate",
525
+ internalDepPackageNames: ["shared"],
526
+ packagesRegistry: createPackagesRegistry(),
527
+
528
+ includeDevDependencies: false,
529
+ });
530
+
531
+ const writeCall = fs.writeFile.mock.calls[0]!;
532
+ const content = writeCall[1] as string;
533
+
534
+ /** Should contain trailing commas before closing braces/brackets */
535
+ expect(content).toMatch(/,\n\s*\}/);
536
+ });
537
+
538
+ it("should write to the correct output path", async () => {
539
+ fs.existsSync.mockReturnValue(true);
540
+ fs.writeFile.mockResolvedValue();
541
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
542
+
543
+ await generateBunLockfile({
544
+ workspaceRootDir: "/workspace",
545
+ targetPackageDir: "/workspace/apps/my-app",
546
+ isolateDir: "/workspace/apps/my-app/isolate",
547
+ internalDepPackageNames: ["shared"],
548
+ packagesRegistry: createPackagesRegistry(),
549
+
550
+ includeDevDependencies: false,
551
+ });
552
+
553
+ expect(fs.writeFile).toHaveBeenCalledWith(
554
+ "/workspace/apps/my-app/isolate/bun.lock",
555
+ expect.any(String),
556
+ );
557
+ });
558
+
559
+ it("should preserve lockfileVersion", async () => {
560
+ fs.existsSync.mockReturnValue(true);
561
+ fs.writeFile.mockResolvedValue();
562
+ readTypedJsonSync.mockReturnValue(createBunLockfile());
563
+
564
+ await generateBunLockfile({
565
+ workspaceRootDir: "/workspace",
566
+ targetPackageDir: "/workspace/apps/my-app",
567
+ isolateDir: "/workspace/apps/my-app/isolate",
568
+ internalDepPackageNames: ["shared"],
569
+ packagesRegistry: createPackagesRegistry(),
570
+
571
+ includeDevDependencies: false,
572
+ });
573
+
574
+ const writeCall = fs.writeFile.mock.calls[0]!;
575
+ const written = JSON.parse(
576
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
577
+ );
578
+
579
+ expect(written.lockfileVersion).toBe(0);
580
+ });
581
+
582
+ it("should resolve transitive dependencies", async () => {
583
+ fs.existsSync.mockReturnValue(true);
584
+ fs.writeFile.mockResolvedValue();
585
+
586
+ const lockfile = createBunLockfile();
587
+ /** Add a chain: express -> body-parser -> raw-body */
588
+ lockfile.packages["body-parser"]![2] = {
589
+ dependencies: { "raw-body": "2.5.1" },
590
+ };
591
+ lockfile.packages["raw-body"] = [
592
+ "raw-body@2.5.1",
593
+ "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
594
+ {},
595
+ "sha512-stu",
596
+ ];
597
+ readTypedJsonSync.mockReturnValue(lockfile);
598
+
599
+ await generateBunLockfile({
600
+ workspaceRootDir: "/workspace",
601
+ targetPackageDir: "/workspace/apps/my-app",
602
+ isolateDir: "/workspace/apps/my-app/isolate",
603
+ internalDepPackageNames: ["shared"],
604
+ packagesRegistry: createPackagesRegistry(),
605
+
606
+ includeDevDependencies: false,
607
+ });
608
+
609
+ const writeCall = fs.writeFile.mock.calls[0]!;
610
+ const written = JSON.parse(
611
+ (writeCall[1] as string).replace(/,(\s*[}\]])/g, "$1"),
612
+ );
613
+
614
+ /** The full transitive chain should be present */
615
+ expect(written.packages["express"]).toBeDefined();
616
+ expect(written.packages["body-parser"]).toBeDefined();
617
+ expect(written.packages["raw-body"]).toBeDefined();
618
+ });
619
+ });