isolate-package 1.33.0 → 1.34.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/index.mjs.map +1 -1
- package/dist/{isolate-DyRD5Zd_.mjs → isolate-DI3eUTci.mjs} +862 -263
- package/dist/isolate-DI3eUTci.mjs.map +1 -0
- package/dist/isolate-bin.mjs +5 -6
- package/dist/isolate-bin.mjs.map +1 -1
- package/package.json +23 -19
- package/src/get-internal-package-names.test.ts +1 -1
- package/src/get-internal-package-names.ts +2 -2
- package/src/isolate-bin.ts +5 -5
- package/src/isolate.ts +22 -17
- package/src/lib/config.test.ts +1 -1
- package/src/lib/config.ts +3 -3
- package/src/lib/lockfile/helpers/bun-lockfile.ts +153 -0
- package/src/lib/lockfile/helpers/generate-bun-lockfile.test.ts +3 -3
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +14 -146
- package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +1 -5
- package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +311 -16
- package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +193 -22
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.test.ts +2 -2
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.ts +6 -6
- package/src/lib/lockfile/helpers/generate-yarn-lockfile.ts +5 -5
- package/src/lib/lockfile/process-lockfile.test.ts +2 -2
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +3 -3
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +2 -2
- package/src/lib/manifest/helpers/adapt-manifest-internal-deps.ts +1 -1
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +4 -4
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +7 -7
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.test.ts +410 -0
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.ts +115 -27
- package/src/lib/manifest/io.ts +6 -2
- package/src/lib/manifest/validate-manifest.ts +2 -2
- package/src/lib/output/get-build-output-dir.ts +1 -1
- package/src/lib/output/pack-dependencies.ts +1 -1
- package/src/lib/output/process-build-output-files.ts +6 -17
- package/src/lib/package-manager/helpers/infer-from-files.ts +5 -5
- package/src/lib/package-manager/helpers/infer-from-manifest.ts +7 -8
- package/src/lib/package-manager/index.ts +1 -1
- package/src/lib/package-manager/names.ts +8 -10
- package/src/lib/patches/collect-installed-names-bun.test.ts +154 -0
- package/src/lib/patches/collect-installed-names-bun.ts +87 -0
- package/src/lib/patches/collect-installed-names-pnpm.test.ts +316 -0
- package/src/lib/patches/collect-installed-names-pnpm.ts +365 -0
- package/src/lib/patches/copy-patches.test.ts +130 -13
- package/src/lib/patches/copy-patches.ts +47 -10
- package/src/lib/patches/write-isolate-pnpm-workspace.test.ts +83 -3
- package/src/lib/patches/write-isolate-pnpm-workspace.ts +4 -4
- package/src/lib/registry/collect-reachable-package-names.test.ts +1 -1
- package/src/lib/registry/create-packages-registry.ts +34 -31
- package/src/lib/registry/helpers/find-packages-globs.ts +23 -19
- package/src/lib/registry/list-internal-packages.test.ts +2 -2
- package/src/lib/types.ts +2 -2
- package/src/lib/utils/filter-patched-dependencies.test.ts +1 -1
- package/src/lib/utils/filter-patched-dependencies.ts +2 -2
- package/src/lib/utils/get-dirname.ts +1 -1
- package/src/lib/utils/index.ts +1 -1
- package/src/lib/utils/json.ts +12 -14
- package/src/lib/utils/pack.ts +32 -22
- package/src/lib/utils/reset-isolate-dir.test.ts +165 -0
- package/src/lib/utils/reset-isolate-dir.ts +147 -0
- package/src/lib/utils/unpack.test.ts +76 -0
- package/src/lib/utils/unpack.ts +16 -10
- package/src/lib/utils/wait-for-complete-file.test.ts +105 -0
- package/src/lib/utils/wait-for-complete-file.ts +44 -0
- package/src/lib/utils/yaml.ts +8 -9
- package/src/testing/setup.ts +1 -1
- package/dist/isolate-DyRD5Zd_.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import type { PackageManifest, PnpmSettings } from "
|
|
2
|
+
import type { PackageManifest, PnpmSettings } from "#/lib/types";
|
|
3
3
|
import { copyPatches } from "./copy-patches";
|
|
4
4
|
|
|
5
5
|
/** Mock fs-extra */
|
|
@@ -12,33 +12,50 @@ vi.mock("fs-extra", () => ({
|
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
14
|
/** Mock the utils */
|
|
15
|
-
vi.mock("
|
|
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
|
|
|
24
32
|
/** Mock the package manager */
|
|
25
|
-
vi.mock("
|
|
33
|
+
vi.mock("#/lib/package-manager", () => ({
|
|
26
34
|
usePackageManager: vi.fn(() => ({ name: "pnpm", majorVersion: 9 })),
|
|
27
35
|
}));
|
|
28
36
|
|
|
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
|
-
vi.mocked(await import("
|
|
41
|
-
const { usePackageManager } = vi.mocked(await import("
|
|
54
|
+
vi.mocked(await import("#/lib/utils"));
|
|
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(() => {
|
|
@@ -49,14 +66,6 @@ describe("copyPatches", () => {
|
|
|
49
66
|
vi.restoreAllMocks();
|
|
50
67
|
});
|
|
51
68
|
|
|
52
|
-
const mockJsonSettings = (settings: PnpmSettings | undefined) => {
|
|
53
|
-
readTypedJson.mockResolvedValue({
|
|
54
|
-
name: "root",
|
|
55
|
-
version: "1.0.0",
|
|
56
|
-
pnpm: settings,
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
|
|
60
69
|
it("should return empty object when workspace root package.json cannot be read", async () => {
|
|
61
70
|
readTypedYamlSync.mockImplementation(() => {
|
|
62
71
|
throw new Error("File not found");
|
|
@@ -65,6 +74,8 @@ describe("copyPatches", () => {
|
|
|
65
74
|
|
|
66
75
|
const result = await copyPatches({
|
|
67
76
|
workspaceRootDir: "/workspace",
|
|
77
|
+
targetPackageDir: "/workspace/packages/test",
|
|
78
|
+
internalDepPackageNames: [],
|
|
68
79
|
targetPackageManifest: { name: "test", version: "1.0.0" },
|
|
69
80
|
isolateDir: "/workspace/isolate",
|
|
70
81
|
packagesRegistry: {},
|
|
@@ -126,6 +137,8 @@ describe("copyPatches", () => {
|
|
|
126
137
|
|
|
127
138
|
const result = await copyPatches({
|
|
128
139
|
workspaceRootDir: "/workspace",
|
|
140
|
+
targetPackageDir: "/workspace/packages/test",
|
|
141
|
+
internalDepPackageNames: [],
|
|
129
142
|
targetPackageManifest: { name: "test", version: "1.0.0" },
|
|
130
143
|
isolateDir: "/workspace/isolate",
|
|
131
144
|
packagesRegistry: {},
|
|
@@ -146,6 +159,8 @@ describe("copyPatches", () => {
|
|
|
146
159
|
|
|
147
160
|
const result = await copyPatches({
|
|
148
161
|
workspaceRootDir: "/workspace",
|
|
162
|
+
targetPackageDir: "/workspace/packages/test",
|
|
163
|
+
internalDepPackageNames: [],
|
|
149
164
|
targetPackageManifest: { name: "test", version: "1.0.0" },
|
|
150
165
|
isolateDir: "/workspace/isolate",
|
|
151
166
|
packagesRegistry: {},
|
|
@@ -176,6 +191,8 @@ describe("copyPatches", () => {
|
|
|
176
191
|
|
|
177
192
|
const result = await copyPatches({
|
|
178
193
|
workspaceRootDir: "/workspace",
|
|
194
|
+
targetPackageDir: "/workspace/packages/test",
|
|
195
|
+
internalDepPackageNames: [],
|
|
179
196
|
targetPackageManifest: targetManifest,
|
|
180
197
|
isolateDir: "/workspace/isolate",
|
|
181
198
|
packagesRegistry: {},
|
|
@@ -214,6 +231,8 @@ describe("copyPatches", () => {
|
|
|
214
231
|
|
|
215
232
|
const result = await copyPatches({
|
|
216
233
|
workspaceRootDir: "/workspace",
|
|
234
|
+
targetPackageDir: "/workspace/packages/test",
|
|
235
|
+
internalDepPackageNames: [],
|
|
217
236
|
targetPackageManifest: targetManifest,
|
|
218
237
|
isolateDir: "/workspace/isolate",
|
|
219
238
|
packagesRegistry: {},
|
|
@@ -256,6 +275,8 @@ describe("copyPatches", () => {
|
|
|
256
275
|
|
|
257
276
|
const result = await copyPatches({
|
|
258
277
|
workspaceRootDir: "/workspace",
|
|
278
|
+
targetPackageDir: "/workspace/packages/test",
|
|
279
|
+
internalDepPackageNames: [],
|
|
259
280
|
targetPackageManifest: targetManifest,
|
|
260
281
|
isolateDir: "/workspace/isolate",
|
|
261
282
|
packagesRegistry: {},
|
|
@@ -287,6 +308,8 @@ describe("copyPatches", () => {
|
|
|
287
308
|
|
|
288
309
|
const result = await copyPatches({
|
|
289
310
|
workspaceRootDir: "/workspace",
|
|
311
|
+
targetPackageDir: "/workspace/packages/test",
|
|
312
|
+
internalDepPackageNames: [],
|
|
290
313
|
targetPackageManifest: targetManifest,
|
|
291
314
|
isolateDir: "/workspace/isolate",
|
|
292
315
|
packagesRegistry: {},
|
|
@@ -321,6 +344,8 @@ describe("copyPatches", () => {
|
|
|
321
344
|
|
|
322
345
|
const result = await copyPatches({
|
|
323
346
|
workspaceRootDir: "/workspace",
|
|
347
|
+
targetPackageDir: "/workspace/packages/test",
|
|
348
|
+
internalDepPackageNames: [],
|
|
324
349
|
targetPackageManifest: targetManifest,
|
|
325
350
|
isolateDir: "/workspace/isolate",
|
|
326
351
|
packagesRegistry: {},
|
|
@@ -364,6 +389,8 @@ describe("copyPatches", () => {
|
|
|
364
389
|
|
|
365
390
|
const result = await copyPatches({
|
|
366
391
|
workspaceRootDir: "/workspace",
|
|
392
|
+
targetPackageDir: "/workspace/packages/test",
|
|
393
|
+
internalDepPackageNames: [],
|
|
367
394
|
targetPackageManifest: targetManifest,
|
|
368
395
|
isolateDir: "/workspace/isolate",
|
|
369
396
|
packagesRegistry: {},
|
|
@@ -409,6 +436,8 @@ describe("copyPatches", () => {
|
|
|
409
436
|
|
|
410
437
|
const result = await copyPatches({
|
|
411
438
|
workspaceRootDir: "/workspace",
|
|
439
|
+
targetPackageDir: "/workspace/packages/test",
|
|
440
|
+
internalDepPackageNames: [],
|
|
412
441
|
targetPackageManifest: targetManifest,
|
|
413
442
|
isolateDir: "/workspace/isolate",
|
|
414
443
|
packagesRegistry: {},
|
|
@@ -456,6 +485,8 @@ describe("copyPatches", () => {
|
|
|
456
485
|
|
|
457
486
|
const result = await copyPatches({
|
|
458
487
|
workspaceRootDir: "/workspace",
|
|
488
|
+
targetPackageDir: "/workspace/packages/test",
|
|
489
|
+
internalDepPackageNames: [],
|
|
459
490
|
targetPackageManifest: targetManifest,
|
|
460
491
|
isolateDir: "/workspace/isolate",
|
|
461
492
|
packagesRegistry: {},
|
|
@@ -509,6 +540,8 @@ describe("copyPatches", () => {
|
|
|
509
540
|
|
|
510
541
|
const result = await copyPatches({
|
|
511
542
|
workspaceRootDir: "/workspace",
|
|
543
|
+
targetPackageDir: "/workspace/packages/test",
|
|
544
|
+
internalDepPackageNames: [],
|
|
512
545
|
targetPackageManifest: consumerManifest,
|
|
513
546
|
isolateDir: "/workspace/isolate",
|
|
514
547
|
packagesRegistry: {
|
|
@@ -536,4 +569,88 @@ describe("copyPatches", () => {
|
|
|
536
569
|
expect(reachable!.has("firebase-package")).toBe(true);
|
|
537
570
|
expect(reachable!.has("tslib")).toBe(true);
|
|
538
571
|
});
|
|
572
|
+
|
|
573
|
+
it("should pick up deep external-to-external transitives from the pnpm lockfile (regression: issue #167 follow-up)", async () => {
|
|
574
|
+
/**
|
|
575
|
+
* Target depends on `@react-pdf/renderer` (external). The patched
|
|
576
|
+
* `@react-pdf/render` is only a transitive of `@react-pdf/renderer`. The
|
|
577
|
+
* manifest walker can't see it because it can't open external manifests,
|
|
578
|
+
* so the lockfile walker has to surface it.
|
|
579
|
+
*/
|
|
580
|
+
const targetManifest: PackageManifest = {
|
|
581
|
+
name: "consumer",
|
|
582
|
+
version: "1.0.0",
|
|
583
|
+
dependencies: { "@react-pdf/renderer": "^4.0.0" },
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
readTypedYamlSync.mockReturnValue({
|
|
587
|
+
patchedDependencies: {
|
|
588
|
+
"@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
readTypedJson.mockResolvedValue({
|
|
592
|
+
name: "root",
|
|
593
|
+
version: "1.0.0",
|
|
594
|
+
} as PackageManifest);
|
|
595
|
+
|
|
596
|
+
filterPatchedDependencies.mockReturnValue({
|
|
597
|
+
"@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
fs.existsSync.mockReturnValue(true);
|
|
601
|
+
|
|
602
|
+
usePackageManager.mockReturnValue({
|
|
603
|
+
name: "pnpm",
|
|
604
|
+
majorVersion: 9,
|
|
605
|
+
version: "9.0.0",
|
|
606
|
+
packageManagerString: "pnpm@9.0.0",
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Fake v9 lockfile: target importer depends on @react-pdf/renderer, which
|
|
611
|
+
* has @react-pdf/render as its only resolved dep.
|
|
612
|
+
*/
|
|
613
|
+
readWantedLockfile_v9.mockResolvedValue({
|
|
614
|
+
lockfileVersion: "9.0",
|
|
615
|
+
importers: {
|
|
616
|
+
"packages/consumer": {
|
|
617
|
+
specifiers: { "@react-pdf/renderer": "^4.0.0" },
|
|
618
|
+
dependencies: { "@react-pdf/renderer": "4.0.0" },
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
packages: {
|
|
622
|
+
"@react-pdf/renderer@4.0.0": {
|
|
623
|
+
resolution: { integrity: "sha512-x" },
|
|
624
|
+
dependencies: { "@react-pdf/render": "4.3.0" },
|
|
625
|
+
},
|
|
626
|
+
"@react-pdf/render@4.3.0": {
|
|
627
|
+
resolution: { integrity: "sha512-y" },
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
} as unknown as Awaited<ReturnType<typeof readWantedLockfile_v9>>);
|
|
631
|
+
|
|
632
|
+
const result = await copyPatches({
|
|
633
|
+
workspaceRootDir: "/workspace",
|
|
634
|
+
targetPackageDir: "/workspace/packages/consumer",
|
|
635
|
+
internalDepPackageNames: [],
|
|
636
|
+
targetPackageManifest: targetManifest,
|
|
637
|
+
isolateDir: "/workspace/isolate",
|
|
638
|
+
packagesRegistry: {},
|
|
639
|
+
includeDevDependencies: false,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
expect(result).toEqual({
|
|
643
|
+
"@react-pdf/render@4.3.0": {
|
|
644
|
+
path: "patches/@react-pdf__render@4.3.0.patch",
|
|
645
|
+
hash: "",
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const filterCall = filterPatchedDependencies.mock.calls[0]?.[0];
|
|
650
|
+
expect(filterCall).toBeDefined();
|
|
651
|
+
const reachable = filterCall!.reachableDependencyNames;
|
|
652
|
+
expect(reachable).toBeInstanceOf(Set);
|
|
653
|
+
expect(reachable!.has("@react-pdf/renderer")).toBe(true);
|
|
654
|
+
expect(reachable!.has("@react-pdf/render")).toBe(true);
|
|
655
|
+
});
|
|
539
656
|
});
|
|
@@ -2,39 +2,45 @@ import fs from "fs-extra";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readWantedLockfile as readWantedLockfile_v8 } from "pnpm_lockfile_file_v8";
|
|
4
4
|
import { readWantedLockfile as readWantedLockfile_v9 } from "pnpm_lockfile_file_v9";
|
|
5
|
-
import { useLogger } from "
|
|
6
|
-
import { usePackageManager } from "
|
|
7
|
-
import { collectReachablePackageNames } from "
|
|
5
|
+
import { useLogger } from "#/lib/logger";
|
|
6
|
+
import { usePackageManager } from "#/lib/package-manager";
|
|
7
|
+
import { collectReachablePackageNames } from "#/lib/registry";
|
|
8
8
|
import type {
|
|
9
9
|
PackageManifest,
|
|
10
10
|
PackagesRegistry,
|
|
11
11
|
PatchFile,
|
|
12
12
|
PnpmSettings,
|
|
13
|
-
} from "
|
|
13
|
+
} from "#/lib/types";
|
|
14
14
|
import {
|
|
15
15
|
filterPatchedDependencies,
|
|
16
16
|
getRootRelativeLogPath,
|
|
17
17
|
isRushWorkspace,
|
|
18
18
|
readTypedJson,
|
|
19
19
|
readTypedYamlSync,
|
|
20
|
-
} from "
|
|
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
|
|
|
@@ -44,9 +50,9 @@ export async function copyPatches({
|
|
|
44
50
|
*/
|
|
45
51
|
if (packageManagerName === "pnpm") {
|
|
46
52
|
try {
|
|
47
|
-
const pnpmSettings = readTypedYamlSync
|
|
53
|
+
const pnpmSettings = readTypedYamlSync(
|
|
48
54
|
path.join(workspaceRootDir, "pnpm-workspace.yaml"),
|
|
49
|
-
);
|
|
55
|
+
) as PnpmSettings | undefined;
|
|
50
56
|
patchedDependencies = pnpmSettings?.patchedDependencies;
|
|
51
57
|
} catch (error) {
|
|
52
58
|
log.warn(
|
|
@@ -67,9 +73,9 @@ export async function copyPatches({
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
try {
|
|
70
|
-
const workspaceRootManifest = await readTypedJson
|
|
76
|
+
const workspaceRootManifest = (await readTypedJson(
|
|
71
77
|
path.join(workspaceRootDir, "package.json"),
|
|
72
|
-
);
|
|
78
|
+
)) as PackageManifest;
|
|
73
79
|
/** PNPM stores patches under pnpm.patchedDependencies, Bun at the top level */
|
|
74
80
|
patchedDependencies =
|
|
75
81
|
workspaceRootManifest?.pnpm?.patchedDependencies ??
|
|
@@ -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,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { PatchFile } from "
|
|
2
|
+
import type { PatchFile } from "#/lib/types";
|
|
3
3
|
import { writeIsolatePnpmWorkspace } from "./write-isolate-pnpm-workspace";
|
|
4
4
|
|
|
5
5
|
vi.mock("fs-extra", () => ({
|
|
@@ -8,14 +8,14 @@ vi.mock("fs-extra", () => ({
|
|
|
8
8
|
},
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
|
-
vi.mock("
|
|
11
|
+
vi.mock("#/lib/utils", () => ({
|
|
12
12
|
readTypedYamlSync: vi.fn(),
|
|
13
13
|
writeTypedYamlSync: vi.fn(),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
const fs = vi.mocked((await import("fs-extra")).default);
|
|
17
17
|
const { readTypedYamlSync, writeTypedYamlSync } = vi.mocked(
|
|
18
|
-
await import("
|
|
18
|
+
await import("#/lib/utils"),
|
|
19
19
|
);
|
|
20
20
|
|
|
21
21
|
const workspaceRootDir = "/workspace";
|
|
@@ -139,6 +139,86 @@ describe("writeIsolatePnpmWorkspace", () => {
|
|
|
139
139
|
);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Regression test for issue #189: pnpm 11 expresses the build-script policy
|
|
144
|
+
* via `allowBuilds` in pnpm-workspace.yaml (and removes the older
|
|
145
|
+
* `pnpm.onlyBuiltDependencies` / `ignoredBuiltDependencies` fields from
|
|
146
|
+
* package.json). The verbatim copy must carry that field — along with other
|
|
147
|
+
* workspace-level settings like `minimumReleaseAge` — into the isolate
|
|
148
|
+
* output so downstream `pnpm install` honors the same policy.
|
|
149
|
+
*/
|
|
150
|
+
it("preserves pnpm 11 workspace settings (allowBuilds, minimumReleaseAge) when no patches are involved", () => {
|
|
151
|
+
readTypedYamlSync.mockReturnValue({
|
|
152
|
+
packages: ["apps/*", "packages/*"],
|
|
153
|
+
allowBuilds: {
|
|
154
|
+
puppeteer: true,
|
|
155
|
+
esbuild: true,
|
|
156
|
+
},
|
|
157
|
+
minimumReleaseAge: 10_080,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
writeIsolatePnpmWorkspace({
|
|
161
|
+
workspaceRootDir,
|
|
162
|
+
isolateDir,
|
|
163
|
+
copiedPatches: {},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* With no patchedDependencies in the source yaml, the file is copied
|
|
168
|
+
* verbatim — preserving `allowBuilds` and any other top-level settings.
|
|
169
|
+
*/
|
|
170
|
+
expect(writeTypedYamlSync).not.toHaveBeenCalled();
|
|
171
|
+
expect(fs.copyFileSync).toHaveBeenCalledWith(
|
|
172
|
+
"/workspace/pnpm-workspace.yaml",
|
|
173
|
+
"/workspace/isolate/pnpm-workspace.yaml",
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* When patches are being filtered, the rewrite path must still carry
|
|
179
|
+
* `allowBuilds` into the output yaml — otherwise pnpm 11's build-script
|
|
180
|
+
* policy is silently dropped.
|
|
181
|
+
*/
|
|
182
|
+
it("preserves allowBuilds when rewriting to filter patchedDependencies", () => {
|
|
183
|
+
readTypedYamlSync.mockReturnValue({
|
|
184
|
+
packages: ["apps/*", "packages/*"],
|
|
185
|
+
allowBuilds: {
|
|
186
|
+
puppeteer: true,
|
|
187
|
+
},
|
|
188
|
+
patchedDependencies: {
|
|
189
|
+
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
|
|
190
|
+
"axios@1.6.0": "patches/axios@1.6.0.patch",
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const copiedPatches: Record<string, PatchFile> = {
|
|
195
|
+
"lodash@4.17.21": {
|
|
196
|
+
path: "patches/lodash@4.17.21.patch",
|
|
197
|
+
hash: "abc",
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
writeIsolatePnpmWorkspace({
|
|
202
|
+
workspaceRootDir,
|
|
203
|
+
isolateDir,
|
|
204
|
+
copiedPatches,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(fs.copyFileSync).not.toHaveBeenCalled();
|
|
208
|
+
expect(writeTypedYamlSync).toHaveBeenCalledWith(
|
|
209
|
+
"/workspace/isolate/pnpm-workspace.yaml",
|
|
210
|
+
{
|
|
211
|
+
packages: ["apps/*", "packages/*"],
|
|
212
|
+
allowBuilds: {
|
|
213
|
+
puppeteer: true,
|
|
214
|
+
},
|
|
215
|
+
patchedDependencies: {
|
|
216
|
+
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
142
222
|
it("copies verbatim when every patch is kept (preserving comments and order)", () => {
|
|
143
223
|
readTypedYamlSync.mockReturnValue({
|
|
144
224
|
packages: ["packages/*"],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { useLogger } from "
|
|
4
|
-
import type { PatchFile, PnpmSettings } from "
|
|
5
|
-
import { readTypedYamlSync, writeTypedYamlSync } from "
|
|
3
|
+
import { useLogger } from "#/lib/logger";
|
|
4
|
+
import type { PatchFile, PnpmSettings } from "#/lib/types";
|
|
5
|
+
import { readTypedYamlSync, writeTypedYamlSync } from "#/lib/utils";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Copy `pnpm-workspace.yaml` from the workspace root to the isolate directory,
|
|
@@ -39,7 +39,7 @@ export function writeIsolatePnpmWorkspace({
|
|
|
39
39
|
let settings: PnpmSettings | undefined;
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
|
-
settings = readTypedYamlSync
|
|
42
|
+
settings = readTypedYamlSync(sourcePath) as PnpmSettings | undefined;
|
|
43
43
|
} catch (error) {
|
|
44
44
|
log.warn(
|
|
45
45
|
`Could not read pnpm-workspace.yaml, falling back to verbatim copy: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { PackageManifest, PackagesRegistry } from "
|
|
2
|
+
import type { PackageManifest, PackagesRegistry } from "#/lib/types";
|
|
3
3
|
import { collectReachablePackageNames } from "./collect-reachable-package-names";
|
|
4
4
|
|
|
5
5
|
function entry(manifest: PackageManifest) {
|
|
@@ -28,38 +28,41 @@ export async function createPackagesRegistry(
|
|
|
28
28
|
workspaceRootDir,
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const manifestPath = path.join(absoluteDir, "package.json");
|
|
31
|
+
const entries = await Promise.all(
|
|
32
|
+
allPackages.map(async (rootRelativeDir) => {
|
|
33
|
+
const absoluteDir = path.join(workspaceRootDir, rootRelativeDir);
|
|
34
|
+
const manifestPath = path.join(absoluteDir, "package.json");
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
log.debug(`Registering package ${rootRelativeDir}`);
|
|
36
|
+
if (!fs.existsSync(manifestPath)) {
|
|
37
|
+
log.warn(
|
|
38
|
+
`Ignoring directory ${rootRelativeDir} because it does not contain a package.json file`,
|
|
39
|
+
);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
path.join(absoluteDir, "package.json"),
|
|
47
|
-
);
|
|
43
|
+
log.debug(`Registering package ${rootRelativeDir}`);
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
const manifest = (await readTypedJson(
|
|
46
|
+
path.join(absoluteDir, "package.json"),
|
|
47
|
+
)) as PackageManifest;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
manifest,
|
|
51
|
+
rootRelativeDir,
|
|
52
|
+
absoluteDir,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const registry: PackagesRegistry = entries.reduce<PackagesRegistry>(
|
|
58
|
+
(acc, info) => {
|
|
59
|
+
if (info) {
|
|
60
|
+
acc[info.manifest.name] = info;
|
|
61
|
+
}
|
|
62
|
+
return acc;
|
|
63
|
+
},
|
|
64
|
+
{},
|
|
65
|
+
);
|
|
63
66
|
|
|
64
67
|
return registry;
|
|
65
68
|
}
|
|
@@ -73,9 +76,9 @@ function listWorkspacePackages(
|
|
|
73
76
|
workspaceRootDir: string,
|
|
74
77
|
) {
|
|
75
78
|
if (isRushWorkspace(workspaceRootDir)) {
|
|
76
|
-
const rushConfig = readTypedJsonSync
|
|
79
|
+
const rushConfig = readTypedJsonSync(
|
|
77
80
|
path.join(workspaceRootDir, "rush.json"),
|
|
78
|
-
);
|
|
81
|
+
) as RushConfig;
|
|
79
82
|
|
|
80
83
|
return rushConfig.projects.map(({ projectFolder }) => projectFolder);
|
|
81
84
|
} else {
|
|
@@ -13,16 +13,16 @@ import {
|
|
|
13
13
|
* monorepo. This configuration is dependent on the package manager used, and I
|
|
14
14
|
* don't know if we're covering all cases yet...
|
|
15
15
|
*/
|
|
16
|
-
export function findPackagesGlobs(workspaceRootDir: string) {
|
|
16
|
+
export function findPackagesGlobs(workspaceRootDir: string): string[] {
|
|
17
17
|
const log = useLogger();
|
|
18
18
|
|
|
19
19
|
const packageManager = usePackageManager();
|
|
20
20
|
|
|
21
21
|
switch (packageManager.name) {
|
|
22
22
|
case "pnpm": {
|
|
23
|
-
const workspaceConfig = readTypedYamlSync
|
|
23
|
+
const workspaceConfig = readTypedYamlSync(
|
|
24
24
|
path.join(workspaceRootDir, "pnpm-workspace.yaml"),
|
|
25
|
-
);
|
|
25
|
+
) as { packages: string[] } | undefined;
|
|
26
26
|
|
|
27
27
|
if (!workspaceConfig) {
|
|
28
28
|
throw new Error(
|
|
@@ -48,9 +48,9 @@ export function findPackagesGlobs(workspaceRootDir: string) {
|
|
|
48
48
|
"package.json",
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
const { workspaces } = readTypedJsonSync
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const { workspaces } = readTypedJsonSync(workspaceRootManifestPath) as {
|
|
52
|
+
workspaces: string[];
|
|
53
|
+
};
|
|
54
54
|
|
|
55
55
|
if (!workspaces) {
|
|
56
56
|
throw new Error(
|
|
@@ -60,21 +60,25 @@ export function findPackagesGlobs(workspaceRootDir: string) {
|
|
|
60
60
|
|
|
61
61
|
if (Array.isArray(workspaces)) {
|
|
62
62
|
return workspaces;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* For Yarn, workspaces could be defined as an object with { packages:
|
|
66
|
-
* [], nohoist: [] }. See
|
|
67
|
-
* https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
|
|
68
|
-
*/
|
|
69
|
-
const workspacesObject = workspaces as { packages?: string[] };
|
|
63
|
+
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
/**
|
|
66
|
+
* For Yarn, workspaces could be defined as an object with { packages: [],
|
|
67
|
+
* nohoist: [] }. See
|
|
68
|
+
* https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
|
|
69
|
+
*/
|
|
70
|
+
const workspacesObject = workspaces as { packages?: string[] };
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
assert(
|
|
73
|
+
Array.isArray(workspacesObject.packages),
|
|
74
|
+
"workspaces.packages must be an array",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return workspacesObject.packages;
|
|
78
78
|
}
|
|
79
|
+
default:
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Unsupported package manager: ${packageManager.name as string}`,
|
|
82
|
+
);
|
|
79
83
|
}
|
|
80
84
|
}
|