isolate-package 1.32.1 → 1.33.0-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.
@@ -15,9 +15,17 @@ vi.mock("fs-extra", () => ({
15
15
  vi.mock("~/lib/utils", () => ({
16
16
  filterPatchedDependencies: vi.fn(),
17
17
  getIsolateRelativeLogPath: vi.fn((p: string) => p),
18
+ getPackageName: vi.fn((spec: string) => {
19
+ if (spec.startsWith("@")) {
20
+ const parts = spec.split("@");
21
+ return `@${parts[1] ?? ""}`;
22
+ }
23
+ return spec.split("@")[0] ?? "";
24
+ }),
18
25
  getRootRelativeLogPath: vi.fn((p: string) => p),
19
26
  isRushWorkspace: vi.fn(() => false),
20
27
  readTypedJson: vi.fn(),
28
+ readTypedJsonSync: vi.fn(),
21
29
  readTypedYamlSync: vi.fn(),
22
30
  }));
23
31
 
@@ -29,16 +37,25 @@ vi.mock("~/lib/package-manager", () => ({
29
37
  /** Mock the pnpm lockfile readers */
30
38
  vi.mock("pnpm_lockfile_file_v8", () => ({
31
39
  readWantedLockfile: vi.fn(() => Promise.resolve(null)),
40
+ getLockfileImporterId: vi.fn(
41
+ (root: string, dir: string) => dir.replace(`${root}/`, "") || ".",
42
+ ),
32
43
  }));
33
44
 
34
45
  vi.mock("pnpm_lockfile_file_v9", () => ({
35
46
  readWantedLockfile: vi.fn(() => Promise.resolve(null)),
47
+ getLockfileImporterId: vi.fn(
48
+ (root: string, dir: string) => dir.replace(`${root}/`, "") || ".",
49
+ ),
36
50
  }));
37
51
 
38
52
  const fs = vi.mocked((await import("fs-extra")).default);
39
53
  const { filterPatchedDependencies, readTypedJson, readTypedYamlSync } =
40
54
  vi.mocked(await import("~/lib/utils"));
41
55
  const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
56
+ const { readWantedLockfile: readWantedLockfile_v9 } = vi.mocked(
57
+ await import("pnpm_lockfile_file_v9"),
58
+ );
42
59
 
43
60
  describe("copyPatches", () => {
44
61
  beforeEach(() => {
@@ -65,6 +82,8 @@ describe("copyPatches", () => {
65
82
 
66
83
  const result = await copyPatches({
67
84
  workspaceRootDir: "/workspace",
85
+ targetPackageDir: "/workspace/packages/test",
86
+ internalDepPackageNames: [],
68
87
  targetPackageManifest: { name: "test", version: "1.0.0" },
69
88
  isolateDir: "/workspace/isolate",
70
89
  packagesRegistry: {},
@@ -126,6 +145,8 @@ describe("copyPatches", () => {
126
145
 
127
146
  const result = await copyPatches({
128
147
  workspaceRootDir: "/workspace",
148
+ targetPackageDir: "/workspace/packages/test",
149
+ internalDepPackageNames: [],
129
150
  targetPackageManifest: { name: "test", version: "1.0.0" },
130
151
  isolateDir: "/workspace/isolate",
131
152
  packagesRegistry: {},
@@ -146,6 +167,8 @@ describe("copyPatches", () => {
146
167
 
147
168
  const result = await copyPatches({
148
169
  workspaceRootDir: "/workspace",
170
+ targetPackageDir: "/workspace/packages/test",
171
+ internalDepPackageNames: [],
149
172
  targetPackageManifest: { name: "test", version: "1.0.0" },
150
173
  isolateDir: "/workspace/isolate",
151
174
  packagesRegistry: {},
@@ -176,6 +199,8 @@ describe("copyPatches", () => {
176
199
 
177
200
  const result = await copyPatches({
178
201
  workspaceRootDir: "/workspace",
202
+ targetPackageDir: "/workspace/packages/test",
203
+ internalDepPackageNames: [],
179
204
  targetPackageManifest: targetManifest,
180
205
  isolateDir: "/workspace/isolate",
181
206
  packagesRegistry: {},
@@ -214,6 +239,8 @@ describe("copyPatches", () => {
214
239
 
215
240
  const result = await copyPatches({
216
241
  workspaceRootDir: "/workspace",
242
+ targetPackageDir: "/workspace/packages/test",
243
+ internalDepPackageNames: [],
217
244
  targetPackageManifest: targetManifest,
218
245
  isolateDir: "/workspace/isolate",
219
246
  packagesRegistry: {},
@@ -256,6 +283,8 @@ describe("copyPatches", () => {
256
283
 
257
284
  const result = await copyPatches({
258
285
  workspaceRootDir: "/workspace",
286
+ targetPackageDir: "/workspace/packages/test",
287
+ internalDepPackageNames: [],
259
288
  targetPackageManifest: targetManifest,
260
289
  isolateDir: "/workspace/isolate",
261
290
  packagesRegistry: {},
@@ -287,6 +316,8 @@ describe("copyPatches", () => {
287
316
 
288
317
  const result = await copyPatches({
289
318
  workspaceRootDir: "/workspace",
319
+ targetPackageDir: "/workspace/packages/test",
320
+ internalDepPackageNames: [],
290
321
  targetPackageManifest: targetManifest,
291
322
  isolateDir: "/workspace/isolate",
292
323
  packagesRegistry: {},
@@ -321,6 +352,8 @@ describe("copyPatches", () => {
321
352
 
322
353
  const result = await copyPatches({
323
354
  workspaceRootDir: "/workspace",
355
+ targetPackageDir: "/workspace/packages/test",
356
+ internalDepPackageNames: [],
324
357
  targetPackageManifest: targetManifest,
325
358
  isolateDir: "/workspace/isolate",
326
359
  packagesRegistry: {},
@@ -364,6 +397,8 @@ describe("copyPatches", () => {
364
397
 
365
398
  const result = await copyPatches({
366
399
  workspaceRootDir: "/workspace",
400
+ targetPackageDir: "/workspace/packages/test",
401
+ internalDepPackageNames: [],
367
402
  targetPackageManifest: targetManifest,
368
403
  isolateDir: "/workspace/isolate",
369
404
  packagesRegistry: {},
@@ -409,6 +444,8 @@ describe("copyPatches", () => {
409
444
 
410
445
  const result = await copyPatches({
411
446
  workspaceRootDir: "/workspace",
447
+ targetPackageDir: "/workspace/packages/test",
448
+ internalDepPackageNames: [],
412
449
  targetPackageManifest: targetManifest,
413
450
  isolateDir: "/workspace/isolate",
414
451
  packagesRegistry: {},
@@ -456,6 +493,8 @@ describe("copyPatches", () => {
456
493
 
457
494
  const result = await copyPatches({
458
495
  workspaceRootDir: "/workspace",
496
+ targetPackageDir: "/workspace/packages/test",
497
+ internalDepPackageNames: [],
459
498
  targetPackageManifest: targetManifest,
460
499
  isolateDir: "/workspace/isolate",
461
500
  packagesRegistry: {},
@@ -509,6 +548,8 @@ describe("copyPatches", () => {
509
548
 
510
549
  const result = await copyPatches({
511
550
  workspaceRootDir: "/workspace",
551
+ targetPackageDir: "/workspace/packages/test",
552
+ internalDepPackageNames: [],
512
553
  targetPackageManifest: consumerManifest,
513
554
  isolateDir: "/workspace/isolate",
514
555
  packagesRegistry: {
@@ -536,4 +577,88 @@ describe("copyPatches", () => {
536
577
  expect(reachable!.has("firebase-package")).toBe(true);
537
578
  expect(reachable!.has("tslib")).toBe(true);
538
579
  });
580
+
581
+ it("should pick up deep external-to-external transitives from the pnpm lockfile (regression: issue #167 follow-up)", async () => {
582
+ /**
583
+ * Target depends on `@react-pdf/renderer` (external). The patched
584
+ * `@react-pdf/render` is only a transitive of `@react-pdf/renderer`. The
585
+ * manifest walker can't see it because it can't open external manifests,
586
+ * so the lockfile walker has to surface it.
587
+ */
588
+ const targetManifest: PackageManifest = {
589
+ name: "consumer",
590
+ version: "1.0.0",
591
+ dependencies: { "@react-pdf/renderer": "^4.0.0" },
592
+ };
593
+
594
+ readTypedYamlSync.mockReturnValue({
595
+ patchedDependencies: {
596
+ "@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
597
+ },
598
+ });
599
+ readTypedJson.mockResolvedValue({
600
+ name: "root",
601
+ version: "1.0.0",
602
+ } as PackageManifest);
603
+
604
+ filterPatchedDependencies.mockReturnValue({
605
+ "@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
606
+ });
607
+
608
+ fs.existsSync.mockReturnValue(true);
609
+
610
+ usePackageManager.mockReturnValue({
611
+ name: "pnpm",
612
+ majorVersion: 9,
613
+ version: "9.0.0",
614
+ packageManagerString: "pnpm@9.0.0",
615
+ });
616
+
617
+ /**
618
+ * Fake v9 lockfile: target importer depends on @react-pdf/renderer, which
619
+ * has @react-pdf/render as its only resolved dep.
620
+ */
621
+ readWantedLockfile_v9.mockResolvedValue({
622
+ lockfileVersion: "9.0",
623
+ importers: {
624
+ "packages/consumer": {
625
+ specifiers: { "@react-pdf/renderer": "^4.0.0" },
626
+ dependencies: { "@react-pdf/renderer": "4.0.0" },
627
+ },
628
+ },
629
+ packages: {
630
+ "@react-pdf/renderer@4.0.0": {
631
+ resolution: { integrity: "sha512-x" },
632
+ dependencies: { "@react-pdf/render": "4.3.0" },
633
+ },
634
+ "@react-pdf/render@4.3.0": {
635
+ resolution: { integrity: "sha512-y" },
636
+ },
637
+ },
638
+ } as unknown as Awaited<ReturnType<typeof readWantedLockfile_v9>>);
639
+
640
+ const result = await copyPatches({
641
+ workspaceRootDir: "/workspace",
642
+ targetPackageDir: "/workspace/packages/consumer",
643
+ internalDepPackageNames: [],
644
+ targetPackageManifest: targetManifest,
645
+ isolateDir: "/workspace/isolate",
646
+ packagesRegistry: {},
647
+ includeDevDependencies: false,
648
+ });
649
+
650
+ expect(result).toEqual({
651
+ "@react-pdf/render@4.3.0": {
652
+ path: "patches/@react-pdf__render@4.3.0.patch",
653
+ hash: "",
654
+ },
655
+ });
656
+
657
+ const filterCall = filterPatchedDependencies.mock.calls[0]?.[0];
658
+ expect(filterCall).toBeDefined();
659
+ const reachable = filterCall!.reachableDependencyNames;
660
+ expect(reachable).toBeInstanceOf(Set);
661
+ expect(reachable!.has("@react-pdf/renderer")).toBe(true);
662
+ expect(reachable!.has("@react-pdf/render")).toBe(true);
663
+ });
539
664
  });
@@ -18,23 +18,29 @@ import {
18
18
  readTypedJson,
19
19
  readTypedYamlSync,
20
20
  } from "~/lib/utils";
21
+ import { collectInstalledNamesFromBunLockfile } from "./collect-installed-names-bun";
22
+ import { collectInstalledNamesFromPnpmLockfile } from "./collect-installed-names-pnpm";
21
23
 
22
24
  export async function copyPatches({
23
25
  workspaceRootDir,
26
+ targetPackageDir,
24
27
  targetPackageManifest,
25
28
  packagesRegistry,
29
+ internalDepPackageNames,
26
30
  isolateDir,
27
31
  includeDevDependencies,
28
32
  }: {
29
33
  workspaceRootDir: string;
34
+ targetPackageDir: string;
30
35
  targetPackageManifest: PackageManifest;
31
36
  packagesRegistry: PackagesRegistry;
37
+ internalDepPackageNames: string[];
32
38
  isolateDir: string;
33
39
  includeDevDependencies: boolean;
34
40
  }): Promise<Record<string, PatchFile>> {
35
41
  const log = useLogger();
36
42
 
37
- const { name: packageManagerName } = usePackageManager();
43
+ const { name: packageManagerName, majorVersion } = usePackageManager();
38
44
 
39
45
  let patchedDependencies: Record<string, string> | undefined;
40
46
 
@@ -102,6 +108,37 @@ export async function copyPatches({
102
108
  includeDevDependencies,
103
109
  });
104
110
 
111
+ /**
112
+ * Manifest-based reachability misses external→external transitives because
113
+ * external manifests aren't loaded here. Walk the package-manager's
114
+ * lockfile to also pick up those names, so a patch for a deeply-nested
115
+ * external dep (e.g. `@react-pdf/render` reached via `@react-pdf/renderer`)
116
+ * survives isolation.
117
+ */
118
+ const lockfileInstalledNames =
119
+ packageManagerName === "pnpm"
120
+ ? await collectInstalledNamesFromPnpmLockfile({
121
+ workspaceRootDir,
122
+ targetPackageDir,
123
+ internalDepPackageNames,
124
+ packagesRegistry,
125
+ majorVersion,
126
+ includeDevDependencies,
127
+ })
128
+ : packageManagerName === "bun"
129
+ ? collectInstalledNamesFromBunLockfile({
130
+ workspaceRootDir,
131
+ targetPackageDir,
132
+ internalDepPackageNames,
133
+ packagesRegistry,
134
+ includeDevDependencies,
135
+ })
136
+ : new Set<string>();
137
+
138
+ for (const name of lockfileInstalledNames) {
139
+ reachableDependencyNames.add(name);
140
+ }
141
+
105
142
  const filteredPatches = filterPatchedDependencies({
106
143
  patchedDependencies,
107
144
  targetPackageManifest,