isolate-package 1.34.0 → 1.35.1
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-DI3eUTci.mjs → isolate-Bni_7Vjj.mjs} +39 -10
- package/dist/isolate-Bni_7Vjj.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/package.json +1 -1
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.test.ts +81 -0
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.ts +27 -0
- package/src/lib/manifest/adapt-target-package-manifest.ts +22 -13
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +69 -0
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +20 -10
- package/src/lib/patches/copy-patches.test.ts +120 -0
- package/src/lib/patches/copy-patches.ts +15 -3
- package/dist/isolate-DI3eUTci.mjs.map +0 -1
package/dist/isolate-bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { o as loadConfigFromFile, t as isolate, u as filterObjectUndefined } from "./isolate-
|
|
2
|
+
import { o as loadConfigFromFile, t as isolate, u as filterObjectUndefined } from "./isolate-Bni_7Vjj.mjs";
|
|
3
3
|
import { outdent } from "outdent";
|
|
4
4
|
import console from "node:console";
|
|
5
5
|
import meow from "meow";
|
package/package.json
CHANGED
|
@@ -303,6 +303,87 @@ describe("generatePnpmLockfile", () => {
|
|
|
303
303
|
expect(writtenLockfile.packageExtensionsChecksum).toBe("abc123");
|
|
304
304
|
});
|
|
305
305
|
|
|
306
|
+
it("should restore the catalogs snapshot after pruning (#198)", async () => {
|
|
307
|
+
const catalogs = {
|
|
308
|
+
default: {
|
|
309
|
+
lodash: { specifier: "^4.17.21", version: "4.17.21" },
|
|
310
|
+
},
|
|
311
|
+
utils: {
|
|
312
|
+
ramda: { specifier: "^0.30.0", version: "0.30.0" },
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
const lockfile = {
|
|
316
|
+
...createMockLockfile(),
|
|
317
|
+
catalogs,
|
|
318
|
+
};
|
|
319
|
+
readWantedLockfile_v9.mockResolvedValue(lockfile as never);
|
|
320
|
+
getLockfileImporterId_v9.mockReturnValue("apps/my-app");
|
|
321
|
+
|
|
322
|
+
/** Simulate prune dropping the catalogs snapshot */
|
|
323
|
+
pruneLockfile_v9.mockImplementation((lf) => {
|
|
324
|
+
const result = { ...(lf as unknown as Record<string, unknown>) };
|
|
325
|
+
delete result.catalogs;
|
|
326
|
+
return result as never;
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
await generatePnpmLockfile({
|
|
330
|
+
workspaceRootDir: "/workspace",
|
|
331
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
332
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
333
|
+
internalDepPackageNames: ["shared"],
|
|
334
|
+
packagesRegistry: {
|
|
335
|
+
shared: {
|
|
336
|
+
absoluteDir: "/workspace/packages/shared",
|
|
337
|
+
rootRelativeDir: "packages/shared",
|
|
338
|
+
manifest: { name: "shared", version: "1.0.0" },
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
342
|
+
majorVersion: 9,
|
|
343
|
+
includeDevDependencies: false,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const writeCall = writeWantedLockfile_v9.mock.calls[0]!;
|
|
347
|
+
const writtenLockfile = writeCall[1] as {
|
|
348
|
+
catalogs?: Record<string, Record<string, unknown>>;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* The catalogs snapshot is restored verbatim (like overrides), so it stays
|
|
353
|
+
* in sync with the importer specifiers and the verbatim pnpm-workspace.yaml
|
|
354
|
+
* copy.
|
|
355
|
+
*/
|
|
356
|
+
expect(writtenLockfile.catalogs).toEqual(catalogs);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("should not set catalogs when the source lockfile has none", async () => {
|
|
360
|
+
const lockfile = createMockLockfile();
|
|
361
|
+
readWantedLockfile_v9.mockResolvedValue(lockfile as never);
|
|
362
|
+
getLockfileImporterId_v9.mockReturnValue("apps/my-app");
|
|
363
|
+
pruneLockfile_v9.mockImplementation((lf) => lf as never);
|
|
364
|
+
|
|
365
|
+
await generatePnpmLockfile({
|
|
366
|
+
workspaceRootDir: "/workspace",
|
|
367
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
368
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
369
|
+
internalDepPackageNames: ["shared"],
|
|
370
|
+
packagesRegistry: {
|
|
371
|
+
shared: {
|
|
372
|
+
absoluteDir: "/workspace/packages/shared",
|
|
373
|
+
rootRelativeDir: "packages/shared",
|
|
374
|
+
manifest: { name: "shared", version: "1.0.0" },
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
378
|
+
majorVersion: 9,
|
|
379
|
+
includeDevDependencies: false,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const writeCall = writeWantedLockfile_v9.mock.calls[0]!;
|
|
383
|
+
const writtenLockfile = writeCall[1] as { catalogs?: unknown };
|
|
384
|
+
expect(writtenLockfile.catalogs).toBeUndefined();
|
|
385
|
+
});
|
|
386
|
+
|
|
306
387
|
it("should include patchedDependencies in written lockfile", async () => {
|
|
307
388
|
const lockfile = createMockLockfile();
|
|
308
389
|
readWantedLockfile_v9.mockResolvedValue(lockfile as never);
|
|
@@ -18,6 +18,17 @@ import type { PackageManifest, PackagesRegistry, PatchFile } from "#/lib/types";
|
|
|
18
18
|
import { getErrorMessage, isRushWorkspace } from "#/lib/utils";
|
|
19
19
|
import { pnpmMapImporter } from "./pnpm-map-importer";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* A pnpm catalog snapshot as stored in the lockfile: a map of catalog name
|
|
23
|
+
* (e.g. "default") to a map of dependency name to its resolved entry. The
|
|
24
|
+
* pinned `@pnpm/lockfile-file` types predate catalogs, so we model the shape
|
|
25
|
+
* locally for the cast below.
|
|
26
|
+
*/
|
|
27
|
+
type CatalogSnapshots = Record<
|
|
28
|
+
string,
|
|
29
|
+
Record<string, { specifier: string; version: string }>
|
|
30
|
+
>;
|
|
31
|
+
|
|
21
32
|
export async function generatePnpmLockfile({
|
|
22
33
|
workspaceRootDir,
|
|
23
34
|
targetPackageDir,
|
|
@@ -167,6 +178,22 @@ export async function generatePnpmLockfile({
|
|
|
167
178
|
lockfile.packageExtensionsChecksum;
|
|
168
179
|
}
|
|
169
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Pruning drops the catalogs snapshot, but the isolated importers keep
|
|
183
|
+
* their "catalog:" specifiers (for pnpm we don't resolve catalog deps in
|
|
184
|
+
* the manifest, since the output is itself a workspace). Restore it
|
|
185
|
+
* verbatim — like overrides above — so it stays in sync with the importer
|
|
186
|
+
* specifiers and the preserved pnpm-workspace.yaml catalog definitions,
|
|
187
|
+
* which are themselves copied verbatim (see issue #198). pnpm tolerates
|
|
188
|
+
* catalog entries that no retained importer references, so there is no need
|
|
189
|
+
* to narrow the snapshot.
|
|
190
|
+
*/
|
|
191
|
+
const catalogs = (lockfile as { catalogs?: CatalogSnapshots }).catalogs;
|
|
192
|
+
|
|
193
|
+
if (catalogs) {
|
|
194
|
+
(prunedLockfile as { catalogs?: CatalogSnapshots }).catalogs = catalogs;
|
|
195
|
+
}
|
|
196
|
+
|
|
170
197
|
/**
|
|
171
198
|
* Use pre-computed patched dependencies with transformed paths. The paths
|
|
172
199
|
* are already adapted by copyPatches to match the isolated directory
|
|
@@ -41,14 +41,26 @@ export async function adaptTargetPackageManifest({
|
|
|
41
41
|
? manifest
|
|
42
42
|
: omit(manifest, ["devDependencies"]);
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
/**
|
|
45
|
+
* For PNPM (non-forceNpm) the isolated output is itself a pnpm workspace and
|
|
46
|
+
* the `pnpm-workspace.yaml` with its catalog definitions is preserved, so
|
|
47
|
+
* "catalog:" specifiers can be kept verbatim — just like "workspace:*". The
|
|
48
|
+
* lockfile importers also keep their "catalog:" specifiers, so resolving them
|
|
49
|
+
* here would create a mismatch and break `pnpm install --frozen-lockfile`
|
|
50
|
+
* (see issue #198). For other package managers (and forceNpm) the catalog is
|
|
51
|
+
* not available in the output, so we resolve the specifiers to versions.
|
|
52
|
+
*/
|
|
53
|
+
const isPnpmWorkspaceOutput = packageManager.name === "pnpm" && !forceNpm;
|
|
54
|
+
|
|
55
|
+
const preparedManifest = isPnpmWorkspaceOutput
|
|
56
|
+
? inputManifest
|
|
57
|
+
: {
|
|
58
|
+
...inputManifest,
|
|
59
|
+
dependencies: await resolveCatalogDependencies(
|
|
60
|
+
inputManifest.dependencies,
|
|
61
|
+
workspaceRootDir,
|
|
62
|
+
),
|
|
63
|
+
};
|
|
52
64
|
|
|
53
65
|
const adaptedManifest =
|
|
54
66
|
(packageManager.name === "pnpm" || packageManager.name === "bun") &&
|
|
@@ -59,13 +71,10 @@ export async function adaptTargetPackageManifest({
|
|
|
59
71
|
* want to adopt workspace-level fields from the root package.json
|
|
60
72
|
* (pnpm.overrides for PNPM, top-level overrides for Bun).
|
|
61
73
|
*/
|
|
62
|
-
await adoptPnpmFieldsFromRoot(
|
|
63
|
-
manifestWithResolvedCatalogs,
|
|
64
|
-
workspaceRootDir,
|
|
65
|
-
)
|
|
74
|
+
await adoptPnpmFieldsFromRoot(preparedManifest, workspaceRootDir)
|
|
66
75
|
: /** For other package managers we replace the links to internal dependencies */
|
|
67
76
|
adaptManifestInternalDeps({
|
|
68
|
-
manifest:
|
|
77
|
+
manifest: preparedManifest,
|
|
69
78
|
packagesRegistry,
|
|
70
79
|
});
|
|
71
80
|
|
|
@@ -20,6 +20,10 @@ vi.mock("./resolve-catalog-dependencies", () => ({
|
|
|
20
20
|
|
|
21
21
|
const { usePackageManager } = vi.mocked(await import("#/lib/package-manager"));
|
|
22
22
|
|
|
23
|
+
const { resolveCatalogDependencies } = vi.mocked(
|
|
24
|
+
await import("./resolve-catalog-dependencies"),
|
|
25
|
+
);
|
|
26
|
+
|
|
23
27
|
const { writeManifest } = vi.mocked(await import("../io"));
|
|
24
28
|
|
|
25
29
|
const { adaptInternalPackageManifests } =
|
|
@@ -178,4 +182,69 @@ describe("adaptInternalPackageManifests", () => {
|
|
|
178
182
|
|
|
179
183
|
expect(writtenManifest.devDependencies).toBeUndefined();
|
|
180
184
|
});
|
|
185
|
+
|
|
186
|
+
it("should preserve catalog: specifiers for pnpm without resolving them (#198)", async () => {
|
|
187
|
+
const manifest: PackageManifest = {
|
|
188
|
+
name: "@repo/shared",
|
|
189
|
+
version: "1.0.0",
|
|
190
|
+
dependencies: {
|
|
191
|
+
"lodash.merge": "catalog:",
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const packagesRegistry = createRegistry({
|
|
196
|
+
"@repo/shared": {
|
|
197
|
+
rootRelativeDir: "packages/shared",
|
|
198
|
+
manifest,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await adaptInternalPackageManifests({
|
|
203
|
+
internalPackageNames: ["@repo/shared"],
|
|
204
|
+
packagesRegistry,
|
|
205
|
+
isolateDir: "/output",
|
|
206
|
+
forceNpm: false,
|
|
207
|
+
workspaceRootDir: "/workspace",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* For pnpm the isolated output is itself a workspace with its catalog
|
|
212
|
+
* definitions preserved, so the specifier is kept verbatim rather than
|
|
213
|
+
* resolved (which would desync it from the lockfile importers).
|
|
214
|
+
*/
|
|
215
|
+
expect(resolveCatalogDependencies).not.toHaveBeenCalled();
|
|
216
|
+
|
|
217
|
+
const writtenManifest = writeManifest.mock.calls[0]![1];
|
|
218
|
+
expect(writtenManifest.dependencies).toEqual({
|
|
219
|
+
"lodash.merge": "catalog:",
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should resolve catalog dependencies when forceNpm is enabled", async () => {
|
|
224
|
+
const manifest: PackageManifest = {
|
|
225
|
+
name: "@repo/shared",
|
|
226
|
+
version: "1.0.0",
|
|
227
|
+
dependencies: {
|
|
228
|
+
"lodash.merge": "catalog:",
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const packagesRegistry = createRegistry({
|
|
233
|
+
"@repo/shared": {
|
|
234
|
+
rootRelativeDir: "packages/shared",
|
|
235
|
+
manifest,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await adaptInternalPackageManifests({
|
|
240
|
+
internalPackageNames: ["@repo/shared"],
|
|
241
|
+
packagesRegistry,
|
|
242
|
+
isolateDir: "/output",
|
|
243
|
+
forceNpm: true,
|
|
244
|
+
workspaceRootDir: "/workspace",
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
/** With forceNpm the catalog is not available in the output, so resolve. */
|
|
248
|
+
expect(resolveCatalogDependencies).toHaveBeenCalledOnce();
|
|
249
|
+
});
|
|
181
250
|
});
|
|
@@ -27,6 +27,15 @@ export async function adaptInternalPackageManifests({
|
|
|
27
27
|
}) {
|
|
28
28
|
const packageManager = usePackageManager();
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* For PNPM (non-forceNpm) the isolated output is itself a pnpm workspace with
|
|
32
|
+
* its `pnpm-workspace.yaml` catalog definitions preserved, so "catalog:"
|
|
33
|
+
* specifiers are kept verbatim to stay in sync with the lockfile importers
|
|
34
|
+
* (see issue #198). For other package managers the catalog is not available
|
|
35
|
+
* in the output, so we resolve the specifiers to versions.
|
|
36
|
+
*/
|
|
37
|
+
const isPnpmWorkspaceOutput = packageManager.name === "pnpm" && !forceNpm;
|
|
38
|
+
|
|
30
39
|
await Promise.all(
|
|
31
40
|
internalPackageNames.map(async (packageName) => {
|
|
32
41
|
const { manifest, rootRelativeDir } = got(packagesRegistry, packageName);
|
|
@@ -45,14 +54,15 @@ export async function adaptInternalPackageManifests({
|
|
|
45
54
|
strippedManifest.scripts = omit(strippedManifest.scripts, ["prepare"]);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const preparedManifest = isPnpmWorkspaceOutput
|
|
58
|
+
? strippedManifest
|
|
59
|
+
: {
|
|
60
|
+
...strippedManifest,
|
|
61
|
+
dependencies: await resolveCatalogDependencies(
|
|
62
|
+
strippedManifest.dependencies,
|
|
63
|
+
workspaceRootDir,
|
|
64
|
+
),
|
|
65
|
+
};
|
|
56
66
|
|
|
57
67
|
const outputManifest =
|
|
58
68
|
(packageManager.name === "pnpm" || packageManager.name === "bun") &&
|
|
@@ -61,10 +71,10 @@ export async function adaptInternalPackageManifests({
|
|
|
61
71
|
* For PNPM and Bun the output itself is a workspace so we can preserve
|
|
62
72
|
* the specifiers with "workspace:*" in the output manifest.
|
|
63
73
|
*/
|
|
64
|
-
|
|
74
|
+
preparedManifest
|
|
65
75
|
: /** For other package managers we replace the links to internal dependencies */
|
|
66
76
|
adaptManifestInternalDeps({
|
|
67
|
-
manifest:
|
|
77
|
+
manifest: preparedManifest,
|
|
68
78
|
packagesRegistry,
|
|
69
79
|
parentRootRelativeDir: rootRelativeDir,
|
|
70
80
|
});
|
|
@@ -653,4 +653,124 @@ describe("copyPatches", () => {
|
|
|
653
653
|
expect(reachable!.has("@react-pdf/renderer")).toBe(true);
|
|
654
654
|
expect(reachable!.has("@react-pdf/render")).toBe(true);
|
|
655
655
|
});
|
|
656
|
+
|
|
657
|
+
it("should read the patch hash from the pnpm 11 string lockfile format (regression: issue #201)", async () => {
|
|
658
|
+
/**
|
|
659
|
+
* pnpm 11 simplified the lockfile `patchedDependencies` format from
|
|
660
|
+
* `Record<string, { path, hash }>` to `Record<string, string>` (selector to
|
|
661
|
+
* hash). The hash must be read from the bare string, otherwise it ends up
|
|
662
|
+
* empty and pnpm rejects the isolated install with
|
|
663
|
+
* ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.
|
|
664
|
+
*/
|
|
665
|
+
const targetManifest: PackageManifest = {
|
|
666
|
+
name: "test",
|
|
667
|
+
version: "1.0.0",
|
|
668
|
+
dependencies: { lodash: "^4.0.0" },
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
readTypedYamlSync.mockReturnValue({
|
|
672
|
+
patchedDependencies: {
|
|
673
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
readTypedJson.mockResolvedValue({
|
|
677
|
+
name: "root",
|
|
678
|
+
version: "1.0.0",
|
|
679
|
+
} as PackageManifest);
|
|
680
|
+
|
|
681
|
+
filterPatchedDependencies.mockReturnValue({
|
|
682
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
fs.existsSync.mockReturnValue(true);
|
|
686
|
+
|
|
687
|
+
usePackageManager.mockReturnValue({
|
|
688
|
+
name: "pnpm",
|
|
689
|
+
majorVersion: 9,
|
|
690
|
+
version: "9.0.0",
|
|
691
|
+
packageManagerString: "pnpm@9.0.0",
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
readWantedLockfile_v9.mockResolvedValue({
|
|
695
|
+
lockfileVersion: "9.0",
|
|
696
|
+
patchedDependencies: {
|
|
697
|
+
"lodash@4.17.21": "sha256-abc123",
|
|
698
|
+
},
|
|
699
|
+
} as unknown as Awaited<ReturnType<typeof readWantedLockfile_v9>>);
|
|
700
|
+
|
|
701
|
+
const result = await copyPatches({
|
|
702
|
+
workspaceRootDir: "/workspace",
|
|
703
|
+
targetPackageDir: "/workspace/packages/test",
|
|
704
|
+
internalDepPackageNames: [],
|
|
705
|
+
targetPackageManifest: targetManifest,
|
|
706
|
+
isolateDir: "/workspace/isolate",
|
|
707
|
+
packagesRegistry: {},
|
|
708
|
+
includeDevDependencies: false,
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
expect(result).toEqual({
|
|
712
|
+
"lodash@4.17.21": {
|
|
713
|
+
path: "patches/lodash.patch",
|
|
714
|
+
hash: "sha256-abc123",
|
|
715
|
+
},
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("should read the patch hash from the pnpm <=10 object lockfile format (regression: issue #201)", async () => {
|
|
720
|
+
const targetManifest: PackageManifest = {
|
|
721
|
+
name: "test",
|
|
722
|
+
version: "1.0.0",
|
|
723
|
+
dependencies: { lodash: "^4.0.0" },
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
readTypedYamlSync.mockReturnValue({
|
|
727
|
+
patchedDependencies: {
|
|
728
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
readTypedJson.mockResolvedValue({
|
|
732
|
+
name: "root",
|
|
733
|
+
version: "1.0.0",
|
|
734
|
+
} as PackageManifest);
|
|
735
|
+
|
|
736
|
+
filterPatchedDependencies.mockReturnValue({
|
|
737
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
fs.existsSync.mockReturnValue(true);
|
|
741
|
+
|
|
742
|
+
usePackageManager.mockReturnValue({
|
|
743
|
+
name: "pnpm",
|
|
744
|
+
majorVersion: 9,
|
|
745
|
+
version: "9.0.0",
|
|
746
|
+
packageManagerString: "pnpm@9.0.0",
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
readWantedLockfile_v9.mockResolvedValue({
|
|
750
|
+
lockfileVersion: "9.0",
|
|
751
|
+
patchedDependencies: {
|
|
752
|
+
"lodash@4.17.21": {
|
|
753
|
+
path: "patches/lodash.patch",
|
|
754
|
+
hash: "sha256-def456",
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
} as unknown as Awaited<ReturnType<typeof readWantedLockfile_v9>>);
|
|
758
|
+
|
|
759
|
+
const result = await copyPatches({
|
|
760
|
+
workspaceRootDir: "/workspace",
|
|
761
|
+
targetPackageDir: "/workspace/packages/test",
|
|
762
|
+
internalDepPackageNames: [],
|
|
763
|
+
targetPackageManifest: targetManifest,
|
|
764
|
+
isolateDir: "/workspace/isolate",
|
|
765
|
+
packagesRegistry: {},
|
|
766
|
+
includeDevDependencies: false,
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
expect(result).toEqual({
|
|
770
|
+
"lodash@4.17.21": {
|
|
771
|
+
path: "patches/lodash.patch",
|
|
772
|
+
hash: "sha256-def456",
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
});
|
|
656
776
|
});
|
|
@@ -177,9 +177,17 @@ export async function copyPatches({
|
|
|
177
177
|
await fs.copy(sourcePatchPath, targetPatchPath);
|
|
178
178
|
log.debug(`Copied patch for ${packageSpec}: ${patchPath}`);
|
|
179
179
|
|
|
180
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Get the hash from the original lockfile, or use empty string if not
|
|
182
|
+
* found. pnpm 11 simplified the lockfile `patchedDependencies` format from
|
|
183
|
+
* `Record<string, { path, hash }>` to `Record<string, string>` (selector to
|
|
184
|
+
* hash), so the entry may be a bare hash string. See issue #201.
|
|
185
|
+
*/
|
|
181
186
|
const originalPatchFile = lockfilePatchedDependencies?.[packageSpec];
|
|
182
|
-
const hash =
|
|
187
|
+
const hash =
|
|
188
|
+
typeof originalPatchFile === "string"
|
|
189
|
+
? originalPatchFile
|
|
190
|
+
: (originalPatchFile?.hash ?? "");
|
|
183
191
|
|
|
184
192
|
if (packageManagerName === "pnpm" && !hash) {
|
|
185
193
|
log.warn(`No hash found for patch ${packageSpec} in lockfile`);
|
|
@@ -201,10 +209,14 @@ export async function copyPatches({
|
|
|
201
209
|
/**
|
|
202
210
|
* Read the patchedDependencies from the original lockfile to get the hashes.
|
|
203
211
|
* Since the file content is the same after copying, the hash remains valid.
|
|
212
|
+
*
|
|
213
|
+
* The value type is `PatchFile | string` because pnpm 11 simplified the format
|
|
214
|
+
* to store the bare hash string per selector, while pnpm <=10 stored a
|
|
215
|
+
* `{ path, hash }` object. See issue #201.
|
|
204
216
|
*/
|
|
205
217
|
async function readLockfilePatchedDependencies(
|
|
206
218
|
workspaceRootDir: string,
|
|
207
|
-
): Promise<Record<string, PatchFile> | undefined> {
|
|
219
|
+
): Promise<Record<string, PatchFile | string> | undefined> {
|
|
208
220
|
try {
|
|
209
221
|
const { majorVersion } = usePackageManager();
|
|
210
222
|
const useVersion9 = majorVersion >= 9;
|