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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pnpm-dedup",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "List duplicates and dedupe pnpm lock file",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dedupe",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"src",
|
|
28
|
-
"dist"
|
|
28
|
+
"dist",
|
|
29
|
+
"bin"
|
|
29
30
|
],
|
|
30
31
|
"type": "module",
|
|
31
32
|
"sideEffects": false,
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"tsc": "tsc"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"pm-dedup-core": "2.1
|
|
53
|
+
"pm-dedup-core": "2.2.1",
|
|
53
54
|
"semver": "7.8.5",
|
|
54
55
|
"tslib": "^2.8.1",
|
|
55
56
|
"yaml": "2.9.1"
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { afterEach, describe, it } from "bun:test";
|
|
2
1
|
import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
|
|
3
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, it } from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ClusterFix,
|
|
8
|
+
DuplicateSnapshot,
|
|
9
|
+
VersionsSnapshot,
|
|
10
|
+
} from "pm-dedup-core";
|
|
7
11
|
import { applyClusterFixes } from "./applyClusterFixes.ts";
|
|
12
|
+
import type { DependentRangesMap } from "./helpers/collectDependentRanges.ts";
|
|
8
13
|
import { createTempProjects } from "./helpers/tempProjects.ts";
|
|
9
14
|
|
|
10
15
|
const fix = (overrides: Partial<ClusterFix>): ClusterFix => ({
|
|
@@ -18,7 +23,6 @@ const fix = (overrides: Partial<ClusterFix>): ClusterFix => ({
|
|
|
18
23
|
excludedMembers: [],
|
|
19
24
|
anchor: null,
|
|
20
25
|
reuseFixes: [],
|
|
21
|
-
floatingMembers: [],
|
|
22
26
|
workspaceChanges: [],
|
|
23
27
|
reResolutionSet: [],
|
|
24
28
|
externalConstraints: [],
|
|
@@ -145,16 +149,309 @@ describe("applyClusterFixes", () => {
|
|
|
145
149
|
ok(logs.some((line) => line.includes("Removing the overrides")));
|
|
146
150
|
});
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
describe("when some overrides do not hold", () => {
|
|
153
|
+
const familyFix = (...convergentMembers: string[]): ClusterFix =>
|
|
154
|
+
fix({ applicable: true, target: "2.0.0", convergentMembers });
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* pnpm as far as the override step can tell, its state kept in the lockfile
|
|
158
|
+
* so that restoring files restores it too. A package with an override in
|
|
159
|
+
* `pnpm-workspace.yaml` merges onto 2.0.0; without one, a merged package
|
|
160
|
+
* stays merged only if `stays` says so, given what was merged before.
|
|
161
|
+
*/
|
|
162
|
+
const simulatePnpm = (
|
|
163
|
+
dir: string,
|
|
164
|
+
packages: string[],
|
|
165
|
+
stays: Record<string, (merged: Set<string>) => boolean>,
|
|
166
|
+
) => {
|
|
167
|
+
const mergedNow = (): Set<string> =>
|
|
168
|
+
new Set(
|
|
169
|
+
/# merged: (.*)/
|
|
170
|
+
.exec(read(dir, "pnpm-lock.yaml"))?.[1]
|
|
171
|
+
?.split(",")
|
|
172
|
+
.filter(Boolean),
|
|
173
|
+
);
|
|
174
|
+
return {
|
|
175
|
+
resolve: (): number => {
|
|
176
|
+
const workspaceYaml = read(dir, "pnpm-workspace.yaml");
|
|
177
|
+
const previous = mergedNow();
|
|
178
|
+
const merged = packages.filter(
|
|
179
|
+
(name) =>
|
|
180
|
+
new RegExp(`"${name}@?":`).test(workspaceYaml) ||
|
|
181
|
+
(previous.has(name) && (stays[name]?.(previous) ?? false)),
|
|
182
|
+
);
|
|
183
|
+
writeFileSync(
|
|
184
|
+
join(dir, "pnpm-lock.yaml"),
|
|
185
|
+
`lockfileVersion: '9.0'\n# merged: ${merged.join(",")}\n`,
|
|
186
|
+
);
|
|
187
|
+
return 0;
|
|
188
|
+
},
|
|
189
|
+
readDuplicates: (): DuplicateSnapshot => {
|
|
190
|
+
const merged = mergedNow();
|
|
191
|
+
return new Set(
|
|
192
|
+
packages.flatMap((name) =>
|
|
193
|
+
merged.has(name) ? [] : [`${name}@1.0.0`, `${name}@2.0.0`],
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
readVersions: (): VersionsSnapshot => {
|
|
198
|
+
const merged = mergedNow();
|
|
199
|
+
return new Map(
|
|
200
|
+
packages.map((name) => [
|
|
201
|
+
name,
|
|
202
|
+
merged.has(name) ? ["2.0.0"] : ["1.0.0", "2.0.0"],
|
|
203
|
+
]),
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const branchDependent = (name: string): DependentRangesMap =>
|
|
210
|
+
new Map([
|
|
211
|
+
[
|
|
212
|
+
name,
|
|
213
|
+
[
|
|
214
|
+
{
|
|
215
|
+
key: "branch@1.0.0",
|
|
216
|
+
range: "^1.0.0",
|
|
217
|
+
resolvedVersion: "1.0.0",
|
|
218
|
+
requesterName: "branch",
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
],
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
const sticky = (): boolean => true;
|
|
225
|
+
const stickyWith =
|
|
226
|
+
(companion: string) =>
|
|
227
|
+
(merged: Set<string>): boolean =>
|
|
228
|
+
merged.has(companion);
|
|
229
|
+
|
|
230
|
+
const run = (
|
|
231
|
+
dir: string,
|
|
232
|
+
fixes: ClusterFix[],
|
|
233
|
+
pnpm: ReturnType<typeof simulatePnpm>,
|
|
234
|
+
logs: string[],
|
|
235
|
+
dependentRanges: DependentRangesMap = new Map(),
|
|
236
|
+
) =>
|
|
237
|
+
applyClusterFixes({
|
|
238
|
+
projectDir: dir,
|
|
239
|
+
color: false,
|
|
240
|
+
log: (message = "") => logs.push(message),
|
|
241
|
+
pnpmVersion: () => "11.17.0",
|
|
242
|
+
readFixes: () => fixes,
|
|
243
|
+
readDependentRanges: () => dependentRanges,
|
|
244
|
+
...pnpm,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const projectFiles = {
|
|
248
|
+
"package.json": manifestContent,
|
|
249
|
+
"pnpm-lock.yaml": "lockfileVersion: '9.0'\n",
|
|
250
|
+
"pnpm-workspace.yaml": workspaceYamlContent,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const addedIn = (logs: string[], from: number): string[] =>
|
|
254
|
+
logs.slice(from).filter((line) => /^ {2}"\w+@": "2\.0\.0"/.test(line));
|
|
255
|
+
|
|
256
|
+
it("keeps only the overrides that hold, and leaves none behind", () => {
|
|
257
|
+
const dir = makeProject(projectFiles);
|
|
258
|
+
const logs: string[] = [];
|
|
259
|
+
const pnpm = simulatePnpm(dir, ["leaf", "twig"], { leaf: sticky });
|
|
260
|
+
|
|
261
|
+
const outcome = run(
|
|
262
|
+
dir,
|
|
263
|
+
[familyFix("leaf", "twig")],
|
|
264
|
+
pnpm,
|
|
265
|
+
logs,
|
|
266
|
+
branchDependent("twig"),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
strictEqual(outcome.status, "applied");
|
|
270
|
+
deepStrictEqual([...outcome.after], ["twig@1.0.0", "twig@2.0.0"]);
|
|
271
|
+
deepStrictEqual(
|
|
272
|
+
outcome.stickyOverrides.map((override) => override.packageName),
|
|
273
|
+
["twig"],
|
|
274
|
+
);
|
|
275
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
276
|
+
|
|
277
|
+
const retry = logs.indexOf("Retrying with only the overrides that held:");
|
|
278
|
+
ok(retry > 0);
|
|
279
|
+
deepStrictEqual(addedIn(logs, retry), [' "leaf@": "2.0.0" (converge)']);
|
|
280
|
+
ok(
|
|
281
|
+
logs.includes(
|
|
282
|
+
"pnpm resolves these back without an override, so they are not merged:",
|
|
283
|
+
),
|
|
284
|
+
);
|
|
285
|
+
ok(logs.includes(" twig onto 2.0.0:"));
|
|
286
|
+
ok(logs.includes(' branch@1.0.0 requires "^1.0.0"'));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("reverts and says so when an override does not merge even while present", () => {
|
|
290
|
+
const dir = makeProject(projectFiles);
|
|
291
|
+
const logs: string[] = [];
|
|
292
|
+
const pnpm = simulatePnpm(dir, ["leaf"], {});
|
|
293
|
+
// pnpm ignores the override: the declared range rejects the version
|
|
294
|
+
const inert = { ...pnpm, resolve: () => 0 };
|
|
295
|
+
|
|
296
|
+
const outcome = run(
|
|
297
|
+
dir,
|
|
298
|
+
[familyFix("leaf")],
|
|
299
|
+
inert,
|
|
300
|
+
logs,
|
|
301
|
+
branchDependent("leaf"),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
strictEqual(outcome.status, "reverted");
|
|
305
|
+
deepStrictEqual(
|
|
306
|
+
outcome.stickyOverrides.map((override) => override.packageName),
|
|
307
|
+
["leaf"],
|
|
308
|
+
);
|
|
309
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
310
|
+
ok(logs.includes("Not merged even with an override:"));
|
|
311
|
+
ok(logs.includes(" No override holds on its own — reverting them"));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("keeps the range edits when no override holds", () => {
|
|
315
|
+
const dir = makeProject(projectFiles);
|
|
316
|
+
const logs: string[] = [];
|
|
317
|
+
const pnpm = simulatePnpm(dir, ["metro-config"], {});
|
|
318
|
+
|
|
319
|
+
const outcome = run(dir, [metroFix], pnpm, logs);
|
|
320
|
+
|
|
321
|
+
strictEqual(outcome.status, "applied");
|
|
322
|
+
ok(read(dir, "package.json").includes('"metro": "0.87.0"'));
|
|
323
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("drops an override that only held alongside one a round lost", () => {
|
|
327
|
+
const dir = makeProject(projectFiles);
|
|
328
|
+
const logs: string[] = [];
|
|
329
|
+
const pnpm = simulatePnpm(dir, ["leaf", "twig", "bud"], {
|
|
330
|
+
leaf: sticky,
|
|
331
|
+
twig: stickyWith("bud"),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const outcome = run(dir, [familyFix("leaf", "twig", "bud")], pnpm, logs);
|
|
335
|
+
|
|
336
|
+
strictEqual(outcome.status, "applied");
|
|
337
|
+
deepStrictEqual(
|
|
338
|
+
outcome.stickyOverrides.map((override) => override.packageName),
|
|
339
|
+
["bud", "twig"],
|
|
340
|
+
);
|
|
341
|
+
deepStrictEqual(
|
|
342
|
+
addedIn(
|
|
343
|
+
logs,
|
|
344
|
+
logs.lastIndexOf("Retrying with only the overrides that held:"),
|
|
345
|
+
),
|
|
346
|
+
[' "leaf@": "2.0.0" (converge)'],
|
|
347
|
+
);
|
|
348
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("gives up after three rounds", () => {
|
|
352
|
+
const dir = makeProject(projectFiles);
|
|
353
|
+
const logs: string[] = [];
|
|
354
|
+
const pnpm = simulatePnpm(dir, ["leaf", "twig", "bud", "seed"], {
|
|
355
|
+
leaf: sticky,
|
|
356
|
+
twig: stickyWith("bud"),
|
|
357
|
+
bud: stickyWith("seed"),
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const outcome = run(
|
|
361
|
+
dir,
|
|
362
|
+
[familyFix("leaf", "twig", "bud", "seed")],
|
|
363
|
+
pnpm,
|
|
364
|
+
logs,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
strictEqual(outcome.status, "reverted");
|
|
368
|
+
strictEqual(outcome.stickyOverrides.length, 4);
|
|
369
|
+
strictEqual(logs.filter((line) => line.startsWith("Retrying")).length, 2);
|
|
370
|
+
ok(
|
|
371
|
+
logs.includes(
|
|
372
|
+
" Overrides still came apart after 3 rounds — reverting them",
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
376
|
+
deepStrictEqual(outcome.after, outcome.before);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("keeps the same rule with plain overrides", () => {
|
|
380
|
+
const dir = makeProject(projectFiles);
|
|
381
|
+
const logs: string[] = [];
|
|
382
|
+
const pnpm = simulatePnpm(dir, ["leaf", "twig"], { leaf: sticky });
|
|
383
|
+
|
|
384
|
+
const outcome = applyClusterFixes({
|
|
385
|
+
projectDir: dir,
|
|
386
|
+
color: false,
|
|
387
|
+
log: (message = "") => logs.push(message),
|
|
388
|
+
convergenceOverrides: false,
|
|
389
|
+
readFixes: () => [familyFix("leaf", "twig")],
|
|
390
|
+
readDependentRanges: () => new Map(),
|
|
391
|
+
...pnpm,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
strictEqual(outcome.status, "applied");
|
|
395
|
+
deepStrictEqual(
|
|
396
|
+
outcome.stickyOverrides.map((override) => override.packageName),
|
|
397
|
+
["twig"],
|
|
398
|
+
);
|
|
399
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("writes the override for a member nothing duplicates that holds its family apart", () => {
|
|
149
404
|
const dir = makeProject({
|
|
150
405
|
"package.json": manifestContent,
|
|
151
406
|
"pnpm-lock.yaml": "lockfileVersion: '9.0'\n",
|
|
152
407
|
"pnpm-workspace.yaml": workspaceYamlContent,
|
|
153
408
|
});
|
|
409
|
+
const logs: string[] = [];
|
|
410
|
+
let parentMoved = false;
|
|
154
411
|
|
|
155
|
-
const
|
|
156
|
-
|
|
412
|
+
const outcome = applyClusterFixes({
|
|
413
|
+
projectDir: dir,
|
|
414
|
+
color: false,
|
|
415
|
+
log: (message = "") => logs.push(message),
|
|
416
|
+
pnpmVersion: () => "11.17.0",
|
|
417
|
+
readFixes: () => [
|
|
418
|
+
fix({
|
|
419
|
+
applicable: true,
|
|
420
|
+
target: "2.0.0",
|
|
421
|
+
convergentMembers: ["leaf"],
|
|
422
|
+
reResolutionSet: ["parent"],
|
|
423
|
+
}),
|
|
424
|
+
],
|
|
425
|
+
readDuplicates: () =>
|
|
426
|
+
parentMoved ? snapshot() : snapshot("leaf@1.0.0", "leaf@2.0.0"),
|
|
427
|
+
readVersions: () =>
|
|
428
|
+
new Map([
|
|
429
|
+
["leaf", parentMoved ? ["2.0.0"] : ["1.0.0", "2.0.0"]],
|
|
430
|
+
["parent", [parentMoved ? "2.0.0" : "1.0.0"]],
|
|
431
|
+
]),
|
|
432
|
+
resolve: () => {
|
|
433
|
+
// parent pins leaf exactly: the family only merges once parent moves
|
|
434
|
+
if (read(dir, "pnpm-workspace.yaml").includes('"parent@"')) {
|
|
435
|
+
parentMoved = true;
|
|
436
|
+
}
|
|
437
|
+
return 0;
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
strictEqual(outcome.status, "applied");
|
|
442
|
+
deepStrictEqual(outcome.stickyOverrides, []);
|
|
443
|
+
ok(logs.includes(' "parent@": "2.0.0" (converge)'));
|
|
444
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("reverts when the result without overrides adds a duplicate", () => {
|
|
448
|
+
const dir = makeProject({
|
|
449
|
+
"package.json": manifestContent,
|
|
450
|
+
"pnpm-lock.yaml": "lockfileVersion: '9.0'\n",
|
|
451
|
+
"pnpm-workspace.yaml": workspaceYamlContent,
|
|
452
|
+
});
|
|
157
453
|
const logs: string[] = [];
|
|
454
|
+
let duplicates = snapshot("leaf@1.0.0", "leaf@2.0.0");
|
|
158
455
|
|
|
159
456
|
const outcome = applyClusterFixes({
|
|
160
457
|
projectDir: dir,
|
|
@@ -164,34 +461,20 @@ describe("applyClusterFixes", () => {
|
|
|
164
461
|
readFixes: () => [leafFix],
|
|
165
462
|
readDuplicates: () => duplicates,
|
|
166
463
|
resolve: () => {
|
|
167
|
-
// the duplicate only stays away while the override is there
|
|
168
464
|
duplicates = read(dir, "pnpm-workspace.yaml").includes('"leaf@"')
|
|
169
465
|
? snapshot()
|
|
170
|
-
:
|
|
466
|
+
: snapshot("leaf@1.0.0", "leaf@2.0.0", "other@1.0.0", "other@2.0.0");
|
|
171
467
|
return 0;
|
|
172
468
|
},
|
|
173
469
|
});
|
|
174
470
|
|
|
175
|
-
strictEqual(outcome.status, "
|
|
176
|
-
deepStrictEqual(
|
|
177
|
-
outcome.stickyOverrides.map((override) => override.packageName),
|
|
178
|
-
["leaf"],
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const workspaceYaml = read(dir, "pnpm-workspace.yaml");
|
|
182
|
-
ok(workspaceYaml.includes('"leaf@": "2.0.0"'));
|
|
183
|
-
ok(workspaceYaml.includes("# Added by pnpm-dedup."));
|
|
471
|
+
strictEqual(outcome.status, "reverted");
|
|
184
472
|
ok(
|
|
185
|
-
|
|
186
|
-
"
|
|
473
|
+
logs.includes(
|
|
474
|
+
" removing the overrides introduced 2 new duplicate(s) — reverting",
|
|
187
475
|
),
|
|
188
476
|
);
|
|
189
|
-
|
|
190
|
-
ok(workspaceYaml.includes("# keep me"));
|
|
191
|
-
ok(workspaceYaml.includes("resolutionMode: time-based"));
|
|
192
|
-
|
|
193
|
-
ok(logs.some((line) => line.includes("github.com/christophehurpeau")));
|
|
194
|
-
ok(logs.some((line) => line.includes("Kept in")));
|
|
477
|
+
strictEqual(read(dir, "pnpm-workspace.yaml"), workspaceYamlContent);
|
|
195
478
|
});
|
|
196
479
|
|
|
197
480
|
it("writes plain overrides and skips the version gate when convergence is disabled", () => {
|
|
@@ -244,17 +527,9 @@ describe("applyClusterFixes", () => {
|
|
|
244
527
|
pnpmVersion: () => "11.17.0",
|
|
245
528
|
readFixes: () => [
|
|
246
529
|
fix({
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
requester: "@tamagui/metro-plugin@1.0.0",
|
|
251
|
-
requesterName: "@tamagui/metro-plugin",
|
|
252
|
-
packageName: "metro-config",
|
|
253
|
-
range: "*",
|
|
254
|
-
from: "0.87.0",
|
|
255
|
-
to: "0.84.5",
|
|
256
|
-
},
|
|
257
|
-
],
|
|
530
|
+
applicable: true,
|
|
531
|
+
target: "0.84.5",
|
|
532
|
+
convergentMembers: ["metro-config"],
|
|
258
533
|
externalConstraints: [
|
|
259
534
|
{
|
|
260
535
|
requester: "@react-native/community-cli-plugin@0.87.0",
|
|
@@ -379,6 +654,93 @@ describe("applyClusterFixes", () => {
|
|
|
379
654
|
ok(outcome.plannedChangeCount > 0);
|
|
380
655
|
});
|
|
381
656
|
|
|
657
|
+
describe("an open range that ignored the pinned version", () => {
|
|
658
|
+
const reuseFix = (
|
|
659
|
+
requesterName: string,
|
|
660
|
+
packageName: string,
|
|
661
|
+
range: string,
|
|
662
|
+
from: string,
|
|
663
|
+
to: string,
|
|
664
|
+
): ClusterFix =>
|
|
665
|
+
fix({
|
|
666
|
+
anchor: to,
|
|
667
|
+
reuseFixes: [
|
|
668
|
+
{
|
|
669
|
+
requester: `${requesterName}@1.0.0`,
|
|
670
|
+
requesterName,
|
|
671
|
+
packageName,
|
|
672
|
+
range,
|
|
673
|
+
from,
|
|
674
|
+
to,
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
});
|
|
678
|
+
// the metro fixture's wildcard, and alouette's peer
|
|
679
|
+
const reuseFixes = [
|
|
680
|
+
reuseFix(
|
|
681
|
+
"@tamagui/metro-plugin",
|
|
682
|
+
"metro-config",
|
|
683
|
+
"*",
|
|
684
|
+
"0.87.0",
|
|
685
|
+
"0.84.5",
|
|
686
|
+
),
|
|
687
|
+
reuseFix(
|
|
688
|
+
"react-native-css",
|
|
689
|
+
"lightningcss",
|
|
690
|
+
">=1.27.0",
|
|
691
|
+
"1.33.0",
|
|
692
|
+
"1.30.1",
|
|
693
|
+
),
|
|
694
|
+
];
|
|
695
|
+
|
|
696
|
+
const planFor = (fixes: ClusterFix[]) => {
|
|
697
|
+
const dir = makeProject({
|
|
698
|
+
"package.json": manifestContent,
|
|
699
|
+
"pnpm-lock.yaml": "lockfileVersion: '9.0'\n",
|
|
700
|
+
});
|
|
701
|
+
const logs: string[] = [];
|
|
702
|
+
const outcome = applyClusterFixes({
|
|
703
|
+
projectDir: dir,
|
|
704
|
+
color: false,
|
|
705
|
+
dryRun: true,
|
|
706
|
+
log: (message = "") => logs.push(message),
|
|
707
|
+
pnpmVersion: () => "11.17.0",
|
|
708
|
+
readFixes: () => fixes,
|
|
709
|
+
readDuplicates: () => snapshot(),
|
|
710
|
+
resolve: () => {
|
|
711
|
+
throw new Error("a dry run must not resolve");
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
return { outcome, logs };
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
it("plans nothing for them: pnpm resolves them back to the highest copy", () => {
|
|
718
|
+
const { outcome, logs } = planFor(reuseFixes);
|
|
719
|
+
|
|
720
|
+
// `--check` has nothing to fail on, run after run
|
|
721
|
+
strictEqual(outcome.plannedChangeCount, 0);
|
|
722
|
+
ok(!logs.some((line) => line.includes("(reuse)")));
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it("lets the family's own target through instead", () => {
|
|
726
|
+
const { logs } = planFor([
|
|
727
|
+
fix({
|
|
728
|
+
...reuseFixes[0]!,
|
|
729
|
+
applicable: true,
|
|
730
|
+
target: "0.87.0",
|
|
731
|
+
convergentMembers: ["metro-config"],
|
|
732
|
+
}),
|
|
733
|
+
]);
|
|
734
|
+
|
|
735
|
+
ok(
|
|
736
|
+
logs.some((line) =>
|
|
737
|
+
line.includes('"metro-config@": "0.87.0" (converge)'),
|
|
738
|
+
),
|
|
739
|
+
);
|
|
740
|
+
ok(!logs.some((line) => line.includes("ignoring")));
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
|
|
382
744
|
it("renders repo-relative paths and the transient override file", () => {
|
|
383
745
|
const dir = makeProject({
|
|
384
746
|
"package.json": manifestContent,
|