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,430 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import type { ClusterFix } from "pm-dedup-core";
|
|
6
|
+
import { buildPnpmPackagesMap } from "./helpers/buildPnpmPackagesMap.ts";
|
|
7
|
+
import { parsePnpmLockPackages } from "./helpers/parsePnpmLockPackages.ts";
|
|
8
|
+
import type { ManifestReader } from "./helpers/readInstalledManifest.ts";
|
|
9
|
+
import { createManifestReader } from "./helpers/readInstalledManifest.ts";
|
|
10
|
+
import { identifyClusterFixes } from "./identifyClusterFixes.ts";
|
|
11
|
+
import { readPnpmLock } from "./readPnpmLock.ts";
|
|
12
|
+
|
|
13
|
+
const fixturesBase = (rel: string) =>
|
|
14
|
+
fileURLToPath(new URL(rel, import.meta.url));
|
|
15
|
+
|
|
16
|
+
// The fixtures are committed without node_modules, so the declared ranges of
|
|
17
|
+
// the requesters that matter are stubbed; the others fall back to their
|
|
18
|
+
// resolved version, as an uninstalled project would.
|
|
19
|
+
const manifests: Record<string, Record<string, string>> = {
|
|
20
|
+
"@tamagui/metro-plugin@1.139.4": {
|
|
21
|
+
"metro-config": "*",
|
|
22
|
+
"metro-transform-worker": "*",
|
|
23
|
+
},
|
|
24
|
+
"@react-native/community-cli-plugin@0.87.0": { metro: "^0.87.0" },
|
|
25
|
+
"@react-native/metro-config@0.87.0": { "metro-config": "^0.87.0" },
|
|
26
|
+
"react-native-web@0.21.2": { "@react-native/normalize-colors": "^0.74.1" },
|
|
27
|
+
"react-native-reanimated@4.5.3": { "react-native": "0.83 - 0.86" },
|
|
28
|
+
"react-native-worklets@0.11.4": {
|
|
29
|
+
"react-native": "0.83 - 0.86",
|
|
30
|
+
"@react-native/metro-config": "*",
|
|
31
|
+
},
|
|
32
|
+
"@pob/eslint-config@65.5.0": {
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^8.59.1",
|
|
34
|
+
"@typescript-eslint/parser": "^8.59.1",
|
|
35
|
+
"typescript-eslint": "^8.59.1",
|
|
36
|
+
},
|
|
37
|
+
"@pob/eslint-plugin@65.5.0": { "@typescript-eslint/utils": "8.59.1" },
|
|
38
|
+
"eslint-plugin-import-x@4.16.2": {
|
|
39
|
+
"@typescript-eslint/types": "^8.56.0",
|
|
40
|
+
"@typescript-eslint/utils": "^8.56.0",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// The @tamagui/* and react-native-* packages peer-depend on react-native with
|
|
45
|
+
// an open range (`*`, or `0.83 - 0.86` for reanimated/worklets, both above):
|
|
46
|
+
// without them react-native's ranges read as exact resolved versions and the
|
|
47
|
+
// "nobody asks for this version" guard is never exercised.
|
|
48
|
+
const readStubbedManifest: ManifestReader = (name, version) => {
|
|
49
|
+
const dependencies = manifests[`${name}@${version}`];
|
|
50
|
+
if (dependencies) return { dependencies };
|
|
51
|
+
if (name.startsWith("@tamagui/") || name.startsWith("react-native-")) {
|
|
52
|
+
return { peerDependencies: { "react-native": "*" } };
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const clusterFixesFor = (
|
|
58
|
+
scenario: string,
|
|
59
|
+
readManifest: ManifestReader = readStubbedManifest,
|
|
60
|
+
): ClusterFix[] => {
|
|
61
|
+
const lock = readPnpmLock(
|
|
62
|
+
fixturesBase(`../test/fixtures/${scenario}/pnpm-lock.yaml`),
|
|
63
|
+
);
|
|
64
|
+
return identifyClusterFixes(
|
|
65
|
+
lock,
|
|
66
|
+
buildPnpmPackagesMap(parsePnpmLockPackages(lock)),
|
|
67
|
+
readManifest,
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Only the families: a duplicate with no lockstep sibling is reported as a
|
|
72
|
+
// cluster of one, which the tests below single out on its own.
|
|
73
|
+
const lockstepFixesFor = (
|
|
74
|
+
scenario: string,
|
|
75
|
+
readManifest: ManifestReader = readStubbedManifest,
|
|
76
|
+
): ClusterFix[] =>
|
|
77
|
+
clusterFixesFor(scenario, readManifest).filter(
|
|
78
|
+
(fix) => fix.members.length > 1,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const metroCluster = (): ClusterFix => {
|
|
82
|
+
const fix = clusterFixesFor("wildcard-not-reused").find((candidate) =>
|
|
83
|
+
candidate.members.includes("metro"),
|
|
84
|
+
);
|
|
85
|
+
ok(fix, "expected a cluster containing metro");
|
|
86
|
+
return fix;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
describe("identifyClusterFixes", () => {
|
|
90
|
+
// Same expectations as bun-dedup's identifyClusterFixes on its own
|
|
91
|
+
// `duplicated-typescript-eslint` fixture: the pnpm adapter must reach the
|
|
92
|
+
// same conclusion from a lockfile shaped very differently.
|
|
93
|
+
it("identifies the @typescript-eslint cluster dedupable to 8.59.1", () => {
|
|
94
|
+
const fixes = lockstepFixesFor("duplicated-typescript-eslint");
|
|
95
|
+
strictEqual(fixes.length, 1);
|
|
96
|
+
const fix = fixes[0]!;
|
|
97
|
+
|
|
98
|
+
strictEqual(fix.applicable, true);
|
|
99
|
+
strictEqual(fix.target, "8.59.1");
|
|
100
|
+
strictEqual(fix.direction, "down");
|
|
101
|
+
strictEqual(fix.needsRoundTrip, true);
|
|
102
|
+
ok(fix.members.includes("typescript-eslint"));
|
|
103
|
+
deepStrictEqual(fix.duplicatedMembers, [
|
|
104
|
+
"@typescript-eslint/project-service",
|
|
105
|
+
"@typescript-eslint/scope-manager",
|
|
106
|
+
"@typescript-eslint/tsconfig-utils",
|
|
107
|
+
"@typescript-eslint/types",
|
|
108
|
+
"@typescript-eslint/typescript-estree",
|
|
109
|
+
"@typescript-eslint/utils",
|
|
110
|
+
"@typescript-eslint/visitor-keys",
|
|
111
|
+
]);
|
|
112
|
+
deepStrictEqual(fix.reResolutionSet, [
|
|
113
|
+
"@typescript-eslint/eslint-plugin",
|
|
114
|
+
"@typescript-eslint/parser",
|
|
115
|
+
"typescript-eslint",
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("finds no target when the requesters' manifests are unavailable", () => {
|
|
120
|
+
// Without node_modules every transitive range degrades to the resolved
|
|
121
|
+
// version, so `@pob/eslint-config`'s `^8.59.1` reads as `8.61.0` and
|
|
122
|
+
// `@pob/eslint-plugin`'s pin keeps utils at 8.59.1: no version is left that
|
|
123
|
+
// any duplicate can collapse onto.
|
|
124
|
+
const fixes = lockstepFixesFor(
|
|
125
|
+
"duplicated-typescript-eslint",
|
|
126
|
+
() => undefined,
|
|
127
|
+
);
|
|
128
|
+
strictEqual(fixes.length, 1);
|
|
129
|
+
const fix = fixes[0]!;
|
|
130
|
+
strictEqual(fix.applicable, false);
|
|
131
|
+
strictEqual(fix.target, null);
|
|
132
|
+
deepStrictEqual(fix.reuseFixes, []);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// `hoisted-node-linker` is the shape of a real repo that reported no fix at
|
|
136
|
+
// all: with `nodeLinker: hoisted` there is no `node_modules/.pnpm/<pkg>` to
|
|
137
|
+
// read, so every declared range degraded to the resolved version. It is the
|
|
138
|
+
// only fixture whose manifests are committed, so the real reader runs here.
|
|
139
|
+
const hoistedFixtureDir = fixturesBase(
|
|
140
|
+
"../test/fixtures/hoisted-node-linker",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const hoistedClusterFix = (readManifest: ManifestReader): ClusterFix => {
|
|
144
|
+
const lock = readPnpmLock(join(hoistedFixtureDir, "pnpm-lock.yaml"));
|
|
145
|
+
const fixes = identifyClusterFixes(
|
|
146
|
+
lock,
|
|
147
|
+
buildPnpmPackagesMap(parsePnpmLockPackages(lock)),
|
|
148
|
+
readManifest,
|
|
149
|
+
);
|
|
150
|
+
strictEqual(fixes.length, 1);
|
|
151
|
+
return fixes[0]!;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
it("reads the ranges out of a hoisted node_modules", () => {
|
|
155
|
+
const fix = hoistedClusterFix(createManifestReader(hoistedFixtureDir));
|
|
156
|
+
|
|
157
|
+
// the pinned 0.84.5 wins: `mini-plugin` asks for `*`, so nothing external
|
|
158
|
+
// wants 0.87.0 and the pin needs no editing
|
|
159
|
+
strictEqual(fix.applicable, true);
|
|
160
|
+
strictEqual(fix.target, "0.84.5");
|
|
161
|
+
strictEqual(fix.direction, "down");
|
|
162
|
+
deepStrictEqual(fix.convergentMembers, ["mini-metro", "mini-metro-config"]);
|
|
163
|
+
deepStrictEqual(fix.driverMembers, ["mini-metro"]);
|
|
164
|
+
deepStrictEqual(fix.workspaceChanges, []);
|
|
165
|
+
strictEqual(fix.needsRoundTrip, false);
|
|
166
|
+
deepStrictEqual(
|
|
167
|
+
fix.reuseFixes.map(
|
|
168
|
+
(reuse) =>
|
|
169
|
+
`${reuse.requesterName}>${reuse.packageName} ${reuse.from} -> ${reuse.to}`,
|
|
170
|
+
),
|
|
171
|
+
[
|
|
172
|
+
// read from a nested manifest three levels deep, via `.modules.yaml`
|
|
173
|
+
"mini-deep>mini-metro-config 0.87.0 -> 0.84.5",
|
|
174
|
+
"mini-plugin>mini-metro-config 0.87.0 -> 0.84.5",
|
|
175
|
+
],
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("inverts the advice when the layout hides the manifests", () => {
|
|
180
|
+
// What the same lockfile reports when the manifests cannot be read:
|
|
181
|
+
// `mini-plugin`'s `*` reads as an exact `0.87.0`, so keeping the pin
|
|
182
|
+
// collapses nothing and the fix becomes "upgrade the pin" — the opposite of
|
|
183
|
+
// the answer above. Hence the warning when nothing is readable.
|
|
184
|
+
const fix = hoistedClusterFix(() => undefined);
|
|
185
|
+
|
|
186
|
+
strictEqual(fix.target, "0.87.0");
|
|
187
|
+
strictEqual(fix.direction, "up");
|
|
188
|
+
deepStrictEqual(
|
|
189
|
+
fix.workspaceChanges.map(
|
|
190
|
+
(change) => `${change.packageName} ${change.range} -> ${change.to}`,
|
|
191
|
+
),
|
|
192
|
+
["mini-metro 0.84.5 -> 0.87.0"],
|
|
193
|
+
);
|
|
194
|
+
deepStrictEqual(fix.reuseFixes, []);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("returns no cluster fix when there is no lockstep family", () => {
|
|
198
|
+
deepStrictEqual(lockstepFixesFor("duplicated-printable-shell-command"), []);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// `exact-pin-forces-downgrade` commits its virtual store, so the real reader
|
|
202
|
+
// runs: `@yudiel/react-qr-scanner` pins barcode-detector at exactly 3.0.3
|
|
203
|
+
// while `expo-camera` declares `^3.0.0` and resolved 3.2.2.
|
|
204
|
+
const downgradeFixtureDir = fixturesBase(
|
|
205
|
+
"../test/fixtures/exact-pin-forces-downgrade",
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
it("converges a lone duplicate on the version every range accepts", () => {
|
|
209
|
+
const fixes = clusterFixesFor(
|
|
210
|
+
"exact-pin-forces-downgrade",
|
|
211
|
+
createManifestReader(downgradeFixtureDir),
|
|
212
|
+
);
|
|
213
|
+
const fix = fixes.find((candidate) =>
|
|
214
|
+
candidate.members.includes("barcode-detector"),
|
|
215
|
+
);
|
|
216
|
+
ok(fix, "expected a fix for barcode-detector");
|
|
217
|
+
|
|
218
|
+
deepStrictEqual(fix.members, ["barcode-detector"]);
|
|
219
|
+
strictEqual(fix.applicable, true);
|
|
220
|
+
// 3.2.2 is rejected by the exact pin, so converging means moving the caret
|
|
221
|
+
// dependent down — which only an override can do
|
|
222
|
+
strictEqual(fix.target, "3.0.3");
|
|
223
|
+
strictEqual(fix.direction, "down");
|
|
224
|
+
deepStrictEqual(fix.convergentMembers, ["barcode-detector"]);
|
|
225
|
+
deepStrictEqual(fix.driverMembers, ["barcode-detector"]);
|
|
226
|
+
deepStrictEqual(fix.workspaceChanges, []);
|
|
227
|
+
deepStrictEqual(
|
|
228
|
+
fix.externalConstraints.map((constraint) => [
|
|
229
|
+
constraint.requesterName,
|
|
230
|
+
constraint.range,
|
|
231
|
+
]),
|
|
232
|
+
[
|
|
233
|
+
["@yudiel/react-qr-scanner", "3.0.3"],
|
|
234
|
+
["expo-camera", "^3.0.0"],
|
|
235
|
+
],
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("finds nothing for the duplicate a convergence drags along", () => {
|
|
240
|
+
const fixes = clusterFixesFor(
|
|
241
|
+
"exact-pin-forces-downgrade",
|
|
242
|
+
createManifestReader(downgradeFixtureDir),
|
|
243
|
+
);
|
|
244
|
+
const fix = fixes.find((candidate) =>
|
|
245
|
+
candidate.members.includes("zxing-wasm"),
|
|
246
|
+
);
|
|
247
|
+
ok(fix, "expected a fix record for zxing-wasm");
|
|
248
|
+
|
|
249
|
+
// barcode-detector@3.0.3 asks `^2.1.2` and 3.2.2 asks exactly 3.1.3
|
|
250
|
+
strictEqual(fix.applicable, false);
|
|
251
|
+
strictEqual(fix.target, null);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("leaves a lone duplicate alone when only a workspace pin could move", () => {
|
|
255
|
+
// `printable-shell-command` is pinned at 5.0.7 by the workspace and at
|
|
256
|
+
// 5.3.1 by `betterdisplaycli`: converging means bumping the user's own pin,
|
|
257
|
+
// which is an upgrade decision rather than a dedupe.
|
|
258
|
+
deepStrictEqual(clusterFixesFor("duplicated-printable-shell-command"), []);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("skips clusters whose members are all single-version", () => {
|
|
262
|
+
for (const fix of lockstepFixesFor("wildcard-not-reused")) {
|
|
263
|
+
ok(fix.duplicatedMembers.length > 0);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("detects the metro family the non-reused wildcard duplicated", () => {
|
|
268
|
+
const fix = metroCluster();
|
|
269
|
+
|
|
270
|
+
for (const member of [
|
|
271
|
+
"metro",
|
|
272
|
+
"metro-config",
|
|
273
|
+
"metro-transform-worker",
|
|
274
|
+
"ob1",
|
|
275
|
+
]) {
|
|
276
|
+
ok(fix.duplicatedMembers.includes(member), `expected ${member}`);
|
|
277
|
+
}
|
|
278
|
+
strictEqual(fix.duplicatedMembers.length, 16);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("keeps external constraints and drops the derived internal pins", () => {
|
|
282
|
+
const byRequester = metroCluster().externalConstraints.map(
|
|
283
|
+
(constraint) =>
|
|
284
|
+
`${constraint.requesterName ?? "workspace"} -> ${constraint.packageName} @ ${constraint.range}`,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
for (const expected of [
|
|
288
|
+
"workspace -> metro @ 0.84.5",
|
|
289
|
+
"@tamagui/metro-plugin -> metro-config @ *",
|
|
290
|
+
"@tamagui/metro-plugin -> metro-transform-worker @ *",
|
|
291
|
+
"react-native-web -> @react-native/normalize-colors @ ^0.74.1",
|
|
292
|
+
]) {
|
|
293
|
+
ok(byRequester.includes(expected), `expected constraint ${expected}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// metro's own `metro-config: 0.84.5` / `0.87.0` pins are internal: they
|
|
297
|
+
// exist only because metro resolved where it did.
|
|
298
|
+
const requesterNames = new Set(
|
|
299
|
+
metroCluster().externalConstraints.map(
|
|
300
|
+
(constraint) => constraint.requesterName,
|
|
301
|
+
),
|
|
302
|
+
);
|
|
303
|
+
strictEqual(requesterNames.has("metro"), false);
|
|
304
|
+
strictEqual(requesterNames.has("metro-config"), false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("converges the family on the pinned version, without touching the pin", () => {
|
|
308
|
+
const fix = metroCluster();
|
|
309
|
+
|
|
310
|
+
// The root pins metro at 0.84.5 on purpose, so the pin is a constraint to
|
|
311
|
+
// resolve around, not something to upgrade: the family collapses onto
|
|
312
|
+
// 0.84.5 and `workspaceChanges` stays empty.
|
|
313
|
+
strictEqual(fix.applicable, true);
|
|
314
|
+
strictEqual(fix.target, "0.84.5");
|
|
315
|
+
strictEqual(fix.direction, "down");
|
|
316
|
+
strictEqual(fix.convergentMembers.length, 15);
|
|
317
|
+
deepStrictEqual(fix.workspaceChanges, []);
|
|
318
|
+
|
|
319
|
+
// metro is the only member a real range applies to (the pin): the 14
|
|
320
|
+
// metro-* packages are requested through `*` or through metro's own exact
|
|
321
|
+
// pins, so they carry no decision and follow it.
|
|
322
|
+
deepStrictEqual(fix.driverMembers, ["metro"]);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("leaves react-native's version to the resolver", () => {
|
|
326
|
+
const fix = metroCluster();
|
|
327
|
+
|
|
328
|
+
// react-native@0.87.0 and @react-native/metro-config@0.87.0 are what hold
|
|
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
|
|
345
|
+
// `metro-config: "*"` / `metro-transform-worker: "*"`, and pnpm resolved
|
|
346
|
+
// both to 0.87.0 although the pinned 0.84.5 was already in the tree and the
|
|
347
|
+
// range accepts it — so the plugin runs against a different metro than the
|
|
348
|
+
// app's.
|
|
349
|
+
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
|
+
);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// `aliased-swapped-names`: the importer pins the real `typescript` at 7.0.2
|
|
382
|
+
// through the `@typescript/native` key while `tool` needs 5.9.3, and the
|
|
383
|
+
// `typescript` key holds an unrelated package. Nothing converges, and nothing
|
|
384
|
+
// may propose editing the declaration that names the other package.
|
|
385
|
+
it("leaves an exact pin declared under a swapped alias key alone", () => {
|
|
386
|
+
const fixes = clusterFixesFor("aliased-swapped-names", () => undefined);
|
|
387
|
+
deepStrictEqual(
|
|
388
|
+
fixes.map((fix) => fix.members),
|
|
389
|
+
[["typescript"]],
|
|
390
|
+
);
|
|
391
|
+
strictEqual(fixes[0]!.applicable, false);
|
|
392
|
+
deepStrictEqual(fixes[0]!.workspaceChanges, []);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// The same swapped keys, with `typescript` sitting in a lockstep family (its
|
|
396
|
+
// platform binaries) as it does from 7.0.0 on: the family makes the cluster
|
|
397
|
+
// big enough to escape the singleton guard above, and the pin is then the only
|
|
398
|
+
// thing standing between the dedupe and a rewrite of `"@typescript/native":
|
|
399
|
+
// "npm:typescript@7.0.2"` into `npm:typescript@6.0.3` — the alias repointed at
|
|
400
|
+
// the very package it exists to sit beside.
|
|
401
|
+
it("never repoints an alias to converge its family", () => {
|
|
402
|
+
const fix = clusterFixesFor(
|
|
403
|
+
"aliased-swapped-names-cluster",
|
|
404
|
+
(name, version) =>
|
|
405
|
+
name === "@typescript/typescript6" && version === "6.0.2"
|
|
406
|
+
? { dependencies: { typescript: "^6" } }
|
|
407
|
+
: undefined,
|
|
408
|
+
).find((candidate) => candidate.members.includes("typescript"));
|
|
409
|
+
|
|
410
|
+
ok(fix);
|
|
411
|
+
strictEqual(fix.members.length, 3);
|
|
412
|
+
deepStrictEqual(fix.duplicatedMembers, ["typescript"]);
|
|
413
|
+
strictEqual(fix.applicable, false);
|
|
414
|
+
strictEqual(fix.target, null);
|
|
415
|
+
deepStrictEqual(fix.workspaceChanges, []);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("reports a cluster no member can leave as not dedupable", () => {
|
|
419
|
+
// metro pins hermes-parser exactly at 0.35.0 and @react-native/* at 0.36.1,
|
|
420
|
+
// and hermes-estree only exists twice because hermes-parser does: nothing
|
|
421
|
+
// can collapse.
|
|
422
|
+
const fix = clusterFixesFor("wildcard-not-reused").find((candidate) =>
|
|
423
|
+
candidate.members.includes("hermes-parser"),
|
|
424
|
+
);
|
|
425
|
+
ok(fix);
|
|
426
|
+
strictEqual(fix.applicable, false);
|
|
427
|
+
strictEqual(fix.target, null);
|
|
428
|
+
deepStrictEqual(fix.convergentMembers, []);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildLockstepClusters,
|
|
3
|
+
identifyLockstepClusterFixes,
|
|
4
|
+
} from "pm-dedup-core";
|
|
5
|
+
import type {
|
|
6
|
+
ClusterDependentsMap,
|
|
7
|
+
ClusterFix,
|
|
8
|
+
ClusterMembersMap,
|
|
9
|
+
} from "pm-dedup-core";
|
|
10
|
+
import type { PackagesMap } from "./helpers/buildPnpmPackagesMap.ts";
|
|
11
|
+
import { collectDependentRanges } from "./helpers/collectDependentRanges.ts";
|
|
12
|
+
import type { ManifestReader } from "./helpers/readInstalledManifest.ts";
|
|
13
|
+
import { toLockstepGraph } from "./helpers/toLockstepGraph.ts";
|
|
14
|
+
import type { PnpmLockFile } from "./pnpmLockTypes.ts";
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
ClusterExternalConstraint,
|
|
18
|
+
ClusterFix,
|
|
19
|
+
ClusterReuseFix,
|
|
20
|
+
} from "pm-dedup-core";
|
|
21
|
+
|
|
22
|
+
// A lone duplicated package is a cluster of one: nothing co-versions with it,
|
|
23
|
+
// but it converges through the same mechanism — the dependents that hold its
|
|
24
|
+
// copies apart — and it is applied the same way, by an override the resolver
|
|
25
|
+
// then has to confirm.
|
|
26
|
+
const singletonClusters = (
|
|
27
|
+
packagesMap: PackagesMap,
|
|
28
|
+
clustered: Set<string>,
|
|
29
|
+
): string[][] =>
|
|
30
|
+
Object.entries(packagesMap)
|
|
31
|
+
.filter(
|
|
32
|
+
([name, resolutions]) =>
|
|
33
|
+
!clustered.has(name) &&
|
|
34
|
+
resolutions.filter((resolution) => resolution.package.type === "npm")
|
|
35
|
+
.length > 1,
|
|
36
|
+
)
|
|
37
|
+
.map(([name]) => [name]);
|
|
38
|
+
|
|
39
|
+
const toClusterMembers = (
|
|
40
|
+
packagesMap: PackagesMap,
|
|
41
|
+
memberNames: string[],
|
|
42
|
+
): ClusterMembersMap =>
|
|
43
|
+
Object.fromEntries(
|
|
44
|
+
memberNames.map((name) => {
|
|
45
|
+
const resolutions = packagesMap[name] ?? [];
|
|
46
|
+
return [
|
|
47
|
+
name,
|
|
48
|
+
{
|
|
49
|
+
npmVersions: resolutions
|
|
50
|
+
.filter((resolution) => resolution.package.type === "npm")
|
|
51
|
+
.map((resolution) => resolution.package.version),
|
|
52
|
+
resolutionCount: resolutions.length,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// The pnpm lockfile stores resolved versions, not requested ranges, so the
|
|
59
|
+
// constraints have to come from `collectDependentRanges`, which recovers the
|
|
60
|
+
// declared range from each requester's installed manifest.
|
|
61
|
+
const toClusterDependents = (
|
|
62
|
+
lock: PnpmLockFile,
|
|
63
|
+
memberNames: string[],
|
|
64
|
+
readManifest: ManifestReader,
|
|
65
|
+
): ClusterDependentsMap => {
|
|
66
|
+
const ranges = collectDependentRanges(
|
|
67
|
+
lock,
|
|
68
|
+
new Set(memberNames),
|
|
69
|
+
readManifest,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return new Map(
|
|
73
|
+
[...ranges].map(([name, dependents]) => [
|
|
74
|
+
name,
|
|
75
|
+
dependents.map((dependent) => ({
|
|
76
|
+
requester: dependent.key,
|
|
77
|
+
requesterName: dependent.requesterName,
|
|
78
|
+
range: dependent.range,
|
|
79
|
+
isAlias: dependent.aliasKey !== undefined,
|
|
80
|
+
resolvedVersion: dependent.resolvedVersion,
|
|
81
|
+
workspace: dependent.workspace,
|
|
82
|
+
})),
|
|
83
|
+
]),
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const identifyClusterFixes = (
|
|
88
|
+
lock: PnpmLockFile,
|
|
89
|
+
packagesMap: PackagesMap,
|
|
90
|
+
readManifest: ManifestReader,
|
|
91
|
+
): ClusterFix[] => {
|
|
92
|
+
const lockstepClusters = buildLockstepClusters(
|
|
93
|
+
toLockstepGraph(lock, packagesMap),
|
|
94
|
+
);
|
|
95
|
+
const clusters = [
|
|
96
|
+
...lockstepClusters,
|
|
97
|
+
...singletonClusters(packagesMap, new Set(lockstepClusters.flat())),
|
|
98
|
+
];
|
|
99
|
+
if (clusters.length === 0) return [];
|
|
100
|
+
|
|
101
|
+
const memberNames = [...new Set(clusters.flat())];
|
|
102
|
+
|
|
103
|
+
return identifyLockstepClusterFixes(
|
|
104
|
+
clusters,
|
|
105
|
+
toClusterMembers(packagesMap, memberNames),
|
|
106
|
+
toClusterDependents(lock, memberNames, readManifest),
|
|
107
|
+
// A lone duplicate that only converges by editing a range the workspace
|
|
108
|
+
// declares is an upgrade of a direct dependency, not a dedupe: that pin is
|
|
109
|
+
// the whole decision here, where in a family it is one constraint among
|
|
110
|
+
// many. Left to the user.
|
|
111
|
+
).filter(
|
|
112
|
+
(fix) => fix.members.length > 1 || fix.workspaceChanges.length === 0,
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { identifyResolutionFixes } from "pm-dedup-core";
|
|
5
|
+
import type { PackageResolution } from "./helpers/buildPnpmPackagesMap.ts";
|
|
6
|
+
import type { Dependent } from "./helpers/collectPnpmDependents.ts";
|
|
7
|
+
|
|
8
|
+
const loadResolutionsFixture = (fileName: string): PackageResolution[] => {
|
|
9
|
+
return JSON.parse(
|
|
10
|
+
fs.readFileSync(
|
|
11
|
+
fileURLToPath(
|
|
12
|
+
new URL(`../test/fixtures/resolutions/${fileName}`, import.meta.url),
|
|
13
|
+
),
|
|
14
|
+
// eslint-disable-next-line unicorn/prefer-json-parse-buffer
|
|
15
|
+
"utf8",
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const loadDependentsFixture = (
|
|
21
|
+
fileName: string,
|
|
22
|
+
): Record<string, Dependent[]> => {
|
|
23
|
+
return JSON.parse(
|
|
24
|
+
fs.readFileSync(
|
|
25
|
+
fileURLToPath(
|
|
26
|
+
new URL(`../test/fixtures/dependents/${fileName}`, import.meta.url),
|
|
27
|
+
),
|
|
28
|
+
// eslint-disable-next-line unicorn/prefer-json-parse-buffer
|
|
29
|
+
"utf8",
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const objetToMap = <K extends string, V>(obj: Record<K, V>): Map<K, V> =>
|
|
35
|
+
new Map(Object.entries(obj) as [K, V][]);
|
|
36
|
+
|
|
37
|
+
describe("identifyResolutionFixes", () => {
|
|
38
|
+
it("should return an empty array when there are no resolutions", () => {
|
|
39
|
+
const resolutions: PackageResolution[] = [];
|
|
40
|
+
|
|
41
|
+
const fixes = identifyResolutionFixes(resolutions, objetToMap({}));
|
|
42
|
+
expect(fixes).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should return an empty array when there is only one resolution", () => {
|
|
46
|
+
const resolutions = loadResolutionsFixture("semver-7.7.3.json");
|
|
47
|
+
const dependents = loadDependentsFixture("semver-7.7.3.json");
|
|
48
|
+
const fixes = identifyResolutionFixes(resolutions, objetToMap(dependents));
|
|
49
|
+
expect(fixes).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should not identify fixes when dependencies are not compatible", () => {
|
|
53
|
+
const resolutions = loadResolutionsFixture(
|
|
54
|
+
"babel-code-frame-7.26.2-7.27.1.json",
|
|
55
|
+
);
|
|
56
|
+
const dependents = loadDependentsFixture(
|
|
57
|
+
"babel-code-frame-7.26.2-7.27.1.json",
|
|
58
|
+
);
|
|
59
|
+
const fixes = identifyResolutionFixes(resolutions, objetToMap(dependents));
|
|
60
|
+
expect(fixes).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should identify resolution fixes when dependencies are compatible", () => {
|
|
64
|
+
const resolutions = loadResolutionsFixture(
|
|
65
|
+
"printable-shell-command-5.0.7-5.0.8.json",
|
|
66
|
+
);
|
|
67
|
+
const dependents = loadDependentsFixture(
|
|
68
|
+
"printable-shell-command-5.0.7-5.0.8.json",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const fixes = identifyResolutionFixes(resolutions, objetToMap(dependents));
|
|
72
|
+
expect(fixes).toEqual([
|
|
73
|
+
{
|
|
74
|
+
mergeableResolutions: [
|
|
75
|
+
"printable-shell-command@5.0.7",
|
|
76
|
+
"printable-shell-command@5.0.8",
|
|
77
|
+
],
|
|
78
|
+
to: "printable-shell-command@5.0.8",
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
});
|