pnpm-dedup 2.2.1 → 2.2.2
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/README.md +15 -5
- package/dist/applyClusterFixes.js +103 -80
- package/dist/applyClusterFixes.js.map +1 -1
- package/dist/applyClusterFixes.test.js +257 -29
- package/dist/applyClusterFixes.test.js.map +1 -1
- package/dist/bin/pnpm-why-duplicate.test.js +17 -8
- package/dist/bin/pnpm-why-duplicate.test.js.map +1 -1
- package/dist/dedupe.js +15 -4
- package/dist/dedupe.js.map +1 -1
- package/dist/dedupeCheckConsistency.test.js +53 -59
- package/dist/dedupeCheckConsistency.test.js.map +1 -1
- package/dist/dedupeEndToEnd.test.js +3 -3
- package/dist/dedupeEndToEnd.test.js.map +1 -1
- package/dist/dedupePeers.test.js +1 -1
- package/dist/dedupePeers.test.js.map +1 -1
- package/dist/definitions/applyClusterFixes.d.ts +6 -3
- package/dist/definitions/applyClusterFixes.d.ts.map +1 -1
- package/dist/definitions/dedupe.d.ts.map +1 -1
- package/dist/definitions/helpers/describeUnheldOverride.d.ts +10 -0
- package/dist/definitions/helpers/describeUnheldOverride.d.ts.map +1 -0
- package/dist/definitions/helpers/describeUnheldOverride.test.d.ts +2 -0
- package/dist/definitions/helpers/describeUnheldOverride.test.d.ts.map +1 -0
- package/dist/definitions/helpers/pnpmWorkspaceYaml.d.ts +1 -5
- package/dist/definitions/helpers/pnpmWorkspaceYaml.d.ts.map +1 -1
- package/dist/definitions/pnpmLockTypes.d.ts +1 -0
- package/dist/definitions/pnpmLockTypes.d.ts.map +1 -1
- package/dist/definitions/readPnpmLock.d.ts +7 -0
- package/dist/definitions/readPnpmLock.d.ts.map +1 -1
- package/dist/definitions/readPnpmLock.test.d.ts +2 -0
- package/dist/definitions/readPnpmLock.test.d.ts.map +1 -0
- package/dist/displayMany.test.js +3 -4
- package/dist/displayMany.test.js.map +1 -1
- package/dist/helpers/buildIdentifiedFixesMap.test.js +1 -1
- package/dist/helpers/buildIdentifiedFixesMap.test.js.map +1 -1
- package/dist/helpers/buildPnpmPackagesMap.test.js +1 -1
- package/dist/helpers/buildPnpmPackagesMap.test.js.map +1 -1
- package/dist/helpers/collectDependentRanges.test.js +1 -1
- package/dist/helpers/collectDependentRanges.test.js.map +1 -1
- package/dist/helpers/collectPnpmDependents.test.js +1 -1
- package/dist/helpers/collectPnpmDependents.test.js.map +1 -1
- package/dist/helpers/describeUnheldOverride.js +30 -0
- package/dist/helpers/describeUnheldOverride.js.map +1 -0
- package/dist/helpers/describeUnheldOverride.test.js +80 -0
- package/dist/helpers/describeUnheldOverride.test.js.map +1 -0
- package/dist/helpers/parsePnpmLockPackages.test.js +1 -1
- package/dist/helpers/parsePnpmLockPackages.test.js.map +1 -1
- package/dist/helpers/pnpmWorkspaceYaml.js +2 -17
- package/dist/helpers/pnpmWorkspaceYaml.js.map +1 -1
- package/dist/helpers/pnpmWorkspaceYaml.test.js +3 -5
- package/dist/helpers/pnpmWorkspaceYaml.test.js.map +1 -1
- package/dist/helpers/readInstalledManifest.test.js +1 -1
- package/dist/helpers/readInstalledManifest.test.js.map +1 -1
- package/dist/helpers/toLockstepGraph.test.js +1 -1
- package/dist/helpers/toLockstepGraph.test.js.map +1 -1
- package/dist/identifyClusterFixes.test.js +22 -41
- package/dist/identifyClusterFixes.test.js.map +1 -1
- package/dist/identifyResolutionFixes.test.js +8 -7
- package/dist/identifyResolutionFixes.test.js.map +1 -1
- package/dist/readPnpmLock.js +21 -2
- package/dist/readPnpmLock.js.map +1 -1
- package/dist/readPnpmLock.test.js +43 -0
- package/dist/readPnpmLock.test.js.map +1 -0
- package/package.json +4 -3
- package/src/applyClusterFixes.test.ts +398 -36
- package/src/applyClusterFixes.ts +178 -125
- package/src/bin/pnpm-why-duplicate.test.ts +31 -8
- package/src/dedupe.ts +22 -7
- package/src/dedupeCheckConsistency.test.ts +84 -100
- package/src/dedupeEndToEnd.test.ts +84 -77
- package/src/dedupePeers.test.ts +1 -1
- package/src/displayMany.test.ts +4 -4
- package/src/helpers/buildIdentifiedFixesMap.test.ts +1 -1
- package/src/helpers/buildPnpmPackagesMap.test.ts +1 -1
- package/src/helpers/collectDependentRanges.test.ts +1 -1
- package/src/helpers/collectPnpmDependents.test.ts +1 -1
- package/src/helpers/describeUnheldOverride.test.ts +100 -0
- package/src/helpers/describeUnheldOverride.ts +53 -0
- package/src/helpers/parsePnpmLockPackages.test.ts +1 -1
- package/src/helpers/pnpmWorkspaceYaml.test.ts +2 -5
- package/src/helpers/pnpmWorkspaceYaml.ts +5 -20
- package/src/helpers/readInstalledManifest.test.ts +1 -1
- package/src/helpers/toLockstepGraph.test.ts +1 -1
- package/src/identifyClusterFixes.test.ts +27 -58
- package/src/identifyResolutionFixes.test.ts +8 -7
- package/src/pnpmLockTypes.ts +1 -0
- package/src/readPnpmLock.test.ts +65 -0
- package/src/readPnpmLock.ts +26 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describe, it } from "bun:test";
|
|
2
1
|
import { deepStrictEqual, strictEqual } from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
3
|
import {
|
|
4
4
|
addOverrides,
|
|
5
5
|
overrideKey,
|
|
@@ -94,7 +94,7 @@ describe("pnpmWorkspaceYaml", () => {
|
|
|
94
94
|
);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it("
|
|
97
|
+
it("appends to an existing block, leaving the user's comment in place", () => {
|
|
98
98
|
strictEqual(
|
|
99
99
|
addOverrides(
|
|
100
100
|
'overrides:\n # a decision taken elsewhere\n "foo@": "1.0.0"\n',
|
|
@@ -102,14 +102,11 @@ describe("pnpmWorkspaceYaml", () => {
|
|
|
102
102
|
["leaf", "2.0.0"],
|
|
103
103
|
["other", "1.0.0"],
|
|
104
104
|
]),
|
|
105
|
-
{ comment: "Added by pnpm-dedup.\nsee https://example.test/issues" },
|
|
106
105
|
),
|
|
107
106
|
[
|
|
108
107
|
"overrides:",
|
|
109
108
|
" # a decision taken elsewhere",
|
|
110
109
|
' "foo@": "1.0.0"',
|
|
111
|
-
" # Added by pnpm-dedup.",
|
|
112
|
-
" # see https://example.test/issues",
|
|
113
110
|
' "leaf@": "2.0.0"',
|
|
114
111
|
' "other@": "1.0.0"',
|
|
115
112
|
"",
|
|
@@ -12,14 +12,6 @@ export const overrideKey = (
|
|
|
12
12
|
convergence: boolean,
|
|
13
13
|
): string => (convergence ? `${packageName}@` : packageName);
|
|
14
14
|
|
|
15
|
-
// `yaml` writes `commentBefore` straight after the `#`, so the leading space
|
|
16
|
-
// that makes it read as prose has to be part of every line.
|
|
17
|
-
const spaced = (comment: string): string =>
|
|
18
|
-
comment
|
|
19
|
-
.split("\n")
|
|
20
|
-
.map((line) => (line.startsWith(" ") ? line : ` ${line}`))
|
|
21
|
-
.join("\n");
|
|
22
|
-
|
|
23
15
|
const quoted = (value: string): Scalar<string> => {
|
|
24
16
|
const scalar = new Scalar(value);
|
|
25
17
|
scalar.type = Scalar.QUOTE_DOUBLE;
|
|
@@ -29,32 +21,25 @@ const quoted = (value: string): Scalar<string> => {
|
|
|
29
21
|
export interface AddOverridesOptions {
|
|
30
22
|
// false writes plain keys, which pnpm applies to every requester
|
|
31
23
|
convergence?: boolean;
|
|
32
|
-
comment?: string;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
/**
|
|
36
27
|
* Add overrides to a `pnpm-workspace.yaml`, editing the document rather than
|
|
37
28
|
* reserializing it: the user's comments, key order and formatting have to
|
|
38
29
|
* survive. Pass `undefined` for a file that does not exist yet.
|
|
39
|
-
*
|
|
40
|
-
* `comment` is attached above the first entry added, not above the `overrides`
|
|
41
|
-
* key, so an existing block and whatever the user wrote about it stay untouched.
|
|
42
30
|
*/
|
|
43
31
|
export const addOverrides = (
|
|
44
32
|
content: string | undefined,
|
|
45
33
|
overrides: Map<string, string>,
|
|
46
|
-
{ convergence = true
|
|
34
|
+
{ convergence = true }: AddOverridesOptions = {},
|
|
47
35
|
): string => {
|
|
48
36
|
const doc = parseDocument(content ?? "");
|
|
49
|
-
let isFirst = true;
|
|
50
37
|
|
|
51
38
|
for (const [packageName, version] of overrides) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
doc.setIn(["overrides", key], quoted(version));
|
|
57
|
-
isFirst = false;
|
|
39
|
+
doc.setIn(
|
|
40
|
+
["overrides", quoted(overrideKey(packageName, convergence))],
|
|
41
|
+
quoted(version),
|
|
42
|
+
);
|
|
58
43
|
}
|
|
59
44
|
|
|
60
45
|
return doc.toString();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { describe, it } from "bun:test";
|
|
2
1
|
import { deepStrictEqual, strictEqual } from "node:assert/strict";
|
|
3
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
5
4
|
import { join } from "node:path";
|
|
5
|
+
import { describe, it } from "node:test";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { createManifestReaderWithStats } from "./readInstalledManifest.ts";
|
|
8
8
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describe, it } from "bun:test";
|
|
2
1
|
import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import type { PnpmLockFile } from "../pnpmLockTypes.ts";
|
|
5
5
|
import { readPnpmLock } from "../readPnpmLock.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, it } from "bun:test";
|
|
2
1
|
import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
|
|
3
2
|
import { join } from "node:path";
|
|
3
|
+
import { describe, it } from "node:test";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import type { ClusterFix } from "pm-dedup-core";
|
|
6
6
|
import { buildPnpmPackagesMap } from "./helpers/buildPnpmPackagesMap.ts";
|
|
@@ -304,17 +304,29 @@ describe("identifyClusterFixes", () => {
|
|
|
304
304
|
strictEqual(requesterNames.has("metro-config"), false);
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
-
it("
|
|
307
|
+
it("proposes the metro pin upgrade, since nothing moves react-native", () => {
|
|
308
308
|
const fix = metroCluster();
|
|
309
309
|
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
// 0.
|
|
310
|
+
// react-native@0.87.0 and @react-native/metro-config@0.87.0 hold the 0.87.0
|
|
311
|
+
// metro subtree. Nothing pins them exactly — they arrive through `*` /
|
|
312
|
+
// `0.83 - 0.86` peer ranges — but a resolver keeps a locked version its
|
|
313
|
+
// requesters accept, so they stay, and their `^0.87.0` on metro keeps the
|
|
314
|
+
// family off 0.84.5. The root's pin is the only thing left to move: a last
|
|
315
|
+
// resort, proposed because nothing else deduplicates the family.
|
|
313
316
|
strictEqual(fix.applicable, true);
|
|
314
|
-
strictEqual(fix.target, "0.
|
|
315
|
-
strictEqual(fix.direction, "
|
|
317
|
+
strictEqual(fix.target, "0.87.0");
|
|
318
|
+
strictEqual(fix.direction, "up");
|
|
316
319
|
strictEqual(fix.convergentMembers.length, 15);
|
|
317
|
-
deepStrictEqual(
|
|
320
|
+
deepStrictEqual(
|
|
321
|
+
fix.workspaceChanges.map(
|
|
322
|
+
(change) => `${change.packageName} ${change.range} -> ${change.to}`,
|
|
323
|
+
),
|
|
324
|
+
["metro 0.84.5 -> 0.87.0"],
|
|
325
|
+
);
|
|
326
|
+
strictEqual(fix.needsRoundTrip, true);
|
|
327
|
+
// react-native is only asked for through open ranges: it is never planned
|
|
328
|
+
// onto a version number that only metro carries
|
|
329
|
+
deepStrictEqual(fix.reResolutionSet, []);
|
|
318
330
|
|
|
319
331
|
// metro is the only member a real range applies to (the pin): the 14
|
|
320
332
|
// metro-* packages are requested through `*` or through metro's own exact
|
|
@@ -322,60 +334,17 @@ describe("identifyClusterFixes", () => {
|
|
|
322
334
|
deepStrictEqual(fix.driverMembers, ["metro"]);
|
|
323
335
|
});
|
|
324
336
|
|
|
325
|
-
it("leaves
|
|
337
|
+
it("leaves the wildcards alone once the pin they ignored moves to them", () => {
|
|
326
338
|
const fix = metroCluster();
|
|
327
339
|
|
|
328
|
-
//
|
|
329
|
-
// the 0.87.0 metro subtree, and nothing pins either of them there — both
|
|
330
|
-
// arrive through `*` / `0.83 - 0.86` peer ranges (0.87.0 does not even
|
|
331
|
-
// satisfy the latter). They have to move, but which version they land on is
|
|
332
|
-
// the resolver's call, so no version is asserted for them.
|
|
333
|
-
deepStrictEqual(fix.floatingMembers, [
|
|
334
|
-
"@react-native/metro-config",
|
|
335
|
-
"react-native",
|
|
336
|
-
]);
|
|
337
|
-
deepStrictEqual(fix.reResolutionSet, []);
|
|
338
|
-
strictEqual(fix.needsRoundTrip, true);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it("repoints the wildcards that ignored the pinned version", () => {
|
|
342
|
-
const fix = metroCluster();
|
|
343
|
-
|
|
344
|
-
// What the fixture is actually about: `@tamagui/metro-plugin` declares
|
|
340
|
+
// What the fixture is about: `@tamagui/metro-plugin` declares
|
|
345
341
|
// `metro-config: "*"` / `metro-transform-worker: "*"`, and pnpm resolved
|
|
346
|
-
// both to 0.87.0
|
|
347
|
-
//
|
|
348
|
-
// app's.
|
|
342
|
+
// both to 0.87.0 beside the pinned 0.84.5 — the plugin runs against a
|
|
343
|
+
// different metro than the app's. The fix moves the pin to 0.87.0, which
|
|
344
|
+
// puts the app on the plugin's copy: sending the plugin back to 0.84.5
|
|
345
|
+
// would split the family again.
|
|
349
346
|
strictEqual(fix.anchor, "0.84.5");
|
|
350
|
-
deepStrictEqual(
|
|
351
|
-
fix.reuseFixes.map(
|
|
352
|
-
(reuse) =>
|
|
353
|
-
`${reuse.requesterName}>${reuse.packageName} "${reuse.range}" ${reuse.from} -> ${reuse.to}`,
|
|
354
|
-
),
|
|
355
|
-
[
|
|
356
|
-
'@tamagui/metro-plugin>metro-config "*" 0.87.0 -> 0.84.5',
|
|
357
|
-
'@tamagui/metro-plugin>metro-transform-worker "*" 0.87.0 -> 0.84.5',
|
|
358
|
-
],
|
|
359
|
-
);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("leaves alone the ranges that cannot accept the pinned version", () => {
|
|
363
|
-
const repointed = new Set(
|
|
364
|
-
metroCluster().reuseFixes.map((reuse) => reuse.packageName),
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
// `@react-native/metro-config` requires `metro-config: ^0.87.0`, and
|
|
368
|
-
// `react-native-web` requires `@react-native/normalize-colors: ^0.74.1`:
|
|
369
|
-
// neither accepts 0.84.5, so neither is repointed.
|
|
370
|
-
strictEqual(repointed.has("@react-native/normalize-colors"), false);
|
|
371
|
-
deepStrictEqual(
|
|
372
|
-
metroCluster()
|
|
373
|
-
.reuseFixes.filter(
|
|
374
|
-
(reuse) => reuse.requesterName === "@react-native/metro-config",
|
|
375
|
-
)
|
|
376
|
-
.map((reuse) => reuse.packageName),
|
|
377
|
-
[],
|
|
378
|
-
);
|
|
347
|
+
deepStrictEqual(fix.reuseFixes, []);
|
|
379
348
|
});
|
|
380
349
|
|
|
381
350
|
// `aliased-swapped-names`: the importer pins the real `typescript` at 7.0.2
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deepStrictEqual } from "node:assert/strict";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { describe, it } from "node:test";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { identifyResolutionFixes } from "pm-dedup-core";
|
|
5
6
|
import type { PackageResolution } from "./helpers/buildPnpmPackagesMap.ts";
|
|
@@ -11,7 +12,7 @@ const loadResolutionsFixture = (fileName: string): PackageResolution[] => {
|
|
|
11
12
|
fileURLToPath(
|
|
12
13
|
new URL(`../test/fixtures/resolutions/${fileName}`, import.meta.url),
|
|
13
14
|
),
|
|
14
|
-
//
|
|
15
|
+
// oxlint-disable-next-line unicorn-js/prefer-json-parse-buffer
|
|
15
16
|
"utf8",
|
|
16
17
|
),
|
|
17
18
|
);
|
|
@@ -25,7 +26,7 @@ const loadDependentsFixture = (
|
|
|
25
26
|
fileURLToPath(
|
|
26
27
|
new URL(`../test/fixtures/dependents/${fileName}`, import.meta.url),
|
|
27
28
|
),
|
|
28
|
-
//
|
|
29
|
+
// oxlint-disable-next-line unicorn-js/prefer-json-parse-buffer
|
|
29
30
|
"utf8",
|
|
30
31
|
),
|
|
31
32
|
);
|
|
@@ -39,14 +40,14 @@ describe("identifyResolutionFixes", () => {
|
|
|
39
40
|
const resolutions: PackageResolution[] = [];
|
|
40
41
|
|
|
41
42
|
const fixes = identifyResolutionFixes(resolutions, objetToMap({}));
|
|
42
|
-
|
|
43
|
+
deepStrictEqual(fixes, []);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
it("should return an empty array when there is only one resolution", () => {
|
|
46
47
|
const resolutions = loadResolutionsFixture("semver-7.7.3.json");
|
|
47
48
|
const dependents = loadDependentsFixture("semver-7.7.3.json");
|
|
48
49
|
const fixes = identifyResolutionFixes(resolutions, objetToMap(dependents));
|
|
49
|
-
|
|
50
|
+
deepStrictEqual(fixes, []);
|
|
50
51
|
});
|
|
51
52
|
|
|
52
53
|
it("should not identify fixes when dependencies are not compatible", () => {
|
|
@@ -57,7 +58,7 @@ describe("identifyResolutionFixes", () => {
|
|
|
57
58
|
"babel-code-frame-7.26.2-7.27.1.json",
|
|
58
59
|
);
|
|
59
60
|
const fixes = identifyResolutionFixes(resolutions, objetToMap(dependents));
|
|
60
|
-
|
|
61
|
+
deepStrictEqual(fixes, []);
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
it("should identify resolution fixes when dependencies are compatible", () => {
|
|
@@ -69,7 +70,7 @@ describe("identifyResolutionFixes", () => {
|
|
|
69
70
|
);
|
|
70
71
|
|
|
71
72
|
const fixes = identifyResolutionFixes(resolutions, objetToMap(dependents));
|
|
72
|
-
|
|
73
|
+
deepStrictEqual(fixes, [
|
|
73
74
|
{
|
|
74
75
|
mergeableResolutions: [
|
|
75
76
|
"printable-shell-command@5.0.7",
|
package/src/pnpmLockTypes.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface ProjectSnapshot {
|
|
|
13
13
|
dependencies?: Record<string, PnpmImporterDependency>;
|
|
14
14
|
devDependencies?: Record<string, PnpmImporterDependency>;
|
|
15
15
|
optionalDependencies?: Record<string, PnpmImporterDependency>;
|
|
16
|
+
packageManagerDependencies?: Record<string, PnpmImporterDependency>;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export interface PackageMeta {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { deepStrictEqual, ok, throws } from "node:assert/strict";
|
|
2
|
+
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, it } from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createTempProjects } from "./helpers/tempProjects.ts";
|
|
7
|
+
import { readPnpmLock } from "./readPnpmLock.ts";
|
|
8
|
+
|
|
9
|
+
const fixtureLock = (scenario: string): string =>
|
|
10
|
+
fileURLToPath(
|
|
11
|
+
new URL(`../test/fixtures/${scenario}/pnpm-lock.yaml`, import.meta.url),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const projects = createTempProjects("pnpm-dedup-read-lock-");
|
|
15
|
+
|
|
16
|
+
afterEach(projects.cleanup);
|
|
17
|
+
|
|
18
|
+
const writeLock = (content: string): string => {
|
|
19
|
+
const path = join(projects.create(), "pnpm-lock.yaml");
|
|
20
|
+
writeFileSync(path, content);
|
|
21
|
+
return path;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("readPnpmLock", () => {
|
|
25
|
+
it("returns the project document of a lockfile that also resolves the package manager", () => {
|
|
26
|
+
const lock = readPnpmLock(fixtureLock("package-manager-document"));
|
|
27
|
+
|
|
28
|
+
deepStrictEqual(Object.keys(lock.importers?.["."] ?? {}), ["dependencies"]);
|
|
29
|
+
const packageIds = Object.keys(lock.packages ?? {});
|
|
30
|
+
ok(packageIds.includes("printable-shell-command@5.0.7"));
|
|
31
|
+
ok(!packageIds.some((id) => id.startsWith("@pnpm/exe")));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("reads the same project as the single-document lockfile", () => {
|
|
35
|
+
deepStrictEqual(
|
|
36
|
+
readPnpmLock(fixtureLock("package-manager-document")),
|
|
37
|
+
readPnpmLock(fixtureLock("duplicated-printable-shell-command")),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("throws on malformed yaml", () => {
|
|
42
|
+
throws(
|
|
43
|
+
() => readPnpmLock(writeLock("lockfileVersion: '9.0'\nimporters: [\n")),
|
|
44
|
+
{ name: "YAMLParseError" },
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("throws when only the package-manager document is present", () => {
|
|
49
|
+
const path = writeLock(
|
|
50
|
+
"lockfileVersion: '9.0'\n\nimporters:\n\n .:\n packageManagerDependencies:\n pnpm:\n specifier: ^12.0.0\n version: 12.10.1\n",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
throws(() => readPnpmLock(path), {
|
|
54
|
+
message: `No project lockfile document found in ${path}`,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("throws on an empty file", () => {
|
|
59
|
+
const path = writeLock("");
|
|
60
|
+
|
|
61
|
+
throws(() => readPnpmLock(path), {
|
|
62
|
+
message: `No project lockfile document found in ${path}`,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/readPnpmLock.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { parseAllDocuments } from "yaml";
|
|
3
3
|
import type { PnpmLockFile } from "./pnpmLockTypes.ts";
|
|
4
4
|
|
|
5
|
+
const isPackageManagerDocument = (lock: PnpmLockFile): boolean =>
|
|
6
|
+
Object.values(lock.importers ?? {}).some(
|
|
7
|
+
(importer) => importer.packageManagerDependencies !== undefined,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A project declaring `devEngines.packageManager` gets a lockfile of two YAML
|
|
12
|
+
* documents from pnpm 12: the first resolves pnpm itself
|
|
13
|
+
* (`packageManagerDependencies`, `@pnpm/exe.*`), the second is the project's.
|
|
14
|
+
* Only the project document is returned — the other one would report pnpm as a
|
|
15
|
+
* dependency of the project.
|
|
16
|
+
*/
|
|
5
17
|
export const readPnpmLock = (filepath: string): PnpmLockFile => {
|
|
6
|
-
|
|
18
|
+
const documents = parseAllDocuments(readFileSync(filepath, "utf8"));
|
|
19
|
+
const locks = documents.map((document) => {
|
|
20
|
+
const [error] = document.errors;
|
|
21
|
+
if (error) throw error;
|
|
22
|
+
return document.toJS() as PnpmLockFile | null;
|
|
23
|
+
});
|
|
24
|
+
const projectLock = locks.findLast(
|
|
25
|
+
(lock) => lock !== null && !isPackageManagerDocument(lock),
|
|
26
|
+
);
|
|
27
|
+
if (!projectLock) {
|
|
28
|
+
throw new Error(`No project lockfile document found in ${filepath}`);
|
|
29
|
+
}
|
|
30
|
+
return projectLock;
|
|
7
31
|
};
|