pnpm-dedup 2.1.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/applyClusterFixes.js +252 -0
- package/dist/applyClusterFixes.js.map +1 -0
- package/dist/applyClusterFixes.test.js +474 -0
- package/dist/applyClusterFixes.test.js.map +1 -0
- package/dist/bin/pnpm-dedupe.js +35 -0
- package/dist/bin/pnpm-dedupe.js.map +1 -0
- package/dist/bin/pnpm-why-duplicate.js +23 -0
- package/dist/bin/pnpm-why-duplicate.js.map +1 -0
- package/dist/bin/pnpm-why-duplicate.test.js +57 -0
- package/dist/bin/pnpm-why-duplicate.test.js.map +1 -0
- package/dist/dedupe.js +86 -0
- package/dist/dedupe.js.map +1 -0
- package/dist/dedupeCheckConsistency.test.js +149 -0
- package/dist/dedupeCheckConsistency.test.js.map +1 -0
- package/dist/dedupeEndToEnd.test.js +121 -0
- package/dist/dedupeEndToEnd.test.js.map +1 -0
- package/dist/dedupePeers.test.js +105 -0
- package/dist/dedupePeers.test.js.map +1 -0
- package/dist/definitions/applyClusterFixes.d.ts +24 -0
- package/dist/definitions/applyClusterFixes.d.ts.map +1 -0
- package/dist/definitions/applyClusterFixes.test.d.ts +2 -0
- package/dist/definitions/applyClusterFixes.test.d.ts.map +1 -0
- package/dist/definitions/bin/pnpm-dedupe.d.ts +3 -0
- package/dist/definitions/bin/pnpm-dedupe.d.ts.map +1 -0
- package/dist/definitions/bin/pnpm-why-duplicate.d.ts +3 -0
- package/dist/definitions/bin/pnpm-why-duplicate.d.ts.map +1 -0
- package/dist/definitions/bin/pnpm-why-duplicate.test.d.ts +2 -0
- package/dist/definitions/bin/pnpm-why-duplicate.test.d.ts.map +1 -0
- package/dist/definitions/dedupe.d.ts +15 -0
- package/dist/definitions/dedupe.d.ts.map +1 -0
- package/dist/definitions/dedupeCheckConsistency.test.d.ts +2 -0
- package/dist/definitions/dedupeCheckConsistency.test.d.ts.map +1 -0
- package/dist/definitions/dedupeEndToEnd.test.d.ts +2 -0
- package/dist/definitions/dedupeEndToEnd.test.d.ts.map +1 -0
- package/dist/definitions/dedupePeers.test.d.ts +2 -0
- package/dist/definitions/dedupePeers.test.d.ts.map +1 -0
- package/dist/definitions/displayMany.d.ts +16 -0
- package/dist/definitions/displayMany.d.ts.map +1 -0
- package/dist/definitions/displayMany.test.d.ts +2 -0
- package/dist/definitions/displayMany.test.d.ts.map +1 -0
- package/dist/definitions/helpers/buildIdentifiedFixesMap.test.d.ts +2 -0
- package/dist/definitions/helpers/buildIdentifiedFixesMap.test.d.ts.map +1 -0
- package/dist/definitions/helpers/buildPnpmPackagesMap.d.ts +13 -0
- package/dist/definitions/helpers/buildPnpmPackagesMap.d.ts.map +1 -0
- package/dist/definitions/helpers/buildPnpmPackagesMap.test.d.ts +2 -0
- package/dist/definitions/helpers/buildPnpmPackagesMap.test.d.ts.map +1 -0
- package/dist/definitions/helpers/collectDependentRanges.d.ts +22 -0
- package/dist/definitions/helpers/collectDependentRanges.d.ts.map +1 -0
- package/dist/definitions/helpers/collectDependentRanges.test.d.ts +2 -0
- package/dist/definitions/helpers/collectDependentRanges.test.d.ts.map +1 -0
- package/dist/definitions/helpers/collectPnpmDependents.d.ts +8 -0
- package/dist/definitions/helpers/collectPnpmDependents.d.ts.map +1 -0
- package/dist/definitions/helpers/collectPnpmDependents.test.d.ts +2 -0
- package/dist/definitions/helpers/collectPnpmDependents.test.d.ts.map +1 -0
- package/dist/definitions/helpers/duplicateSnapshot.d.ts +3 -0
- package/dist/definitions/helpers/duplicateSnapshot.d.ts.map +1 -0
- package/dist/definitions/helpers/parsePnpmLockPackages.d.ts +44 -0
- package/dist/definitions/helpers/parsePnpmLockPackages.d.ts.map +1 -0
- package/dist/definitions/helpers/parsePnpmLockPackages.test.d.ts +2 -0
- package/dist/definitions/helpers/parsePnpmLockPackages.test.d.ts.map +1 -0
- package/dist/definitions/helpers/pnpmProtocol.d.ts +2 -0
- package/dist/definitions/helpers/pnpmProtocol.d.ts.map +1 -0
- package/dist/definitions/helpers/pnpmVersion.d.ts +11 -0
- package/dist/definitions/helpers/pnpmVersion.d.ts.map +1 -0
- package/dist/definitions/helpers/pnpmWorkspaceYaml.d.ts +23 -0
- package/dist/definitions/helpers/pnpmWorkspaceYaml.d.ts.map +1 -0
- package/dist/definitions/helpers/pnpmWorkspaceYaml.test.d.ts +2 -0
- package/dist/definitions/helpers/pnpmWorkspaceYaml.test.d.ts.map +1 -0
- package/dist/definitions/helpers/projectDir.d.ts +3 -0
- package/dist/definitions/helpers/projectDir.d.ts.map +1 -0
- package/dist/definitions/helpers/readInstalledManifest.d.ts +32 -0
- package/dist/definitions/helpers/readInstalledManifest.d.ts.map +1 -0
- package/dist/definitions/helpers/readInstalledManifest.test.d.ts +2 -0
- package/dist/definitions/helpers/readInstalledManifest.test.d.ts.map +1 -0
- package/dist/definitions/helpers/runPnpm.d.ts +11 -0
- package/dist/definitions/helpers/runPnpm.d.ts.map +1 -0
- package/dist/definitions/helpers/tempProjects.d.ts +8 -0
- package/dist/definitions/helpers/tempProjects.d.ts.map +1 -0
- package/dist/definitions/helpers/toLockstepGraph.d.ts +12 -0
- package/dist/definitions/helpers/toLockstepGraph.d.ts.map +1 -0
- package/dist/definitions/helpers/toLockstepGraph.test.d.ts +2 -0
- package/dist/definitions/helpers/toLockstepGraph.test.d.ts.map +1 -0
- package/dist/definitions/helpers/versionsSnapshot.d.ts +3 -0
- package/dist/definitions/helpers/versionsSnapshot.d.ts.map +1 -0
- package/dist/definitions/identifyClusterFixes.d.ts +7 -0
- package/dist/definitions/identifyClusterFixes.d.ts.map +1 -0
- package/dist/definitions/identifyClusterFixes.test.d.ts +2 -0
- package/dist/definitions/identifyClusterFixes.test.d.ts.map +1 -0
- package/dist/definitions/identifyResolutionFixes.test.d.ts +2 -0
- package/dist/definitions/identifyResolutionFixes.test.d.ts.map +1 -0
- package/dist/definitions/index.d.ts +25 -0
- package/dist/definitions/index.d.ts.map +1 -0
- package/dist/definitions/pnpmLockTypes.d.ts +42 -0
- package/dist/definitions/pnpmLockTypes.d.ts.map +1 -0
- package/dist/definitions/readPnpmLock.d.ts +3 -0
- package/dist/definitions/readPnpmLock.d.ts.map +1 -0
- package/dist/displayMany.js +56 -0
- package/dist/displayMany.js.map +1 -0
- package/dist/displayMany.test.js +146 -0
- package/dist/displayMany.test.js.map +1 -0
- package/dist/helpers/buildIdentifiedFixesMap.test.js +27 -0
- package/dist/helpers/buildIdentifiedFixesMap.test.js.map +1 -0
- package/dist/helpers/buildPnpmPackagesMap.js +23 -0
- package/dist/helpers/buildPnpmPackagesMap.js.map +1 -0
- package/dist/helpers/buildPnpmPackagesMap.test.js +68 -0
- package/dist/helpers/buildPnpmPackagesMap.test.js.map +1 -0
- package/dist/helpers/collectDependentRanges.js +118 -0
- package/dist/helpers/collectDependentRanges.js.map +1 -0
- package/dist/helpers/collectDependentRanges.test.js +211 -0
- package/dist/helpers/collectDependentRanges.test.js.map +1 -0
- package/dist/helpers/collectPnpmDependents.js +55 -0
- package/dist/helpers/collectPnpmDependents.js.map +1 -0
- package/dist/helpers/collectPnpmDependents.test.js +78 -0
- package/dist/helpers/collectPnpmDependents.test.js.map +1 -0
- package/dist/helpers/duplicateSnapshot.js +5 -0
- package/dist/helpers/duplicateSnapshot.js.map +1 -0
- package/dist/helpers/parsePnpmLockPackages.js +62 -0
- package/dist/helpers/parsePnpmLockPackages.js.map +1 -0
- package/dist/helpers/parsePnpmLockPackages.test.js +62 -0
- package/dist/helpers/parsePnpmLockPackages.test.js.map +1 -0
- package/dist/helpers/pnpmProtocol.js +2 -0
- package/dist/helpers/pnpmProtocol.js.map +1 -0
- package/dist/helpers/pnpmVersion.js +23 -0
- package/dist/helpers/pnpmVersion.js.map +1 -0
- package/dist/helpers/pnpmWorkspaceYaml.js +48 -0
- package/dist/helpers/pnpmWorkspaceYaml.js.map +1 -0
- package/dist/helpers/pnpmWorkspaceYaml.test.js +91 -0
- package/dist/helpers/pnpmWorkspaceYaml.test.js.map +1 -0
- package/dist/helpers/projectDir.js +10 -0
- package/dist/helpers/projectDir.js.map +1 -0
- package/dist/helpers/readInstalledManifest.js +139 -0
- package/dist/helpers/readInstalledManifest.js.map +1 -0
- package/dist/helpers/readInstalledManifest.test.js +83 -0
- package/dist/helpers/readInstalledManifest.test.js.map +1 -0
- package/dist/helpers/runPnpm.js +16 -0
- package/dist/helpers/runPnpm.js.map +1 -0
- package/dist/helpers/tempProjects.js +85 -0
- package/dist/helpers/tempProjects.js.map +1 -0
- package/dist/helpers/toLockstepGraph.js +34 -0
- package/dist/helpers/toLockstepGraph.js.map +1 -0
- package/dist/helpers/toLockstepGraph.test.js +81 -0
- package/dist/helpers/toLockstepGraph.test.js.map +1 -0
- package/dist/helpers/versionsSnapshot.js +5 -0
- package/dist/helpers/versionsSnapshot.js.map +1 -0
- package/dist/identifyClusterFixes.js +53 -0
- package/dist/identifyClusterFixes.js.map +1 -0
- package/dist/identifyClusterFixes.test.js +307 -0
- package/dist/identifyClusterFixes.test.js.map +1 -0
- package/dist/identifyResolutionFixes.test.js +49 -0
- package/dist/identifyResolutionFixes.test.js.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/pnpmLockTypes.js +7 -0
- package/dist/pnpmLockTypes.js.map +1 -0
- package/dist/readPnpmLock.js +6 -0
- package/dist/readPnpmLock.js.map +1 -0
- package/package.json +71 -0
- package/src/applyClusterFixes.test.ts +560 -0
- package/src/applyClusterFixes.ts +466 -0
- package/src/bin/pnpm-dedupe.ts +44 -0
- package/src/bin/pnpm-why-duplicate.test.ts +79 -0
- package/src/bin/pnpm-why-duplicate.ts +34 -0
- package/src/dedupe.ts +124 -0
- package/src/dedupeCheckConsistency.test.ts +218 -0
- package/src/dedupeEndToEnd.test.ts +168 -0
- package/src/dedupePeers.test.ts +175 -0
- package/src/displayMany.test.ts +208 -0
- package/src/displayMany.ts +104 -0
- package/src/helpers/buildIdentifiedFixesMap.test.ts +33 -0
- package/src/helpers/buildPnpmPackagesMap.test.ts +119 -0
- package/src/helpers/buildPnpmPackagesMap.ts +53 -0
- package/src/helpers/collectDependentRanges.test.ts +311 -0
- package/src/helpers/collectDependentRanges.ts +185 -0
- package/src/helpers/collectPnpmDependents.test.ts +112 -0
- package/src/helpers/collectPnpmDependents.ts +79 -0
- package/src/helpers/duplicateSnapshot.ts +18 -0
- package/src/helpers/parsePnpmLockPackages.test.ts +93 -0
- package/src/helpers/parsePnpmLockPackages.ts +99 -0
- package/src/helpers/pnpmProtocol.ts +12 -0
- package/src/helpers/pnpmVersion.ts +28 -0
- package/src/helpers/pnpmWorkspaceYaml.test.ts +140 -0
- package/src/helpers/pnpmWorkspaceYaml.ts +77 -0
- package/src/helpers/projectDir.ts +14 -0
- package/src/helpers/readInstalledManifest.test.ts +124 -0
- package/src/helpers/readInstalledManifest.ts +202 -0
- package/src/helpers/runPnpm.ts +34 -0
- package/src/helpers/tempProjects.ts +94 -0
- package/src/helpers/toLockstepGraph.test.ts +98 -0
- package/src/helpers/toLockstepGraph.ts +52 -0
- package/src/helpers/versionsSnapshot.ts +9 -0
- package/src/identifyClusterFixes.test.ts +430 -0
- package/src/identifyClusterFixes.ts +114 -0
- package/src/identifyResolutionFixes.test.ts +82 -0
- package/src/index.ts +159 -0
- package/src/pnpmLockTypes.ts +44 -0
- package/src/readPnpmLock.ts +7 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { ok } from "node:assert/strict";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { displayMany } from "./displayMany.js";
|
|
5
|
+
import { buildPnpmPackagesMap, filterDuplicatesPnpmPackagesMap, } from "./helpers/buildPnpmPackagesMap.js";
|
|
6
|
+
import { collectDependentRanges } from "./helpers/collectDependentRanges.js";
|
|
7
|
+
import { parsePnpmLockPackages } from "./helpers/parsePnpmLockPackages.js";
|
|
8
|
+
import { createManifestReader } from "./helpers/readInstalledManifest.js";
|
|
9
|
+
import { identifyClusterFixes } from "./identifyClusterFixes.js";
|
|
10
|
+
import { readPnpmLock } from "./readPnpmLock.js";
|
|
11
|
+
const fixturesBase = (rel) => fileURLToPath(new URL(rel, import.meta.url));
|
|
12
|
+
// The report layout is covered in pm-dedup-core (`renderDuplicatesReport`); these
|
|
13
|
+
// only check that the pnpm lockfile model is mapped onto it correctly.
|
|
14
|
+
const renderDuplicates = (scenario, clusterFixes, readManifest = () => undefined) => {
|
|
15
|
+
const lock = readPnpmLock(fixturesBase(`../test/fixtures/${scenario}/pnpm-lock.yaml`));
|
|
16
|
+
const packagesMap = buildPnpmPackagesMap(parsePnpmLockPackages(lock));
|
|
17
|
+
const duplicates = filterDuplicatesPnpmPackagesMap(packagesMap);
|
|
18
|
+
let buffer = "";
|
|
19
|
+
displayMany({
|
|
20
|
+
title: "duplicates",
|
|
21
|
+
duplicatesPackagesMap: duplicates,
|
|
22
|
+
dependents: collectDependentRanges(lock, new Set(Object.keys(duplicates)), readManifest),
|
|
23
|
+
totalDependencies: Object.keys(packagesMap).length,
|
|
24
|
+
clusterFixes,
|
|
25
|
+
details: true,
|
|
26
|
+
color: false,
|
|
27
|
+
log: (message = "") => {
|
|
28
|
+
buffer += `${message}\n`;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
return buffer;
|
|
32
|
+
};
|
|
33
|
+
// `exact-pin-forces-downgrade` commits its virtual store, so the report runs on
|
|
34
|
+
// the real declared ranges: an exact 3.0.3 against expo-camera's `^3.0.0`.
|
|
35
|
+
const downgradeScenario = "exact-pin-forces-downgrade";
|
|
36
|
+
const downgradeDir = fixturesBase(`../test/fixtures/${downgradeScenario}`);
|
|
37
|
+
const renderDowngrade = () => {
|
|
38
|
+
const lock = readPnpmLock(`${downgradeDir}/pnpm-lock.yaml`);
|
|
39
|
+
const readManifest = createManifestReader(downgradeDir);
|
|
40
|
+
return renderDuplicates(downgradeScenario, identifyClusterFixes(lock, buildPnpmPackagesMap(parsePnpmLockPackages(lock)), readManifest), readManifest);
|
|
41
|
+
};
|
|
42
|
+
const lastLine = (output) => output.trimEnd().split("\n").at(-1);
|
|
43
|
+
const clusterFix = (overrides = {}) => ({
|
|
44
|
+
members: ["metro", "metro-config"],
|
|
45
|
+
duplicatedMembers: ["metro", "metro-config"],
|
|
46
|
+
memberVersions: {
|
|
47
|
+
metro: { versions: ["0.84.5", "0.87.0"], nonNpmCount: 0 },
|
|
48
|
+
"metro-config": { versions: ["0.84.5", "0.87.0"], nonNpmCount: 0 },
|
|
49
|
+
},
|
|
50
|
+
target: "0.87.0",
|
|
51
|
+
direction: "up",
|
|
52
|
+
convergentMembers: ["metro", "metro-config"],
|
|
53
|
+
driverMembers: ["metro"],
|
|
54
|
+
excludedMembers: [],
|
|
55
|
+
anchor: null,
|
|
56
|
+
reuseFixes: [],
|
|
57
|
+
floatingMembers: [],
|
|
58
|
+
workspaceChanges: [],
|
|
59
|
+
reResolutionSet: ["metro"],
|
|
60
|
+
externalConstraints: [],
|
|
61
|
+
needsRoundTrip: true,
|
|
62
|
+
applicable: true,
|
|
63
|
+
...overrides,
|
|
64
|
+
});
|
|
65
|
+
const singletonFix = (overrides = {}) => ({
|
|
66
|
+
...clusterFix(),
|
|
67
|
+
members: ["@babel/code-frame"],
|
|
68
|
+
duplicatedMembers: ["@babel/code-frame"],
|
|
69
|
+
memberVersions: {
|
|
70
|
+
"@babel/code-frame": { versions: ["7.26.2", "7.29.7"], nonNpmCount: 0 },
|
|
71
|
+
},
|
|
72
|
+
target: "7.29.7",
|
|
73
|
+
direction: "up",
|
|
74
|
+
convergentMembers: ["@babel/code-frame"],
|
|
75
|
+
driverMembers: ["@babel/code-frame"],
|
|
76
|
+
reResolutionSet: [],
|
|
77
|
+
needsRoundTrip: false,
|
|
78
|
+
...overrides,
|
|
79
|
+
});
|
|
80
|
+
describe("displayMany", () => {
|
|
81
|
+
it("groups every dependent under the version it resolved to", () => {
|
|
82
|
+
const output = renderDuplicates("duplicated-babel-frame");
|
|
83
|
+
ok(output.startsWith("Found "));
|
|
84
|
+
ok(output.includes("@babel/code-frame — 2 versions"));
|
|
85
|
+
ok(output.includes('package.json in dependencies requires "7.26.2"'));
|
|
86
|
+
// pnpm defers the per-package merge to `pnpm dedupe`
|
|
87
|
+
ok(!output.includes("can be deduped"));
|
|
88
|
+
});
|
|
89
|
+
it("renders the printable-shell-command duplicate", () => {
|
|
90
|
+
const output = renderDuplicates("duplicated-printable-shell-command");
|
|
91
|
+
ok(output.includes("printable-shell-command — 2 versions"));
|
|
92
|
+
ok(output.includes(" 5.0.7"));
|
|
93
|
+
});
|
|
94
|
+
it("reports no duplicates for a clean lockfile", () => {
|
|
95
|
+
expect(renderDuplicates("simple")).toBe("No duplicates found\n\nFound 1 dependency, 0 duplicates, 0 dedupable.\n");
|
|
96
|
+
});
|
|
97
|
+
it("maps a cluster of one to the versions that would collapse", () => {
|
|
98
|
+
const output = renderDuplicates("duplicated-babel-frame", [singletonFix()]);
|
|
99
|
+
ok(output.includes("@babel/code-frame — 2 versions, can be deduped to 7.29.7 (upgrade)"));
|
|
100
|
+
ok(output.includes(" 7.26.2 can be deduped to 7.29.7 (upgrade)"));
|
|
101
|
+
// a lone package is no family, so it gets no cluster section
|
|
102
|
+
ok(!output.includes("Lockstep clusters:"));
|
|
103
|
+
});
|
|
104
|
+
it("counts the whole lockfile and names the pnpm command", () => {
|
|
105
|
+
const output = renderDuplicates("duplicated-babel-frame", [singletonFix()]);
|
|
106
|
+
ok(lastLine(output).startsWith("Found 39 dependencies, 1 duplicate, at least 1 dedupable (deduping may remove more). Run `pnpm-dedupe`"));
|
|
107
|
+
});
|
|
108
|
+
it("says nothing collapses when the cluster of one is unfixable", () => {
|
|
109
|
+
const output = renderDuplicates("duplicated-babel-frame", [
|
|
110
|
+
singletonFix({
|
|
111
|
+
target: null,
|
|
112
|
+
direction: "none",
|
|
113
|
+
convergentMembers: [],
|
|
114
|
+
driverMembers: [],
|
|
115
|
+
applicable: false,
|
|
116
|
+
}),
|
|
117
|
+
]);
|
|
118
|
+
ok(!output.includes("can be deduped"));
|
|
119
|
+
ok(lastLine(output).includes("0 dedupable"));
|
|
120
|
+
});
|
|
121
|
+
it("files a declared range under the version it resolved to", () => {
|
|
122
|
+
const output = renderDowngrade();
|
|
123
|
+
ok(output.includes(' - expo-camera@57.0.3 requires "^3.0.0"'));
|
|
124
|
+
ok(output.includes(' - @yudiel/react-qr-scanner@2.3.1 requires "3.0.3"'));
|
|
125
|
+
});
|
|
126
|
+
it("names the downgrade a converging dependent has to take", () => {
|
|
127
|
+
const output = renderDowngrade();
|
|
128
|
+
ok(output.includes(" 3.2.2 can be deduped to 3.0.3 (downgrade)"));
|
|
129
|
+
// zxing-wasm cannot converge: `^2.1.2` against an exact 3.1.3
|
|
130
|
+
ok(lastLine(output).includes("2 duplicates, at least 1 dedupable"));
|
|
131
|
+
});
|
|
132
|
+
it("forwards the cluster fixes", () => {
|
|
133
|
+
const output = renderDuplicates("duplicated-babel-frame", [clusterFix()]);
|
|
134
|
+
ok(output.includes("Lockstep clusters:"));
|
|
135
|
+
ok(output.includes("cluster 1 — metro* [2 packages, 2 duplicated, 2 fixable]:"));
|
|
136
|
+
// the family's own members are not in this lockfile's duplicates, so the
|
|
137
|
+
// count — which follows the listed packages — stays at zero
|
|
138
|
+
ok(lastLine(output).includes("0 dedupable"));
|
|
139
|
+
});
|
|
140
|
+
it("renders a cluster from a lockfile with no per-package duplicate", () => {
|
|
141
|
+
const output = renderDuplicates("simple", [clusterFix()]);
|
|
142
|
+
ok(output.startsWith("No duplicates found"));
|
|
143
|
+
ok(output.includes("Lockstep clusters:"));
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=displayMany.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"displayMany.test.js","sourceRoot":"","sources":["../src/displayMany.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { ok, strictEqual } from "node:assert/strict";
|
|
3
|
+
import { buildIdentifiedFixesMap } from "pm-dedup-core";
|
|
4
|
+
describe("buildIdentifiedFixesMap", () => {
|
|
5
|
+
it("builds a map even when there are no fixes", () => {
|
|
6
|
+
const fakeResolution = {
|
|
7
|
+
resolution: "pkg@1.0.0",
|
|
8
|
+
package: {
|
|
9
|
+
type: "npm",
|
|
10
|
+
name: "pkg",
|
|
11
|
+
resolution: "pkg@1.0.0",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
},
|
|
14
|
+
installations: ["pkg@1.0.0"],
|
|
15
|
+
};
|
|
16
|
+
const duplicates = {
|
|
17
|
+
pkg: [fakeResolution],
|
|
18
|
+
};
|
|
19
|
+
const dependents = new Map();
|
|
20
|
+
const map = buildIdentifiedFixesMap(duplicates, dependents);
|
|
21
|
+
ok(map instanceof Map);
|
|
22
|
+
strictEqual(map.has("pkg"), true);
|
|
23
|
+
strictEqual(Array.isArray(map.get("pkg")), true);
|
|
24
|
+
strictEqual((map.get("pkg") || []).length, 0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=buildIdentifiedFixesMap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildIdentifiedFixesMap.test.js","sourceRoot":"","sources":["../../src/helpers/buildIdentifiedFixesMap.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAIxD,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,cAAc,GAAsB;YACxC,UAAU,EAAE,WAAW;YACvB,OAAO,EAAE;gBACP,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,KAAK;gBACX,UAAU,EAAE,WAAW;gBACvB,OAAO,EAAE,OAAO;aACjB;YACD,aAAa,EAAE,CAAC,WAAW,CAAC;SAC7B,CAAC;QAEF,MAAM,UAAU,GAAgB;YAC9B,GAAG,EAAE,CAAC,cAAc,CAAC;SACtB,CAAC;QAEF,MAAM,UAAU,GAAkB,IAAI,GAAG,EAAE,CAAC;QAE5C,MAAM,GAAG,GAAG,uBAAuB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAE5D,EAAE,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC;QACvB,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QAClC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACjD,WAAW,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Several packages can share the same name but different versions (duplicates).
|
|
2
|
+
// When a single resolution is installed in several peer contexts, every context
|
|
3
|
+
// is listed in `installations`.
|
|
4
|
+
export function buildPnpmPackagesMap({ packages, installationsByResolution, }) {
|
|
5
|
+
const packagesMap = {};
|
|
6
|
+
for (const pkg of packages.values()) {
|
|
7
|
+
if (!packagesMap[pkg.name]) {
|
|
8
|
+
packagesMap[pkg.name] = [];
|
|
9
|
+
}
|
|
10
|
+
packagesMap[pkg.name].push({
|
|
11
|
+
resolution: pkg.resolution,
|
|
12
|
+
package: pkg,
|
|
13
|
+
installations: installationsByResolution.get(pkg.resolution) ?? [
|
|
14
|
+
pkg.resolution,
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return packagesMap;
|
|
19
|
+
}
|
|
20
|
+
export function filterDuplicatesPnpmPackagesMap(packagesMap) {
|
|
21
|
+
return Object.fromEntries(Object.entries(packagesMap).filter(([, resolutions]) => resolutions.length > 1));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=buildPnpmPackagesMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildPnpmPackagesMap.js","sourceRoot":"","sources":["../../src/helpers/buildPnpmPackagesMap.ts"],"names":[],"mappings":"AAkBA,gFAAgF;AAChF,gFAAgF;AAChF,gCAAgC;AAChC,MAAM,UAAU,oBAAoB,CAAC,EACnC,QAAQ,EACR,yBAAyB,GACN;IACnB,MAAM,WAAW,GAAgB,EAAE,CAAC;IAEpC,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,CAAC;QAED,WAAW,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,IAAI,CAAC;YAC1B,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,OAAO,EAAE,GAAG;YACZ,aAAa,EAAE,yBAAyB,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI;gBAC9D,GAAG,CAAC,UAAU;aACf;SACF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,WAAwB;IAExB,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAC5C,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { readPnpmLock } from "../readPnpmLock.js";
|
|
5
|
+
import { buildPnpmPackagesMap, filterDuplicatesPnpmPackagesMap, } from "./buildPnpmPackagesMap.js";
|
|
6
|
+
import { parsePnpmLockPackages } from "./parsePnpmLockPackages.js";
|
|
7
|
+
const fixturesBase = (rel) => fileURLToPath(new URL(rel, import.meta.url));
|
|
8
|
+
const buildFor = (scenario) => buildPnpmPackagesMap(parsePnpmLockPackages(readPnpmLock(fixturesBase(`../../test/fixtures/${scenario}/pnpm-lock.yaml`))));
|
|
9
|
+
const versionsOf = (packagesMap, name) => new Set((packagesMap[name] ?? []).map((resolution) => resolution.package.version));
|
|
10
|
+
describe("buildPnpmPackagesMap", () => {
|
|
11
|
+
it("reports no duplicates for the simple fixture", () => {
|
|
12
|
+
const packagesMap = buildFor("simple");
|
|
13
|
+
strictEqual(packagesMap.semver?.length, 1);
|
|
14
|
+
strictEqual(Object.keys(filterDuplicatesPnpmPackagesMap(packagesMap)).length, 0);
|
|
15
|
+
});
|
|
16
|
+
it("reports no duplicates for one-dependency-of-dependency", () => {
|
|
17
|
+
const packagesMap = buildFor("one-dependency-of-dependency");
|
|
18
|
+
strictEqual(Object.keys(filterDuplicatesPnpmPackagesMap(packagesMap)).length, 0);
|
|
19
|
+
});
|
|
20
|
+
it("detects the duplicated @babel/code-frame versions", () => {
|
|
21
|
+
const duplicates = filterDuplicatesPnpmPackagesMap(buildFor("duplicated-babel-frame"));
|
|
22
|
+
const versions = versionsOf(duplicates, "@babel/code-frame");
|
|
23
|
+
strictEqual(versions.size, 2);
|
|
24
|
+
ok(versions.has("7.26.2"));
|
|
25
|
+
});
|
|
26
|
+
it("detects the duplicated printable-shell-command versions", () => {
|
|
27
|
+
const duplicates = filterDuplicatesPnpmPackagesMap(buildFor("duplicated-printable-shell-command"));
|
|
28
|
+
ok(duplicates["printable-shell-command"]);
|
|
29
|
+
strictEqual(duplicates["printable-shell-command"].length, 2);
|
|
30
|
+
});
|
|
31
|
+
it("detects the metro family a non-reused wildcard duplicated", () => {
|
|
32
|
+
// `@tamagui/metro-plugin` requests metro-config/metro-transform-worker with
|
|
33
|
+
// `*`; pnpm resolved them to 0.87.0 instead of reusing the 0.84.5 pulled by
|
|
34
|
+
// the root `metro: 0.84.5` pin, so the whole family is installed twice.
|
|
35
|
+
const duplicates = filterDuplicatesPnpmPackagesMap(buildFor("wildcard-not-reused"));
|
|
36
|
+
const family = Object.keys(duplicates).filter((name) => name === "metro" || name.startsWith("metro-"));
|
|
37
|
+
strictEqual(family.length, 14);
|
|
38
|
+
for (const name of family) {
|
|
39
|
+
deepStrictEqual(versionsOf(duplicates, name), new Set(["0.84.5", "0.87.0"]));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it("detects at least the known @typescript-eslint duplicates", () => {
|
|
43
|
+
const duplicates = filterDuplicatesPnpmPackagesMap(buildFor("duplicated-typescript-eslint"));
|
|
44
|
+
// Acceptance floor: the set of detected duplicates must include these.
|
|
45
|
+
const required = [
|
|
46
|
+
"@typescript-eslint/types",
|
|
47
|
+
"@typescript-eslint/scope-manager",
|
|
48
|
+
"@typescript-eslint/utils",
|
|
49
|
+
"@eslint/plugin-kit",
|
|
50
|
+
];
|
|
51
|
+
for (const name of required) {
|
|
52
|
+
ok(duplicates[name], `expected ${name} to be detected as duplicate`);
|
|
53
|
+
strictEqual(duplicates[name].length, 2);
|
|
54
|
+
}
|
|
55
|
+
const typesVersions = versionsOf(duplicates, "@typescript-eslint/types");
|
|
56
|
+
ok(typesVersions.has("8.59.1"));
|
|
57
|
+
ok(typesVersions.has("8.61.0"));
|
|
58
|
+
});
|
|
59
|
+
// `aliased-swapped-names`: identity comes from the package id, so the two real
|
|
60
|
+
// `typescript` copies meet and the `@typescript/typescript6` the `typescript`
|
|
61
|
+
// key points at stays on its own.
|
|
62
|
+
it("identifies packages by their id when alias keys are swapped", () => {
|
|
63
|
+
const duplicates = filterDuplicatesPnpmPackagesMap(buildFor("aliased-swapped-names"));
|
|
64
|
+
deepStrictEqual(Object.keys(duplicates), ["typescript"]);
|
|
65
|
+
deepStrictEqual(versionsOf(duplicates, "typescript"), new Set(["5.9.3", "7.0.2"]));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=buildPnpmPackagesMap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildPnpmPackagesMap.test.js","sourceRoot":"","sources":["../../src/helpers/buildPnpmPackagesMap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { PackageDependencyDescriptorUtils, isSemverComparable, } from "pm-dedup-core";
|
|
2
|
+
import { parsePackageId, resolveSnapshotDependency, } from "./parsePnpmLockPackages.js";
|
|
3
|
+
const snapshotDepTypes = ["dependencies", "optionalDependencies"];
|
|
4
|
+
const manifestDepTypes = [
|
|
5
|
+
"dependencies",
|
|
6
|
+
"optionalDependencies",
|
|
7
|
+
"peerDependencies",
|
|
8
|
+
];
|
|
9
|
+
// a declaration semver cannot read says nothing about the version; the caller
|
|
10
|
+
// falls back to the version this requester actually got
|
|
11
|
+
const declaredRange = (depName, declared) => {
|
|
12
|
+
const parsed = PackageDependencyDescriptorUtils.parse(depName, declared);
|
|
13
|
+
return isSemverComparable(parsed) ? parsed.selector : undefined;
|
|
14
|
+
};
|
|
15
|
+
const manifestRange = (manifest, depName) => {
|
|
16
|
+
if (!manifest)
|
|
17
|
+
return undefined;
|
|
18
|
+
for (const depType of manifestDepTypes) {
|
|
19
|
+
const declared = manifest[depType]?.[depName];
|
|
20
|
+
if (declared === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
const range = declaredRange(depName, declared);
|
|
23
|
+
if (range === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
return depType === "peerDependencies" ? { range, peer: true } : { range };
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The peer range as the lockfile itself records it.
|
|
31
|
+
*
|
|
32
|
+
* pnpm resolves peers before writing the lockfile and folds each resolved peer
|
|
33
|
+
* into the snapshot's `dependencies` — which is why a peer edge is found here at
|
|
34
|
+
* all, unlike yarn's, where the fold lives only in the virtual packages the
|
|
35
|
+
* lockfile omits. But the snapshot carries the resolved *version*; the range
|
|
36
|
+
* stays behind in the `packages:` entry. Without it, seventeen plugins declaring
|
|
37
|
+
* `eslint: "^6.0.0 || ^7.0.0 || >=8.0.0"` and the like all read as exact pins of
|
|
38
|
+
* whatever got installed, and no version can satisfy them all — every merge
|
|
39
|
+
* ruled out by ranges that never said so.
|
|
40
|
+
*
|
|
41
|
+
* `packages:` keys carry no peer suffix, so the resolved id addresses the entry
|
|
42
|
+
* directly. `depName` is the key the requester declared, which is what an
|
|
43
|
+
* aliased peer sits under.
|
|
44
|
+
*/
|
|
45
|
+
const lockfilePeerRange = (lock, dependent, depName) => {
|
|
46
|
+
const declared = lock.packages?.[`${dependent.name}@${dependent.version}`]
|
|
47
|
+
?.peerDependencies?.[depName];
|
|
48
|
+
if (declared === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
const range = declaredRange(depName, declared);
|
|
51
|
+
return range === undefined ? undefined : { range, peer: true };
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Collect, for each duplicated package, the real semver range every dependent
|
|
55
|
+
* declares. Direct (importer) dependents carry their specifier; transitive
|
|
56
|
+
* dependents' ranges are read from their installed manifest (the pnpm lockfile
|
|
57
|
+
* only stores the resolved version, not the range). When a manifest is missing
|
|
58
|
+
* (project not installed), the resolved version is used as an exact fallback.
|
|
59
|
+
*/
|
|
60
|
+
export const collectDependentRanges = (lock, duplicatePackageNames, readManifest) => {
|
|
61
|
+
const rangesMap = new Map();
|
|
62
|
+
const add = (name, dependent) => {
|
|
63
|
+
if (!duplicatePackageNames.has(name))
|
|
64
|
+
return;
|
|
65
|
+
let dependents = rangesMap.get(name);
|
|
66
|
+
if (!dependents) {
|
|
67
|
+
dependents = [];
|
|
68
|
+
rangesMap.set(name, dependents);
|
|
69
|
+
}
|
|
70
|
+
dependents.push(dependent);
|
|
71
|
+
};
|
|
72
|
+
for (const [importerPath, project] of Object.entries(lock.importers ?? {})) {
|
|
73
|
+
["dependencies", "devDependencies", "optionalDependencies"].forEach((depType) => {
|
|
74
|
+
const deps = project[depType];
|
|
75
|
+
if (!deps)
|
|
76
|
+
return;
|
|
77
|
+
for (const [depName, { specifier, version }] of Object.entries(deps)) {
|
|
78
|
+
const parsed = PackageDependencyDescriptorUtils.parse(depName, specifier);
|
|
79
|
+
// `workspace:` and `catalog:` name something other than the npm package
|
|
80
|
+
// sharing this key, so they constrain no npm version
|
|
81
|
+
if (!isSemverComparable(parsed))
|
|
82
|
+
continue;
|
|
83
|
+
add(parsed.npmName, {
|
|
84
|
+
key: `${importerPath === "." ? "package.json" : importerPath} in ${depType}`,
|
|
85
|
+
range: parsed.selector,
|
|
86
|
+
// an aliased importer entry stores `realName@version`, not a version
|
|
87
|
+
resolvedVersion: resolveSnapshotDependency(depName, version).version,
|
|
88
|
+
...(parsed.isAlias ? { aliasKey: parsed.key } : {}),
|
|
89
|
+
workspace: { path: importerPath, depType },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
for (const [snapshotKey, snapshot] of Object.entries(lock.snapshots ?? {})) {
|
|
95
|
+
const dependent = parsePackageId(snapshotKey);
|
|
96
|
+
snapshotDepTypes.forEach((depType) => {
|
|
97
|
+
const deps = snapshot[depType];
|
|
98
|
+
if (!deps)
|
|
99
|
+
return;
|
|
100
|
+
for (const [depName, depValue] of Object.entries(deps)) {
|
|
101
|
+
const { name, version } = resolveSnapshotDependency(depName, depValue);
|
|
102
|
+
if (!duplicatePackageNames.has(name))
|
|
103
|
+
continue;
|
|
104
|
+
const source = manifestRange(readManifest(dependent.name, dependent.version), depName) ?? lockfilePeerRange(lock, dependent, depName);
|
|
105
|
+
add(name, {
|
|
106
|
+
key: `${dependent.name}@${dependent.version}`,
|
|
107
|
+
range: source?.range ?? version,
|
|
108
|
+
resolvedVersion: version,
|
|
109
|
+
requesterName: dependent.name,
|
|
110
|
+
...(name === depName ? {} : { aliasKey: depName }),
|
|
111
|
+
...(source?.peer ? { peer: source.peer } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return rangesMap;
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=collectDependentRanges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectDependentRanges.js","sourceRoot":"","sources":["../../src/helpers/collectDependentRanges.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { deepStrictEqual, strictEqual } from "node:assert/strict";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { readPnpmLock } from "../readPnpmLock.js";
|
|
5
|
+
import { collectDependentRanges } from "./collectDependentRanges.js";
|
|
6
|
+
import { createManifestReader } from "./readInstalledManifest.js";
|
|
7
|
+
const fixturesBase = (rel) => fileURLToPath(new URL(rel, import.meta.url));
|
|
8
|
+
const lockFor = (scenario) => readPnpmLock(fixturesBase(`../../test/fixtures/${scenario}/pnpm-lock.yaml`));
|
|
9
|
+
const rangesFor = (scenario, packageNames, readManifest = () => undefined) => collectDependentRanges(lockFor(scenario), new Set(packageNames), readManifest);
|
|
10
|
+
// In `wildcard-not-reused`, `@tamagui/metro-plugin@1.139.4` declares
|
|
11
|
+
// `metro-config: "*"` and `metro-transform-worker: "*"`. The lockfile only
|
|
12
|
+
// stores the version pnpm picked (0.87.0), so the wildcard is only visible
|
|
13
|
+
// through the installed manifest.
|
|
14
|
+
const wildcardScenario = "wildcard-not-reused";
|
|
15
|
+
const wildcardPackages = ["metro-config", "metro-transform-worker"];
|
|
16
|
+
const wildcardDependent = "@tamagui/metro-plugin@1.139.4";
|
|
17
|
+
const readWildcardManifest = (name, version) => name === "@tamagui/metro-plugin" && version === "1.139.4"
|
|
18
|
+
? { dependencies: { "metro-config": "*", "metro-transform-worker": "*" } }
|
|
19
|
+
: undefined;
|
|
20
|
+
// `exact-pin-forces-downgrade` commits its virtual store, so the ranges come
|
|
21
|
+
// from the real reader: `@yudiel/react-qr-scanner` pins `barcode-detector`
|
|
22
|
+
// exactly at 3.0.3 while `expo-camera` declares `^3.0.0` and resolved 3.2.2.
|
|
23
|
+
const downgradeScenario = "exact-pin-forces-downgrade";
|
|
24
|
+
const readDowngradeManifest = createManifestReader(fixturesBase(`../../test/fixtures/${downgradeScenario}`));
|
|
25
|
+
const rangeOf = (ranges, packageName, dependentKey) => ranges.get(packageName)?.find((dependent) => dependent.key === dependentKey)
|
|
26
|
+
?.range;
|
|
27
|
+
describe("collectDependentRanges", () => {
|
|
28
|
+
it("takes a direct dependency's range from the importer specifier", () => {
|
|
29
|
+
deepStrictEqual(rangesFor("simple", ["semver"]).get("semver"), [
|
|
30
|
+
{
|
|
31
|
+
key: "package.json in dependencies",
|
|
32
|
+
range: "7.7.3",
|
|
33
|
+
resolvedVersion: "7.7.3",
|
|
34
|
+
workspace: { path: ".", depType: "dependencies" },
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
it("ignores packages that are not in the requested set", () => {
|
|
39
|
+
deepStrictEqual([...rangesFor("duplicated-babel-frame", ["@babel/code-frame"]).keys()], ["@babel/code-frame"]);
|
|
40
|
+
});
|
|
41
|
+
it("reads a transitive dependent's declared range from its manifest", () => {
|
|
42
|
+
const ranges = rangesFor(wildcardScenario, wildcardPackages, readWildcardManifest);
|
|
43
|
+
for (const name of wildcardPackages) {
|
|
44
|
+
strictEqual(rangeOf(ranges, name, wildcardDependent), "*");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it("falls back to the resolved version when the manifest is unavailable", () => {
|
|
48
|
+
const ranges = rangesFor(wildcardScenario, wildcardPackages);
|
|
49
|
+
for (const name of wildcardPackages) {
|
|
50
|
+
strictEqual(rangeOf(ranges, name, wildcardDependent), "0.87.0");
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// pnpm folds a resolved peer into the snapshot's `dependencies`, so the edge is
|
|
54
|
+
// there — but only as the version it resolved to. The range stays in the
|
|
55
|
+
// `packages:` entry, and read from there a peer constraint needs no
|
|
56
|
+
// `node_modules`: without it every one of these read as an exact pin of 10.5.0,
|
|
57
|
+
// and no version could satisfy them all.
|
|
58
|
+
it("reads a peer range from the lockfile when no manifest is available", () => {
|
|
59
|
+
const ranges = rangesFor("duplicated-typescript-eslint", ["eslint"]);
|
|
60
|
+
strictEqual(rangeOf(ranges, "eslint", "@eslint-community/eslint-utils@4.9.1"), "^6.0.0 || ^7.0.0 || >=8.0.0");
|
|
61
|
+
strictEqual(rangeOf(ranges, "eslint", "@typescript-eslint/eslint-plugin@8.61.0"), "^8.57.0 || ^9.0.0 || ^10.0.0");
|
|
62
|
+
// declared optional, and kept: pnpm only writes the edge once something
|
|
63
|
+
// provides the peer, so an edge that exists is a peer that was provided
|
|
64
|
+
strictEqual(rangeOf(ranges, "eslint", "@eslint/js@10.0.1"), "^10.0.0");
|
|
65
|
+
});
|
|
66
|
+
// pnpm resolves a peer like any other edge, so nothing in the snapshot tells a
|
|
67
|
+
// peer requester apart from one holding a copy of its own — the report has to
|
|
68
|
+
it("marks a dependent whose range came from a peer declaration", () => {
|
|
69
|
+
const dependents = rangesFor("duplicated-typescript-eslint", ["eslint"]).get("eslint") ?? [];
|
|
70
|
+
strictEqual(dependents.find((dependent) => dependent.key === "eslint-plugin-n@17.24.0")?.peer, true);
|
|
71
|
+
// a real dependency edge onto the same package stays unmarked
|
|
72
|
+
strictEqual(rangesFor("duplicated-typescript-eslint", ["@eslint/plugin-kit"])
|
|
73
|
+
.get("@eslint/plugin-kit")
|
|
74
|
+
?.find((dependent) => dependent.key === "eslint@10.5.0")?.peer, undefined);
|
|
75
|
+
});
|
|
76
|
+
// the manifest is read first, so a name a package declares as both a
|
|
77
|
+
// dependency and a peer keeps the dependency's range
|
|
78
|
+
it("prefers a manifest declaration over the lockfile peer block", () => {
|
|
79
|
+
strictEqual(rangeOf(rangesFor("duplicated-typescript-eslint", ["eslint"], (name, version) => name === "@eslint-community/eslint-utils" && version === "4.9.1"
|
|
80
|
+
? { dependencies: { eslint: "^10.1.0" } }
|
|
81
|
+
: undefined), "eslint", "@eslint-community/eslint-utils@4.9.1"), "^10.1.0");
|
|
82
|
+
});
|
|
83
|
+
it("separates a caret dependent's range from the version it resolved to", () => {
|
|
84
|
+
const ranges = rangesFor(downgradeScenario, ["barcode-detector"], readDowngradeManifest);
|
|
85
|
+
deepStrictEqual(ranges.get("barcode-detector"), [
|
|
86
|
+
{
|
|
87
|
+
key: "@yudiel/react-qr-scanner@2.3.1",
|
|
88
|
+
range: "3.0.3",
|
|
89
|
+
resolvedVersion: "3.0.3",
|
|
90
|
+
requesterName: "@yudiel/react-qr-scanner",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: "expo-camera@57.0.3",
|
|
94
|
+
range: "^3.0.0",
|
|
95
|
+
resolvedVersion: "3.2.2",
|
|
96
|
+
requesterName: "expo-camera",
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
// `aliased-swapped-names`: the importer reaches the real `typescript` through
|
|
101
|
+
// the `@typescript/native` key and hands the `typescript` key to another
|
|
102
|
+
// package. The dependent has to be filed under the npm name the specifier
|
|
103
|
+
// targets, with the range and the version the alias resolved to, and the key
|
|
104
|
+
// it was declared under — that key is what makes the declaration binding
|
|
105
|
+
// rather than a range the dedupe may rewrite.
|
|
106
|
+
it("resolves an importer alias whose key names another package", () => {
|
|
107
|
+
const ranges = rangesFor("aliased-swapped-names", [
|
|
108
|
+
"typescript",
|
|
109
|
+
"@typescript/typescript6",
|
|
110
|
+
]);
|
|
111
|
+
deepStrictEqual(ranges.get("typescript"), [
|
|
112
|
+
{
|
|
113
|
+
key: "package.json in devDependencies",
|
|
114
|
+
range: "7.0.2",
|
|
115
|
+
resolvedVersion: "7.0.2",
|
|
116
|
+
aliasKey: "@typescript/native",
|
|
117
|
+
workspace: { path: ".", depType: "devDependencies" },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: "tool@1.0.0",
|
|
121
|
+
range: "5.9.3",
|
|
122
|
+
resolvedVersion: "5.9.3",
|
|
123
|
+
requesterName: "tool",
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
deepStrictEqual(ranges.get("@typescript/typescript6"), [
|
|
127
|
+
{
|
|
128
|
+
key: "package.json in devDependencies",
|
|
129
|
+
range: "6.0.2",
|
|
130
|
+
resolvedVersion: "6.0.2",
|
|
131
|
+
aliasKey: "typescript",
|
|
132
|
+
workspace: { path: ".", depType: "devDependencies" },
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
// pnpm writes an aliased importer's `version` as `realName@version`, which is
|
|
137
|
+
// not a version: carrying it through would put a string no semver call accepts
|
|
138
|
+
// where the resolved version belongs.
|
|
139
|
+
it("strips the alias target from an importer's resolved version", () => {
|
|
140
|
+
deepStrictEqual(rangesFor("mergeable-alias", ["printable-shell-command"])
|
|
141
|
+
.get("printable-shell-command")
|
|
142
|
+
?.filter((dependent) => dependent.workspace !== undefined), [
|
|
143
|
+
{
|
|
144
|
+
key: "package.json in devDependencies",
|
|
145
|
+
range: "^5.0.0",
|
|
146
|
+
resolvedVersion: "5.3.1",
|
|
147
|
+
workspace: { path: ".", depType: "devDependencies" },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
key: "package.json in devDependencies",
|
|
151
|
+
range: "5.0.7",
|
|
152
|
+
resolvedVersion: "5.0.7",
|
|
153
|
+
aliasKey: "printable-shell-command-pinned",
|
|
154
|
+
workspace: { path: ".", depType: "devDependencies" },
|
|
155
|
+
},
|
|
156
|
+
]);
|
|
157
|
+
});
|
|
158
|
+
it("keeps the exact ranges a package declares for its own family", () => {
|
|
159
|
+
const ranges = rangesFor(wildcardScenario, wildcardPackages, readWildcardManifest);
|
|
160
|
+
for (const name of wildcardPackages) {
|
|
161
|
+
strictEqual(rangeOf(ranges, name, "metro@0.84.5"), "0.84.5");
|
|
162
|
+
strictEqual(rangeOf(ranges, name, "metro@0.87.0"), "0.87.0");
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
// A `workspace:`, `catalog:`, `file:` or `link:` specifier names something
|
|
166
|
+
// other than the npm package sharing its key, and a dist-tag carries no
|
|
167
|
+
// comparable range. Both would read as unsatisfiable ranges rather than as
|
|
168
|
+
// "no constraint", which suppresses merges the real dependents allow.
|
|
169
|
+
it("leaves out importer specifiers that are not npm ranges", () => {
|
|
170
|
+
const ranges = rangesFor("protocol-dependents", [
|
|
171
|
+
"helper",
|
|
172
|
+
"semver",
|
|
173
|
+
"picocolors",
|
|
174
|
+
"left-pad",
|
|
175
|
+
"kleur",
|
|
176
|
+
"chalk",
|
|
177
|
+
]);
|
|
178
|
+
// only the snapshot dependent of `app` survives for semver; the importer
|
|
179
|
+
// declares it as `latest`
|
|
180
|
+
deepStrictEqual(ranges.get("semver"), [
|
|
181
|
+
{
|
|
182
|
+
key: "app@1.0.0",
|
|
183
|
+
range: "7.8.1",
|
|
184
|
+
resolvedVersion: "7.8.1",
|
|
185
|
+
requesterName: "app",
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
188
|
+
for (const name of ["helper", "picocolors", "left-pad", "kleur", "chalk"]) {
|
|
189
|
+
strictEqual(ranges.has(name), false);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
// A transitive dependent whose installed manifest declares a non-npm
|
|
193
|
+
// protocol falls back to the version it actually got, rather than carrying
|
|
194
|
+
// `workspace:*` into a semver comparison.
|
|
195
|
+
it("falls back to the resolved version when the manifest range is not comparable", () => {
|
|
196
|
+
const readWorkspaceManifest = (name, version) => name === "@tamagui/metro-plugin" && version === "1.139.4"
|
|
197
|
+
? {
|
|
198
|
+
dependencies: {
|
|
199
|
+
"metro-config": "workspace:*",
|
|
200
|
+
"metro-transform-worker": "*",
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
: undefined;
|
|
204
|
+
const ranges = rangesFor(wildcardScenario, wildcardPackages, readWorkspaceManifest);
|
|
205
|
+
strictEqual(rangeOf(ranges, "metro-config", wildcardDependent),
|
|
206
|
+
// the resolved version, not `*` and not `workspace:*`
|
|
207
|
+
"0.87.0");
|
|
208
|
+
strictEqual(rangeOf(ranges, "metro-transform-worker", wildcardDependent), "*");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=collectDependentRanges.test.js.map
|