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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +218 -0
  3. package/dist/applyClusterFixes.js +252 -0
  4. package/dist/applyClusterFixes.js.map +1 -0
  5. package/dist/applyClusterFixes.test.js +474 -0
  6. package/dist/applyClusterFixes.test.js.map +1 -0
  7. package/dist/bin/pnpm-dedupe.js +35 -0
  8. package/dist/bin/pnpm-dedupe.js.map +1 -0
  9. package/dist/bin/pnpm-why-duplicate.js +23 -0
  10. package/dist/bin/pnpm-why-duplicate.js.map +1 -0
  11. package/dist/bin/pnpm-why-duplicate.test.js +57 -0
  12. package/dist/bin/pnpm-why-duplicate.test.js.map +1 -0
  13. package/dist/dedupe.js +86 -0
  14. package/dist/dedupe.js.map +1 -0
  15. package/dist/dedupeCheckConsistency.test.js +149 -0
  16. package/dist/dedupeCheckConsistency.test.js.map +1 -0
  17. package/dist/dedupeEndToEnd.test.js +121 -0
  18. package/dist/dedupeEndToEnd.test.js.map +1 -0
  19. package/dist/dedupePeers.test.js +105 -0
  20. package/dist/dedupePeers.test.js.map +1 -0
  21. package/dist/definitions/applyClusterFixes.d.ts +24 -0
  22. package/dist/definitions/applyClusterFixes.d.ts.map +1 -0
  23. package/dist/definitions/applyClusterFixes.test.d.ts +2 -0
  24. package/dist/definitions/applyClusterFixes.test.d.ts.map +1 -0
  25. package/dist/definitions/bin/pnpm-dedupe.d.ts +3 -0
  26. package/dist/definitions/bin/pnpm-dedupe.d.ts.map +1 -0
  27. package/dist/definitions/bin/pnpm-why-duplicate.d.ts +3 -0
  28. package/dist/definitions/bin/pnpm-why-duplicate.d.ts.map +1 -0
  29. package/dist/definitions/bin/pnpm-why-duplicate.test.d.ts +2 -0
  30. package/dist/definitions/bin/pnpm-why-duplicate.test.d.ts.map +1 -0
  31. package/dist/definitions/dedupe.d.ts +15 -0
  32. package/dist/definitions/dedupe.d.ts.map +1 -0
  33. package/dist/definitions/dedupeCheckConsistency.test.d.ts +2 -0
  34. package/dist/definitions/dedupeCheckConsistency.test.d.ts.map +1 -0
  35. package/dist/definitions/dedupeEndToEnd.test.d.ts +2 -0
  36. package/dist/definitions/dedupeEndToEnd.test.d.ts.map +1 -0
  37. package/dist/definitions/dedupePeers.test.d.ts +2 -0
  38. package/dist/definitions/dedupePeers.test.d.ts.map +1 -0
  39. package/dist/definitions/displayMany.d.ts +16 -0
  40. package/dist/definitions/displayMany.d.ts.map +1 -0
  41. package/dist/definitions/displayMany.test.d.ts +2 -0
  42. package/dist/definitions/displayMany.test.d.ts.map +1 -0
  43. package/dist/definitions/helpers/buildIdentifiedFixesMap.test.d.ts +2 -0
  44. package/dist/definitions/helpers/buildIdentifiedFixesMap.test.d.ts.map +1 -0
  45. package/dist/definitions/helpers/buildPnpmPackagesMap.d.ts +13 -0
  46. package/dist/definitions/helpers/buildPnpmPackagesMap.d.ts.map +1 -0
  47. package/dist/definitions/helpers/buildPnpmPackagesMap.test.d.ts +2 -0
  48. package/dist/definitions/helpers/buildPnpmPackagesMap.test.d.ts.map +1 -0
  49. package/dist/definitions/helpers/collectDependentRanges.d.ts +22 -0
  50. package/dist/definitions/helpers/collectDependentRanges.d.ts.map +1 -0
  51. package/dist/definitions/helpers/collectDependentRanges.test.d.ts +2 -0
  52. package/dist/definitions/helpers/collectDependentRanges.test.d.ts.map +1 -0
  53. package/dist/definitions/helpers/collectPnpmDependents.d.ts +8 -0
  54. package/dist/definitions/helpers/collectPnpmDependents.d.ts.map +1 -0
  55. package/dist/definitions/helpers/collectPnpmDependents.test.d.ts +2 -0
  56. package/dist/definitions/helpers/collectPnpmDependents.test.d.ts.map +1 -0
  57. package/dist/definitions/helpers/duplicateSnapshot.d.ts +3 -0
  58. package/dist/definitions/helpers/duplicateSnapshot.d.ts.map +1 -0
  59. package/dist/definitions/helpers/parsePnpmLockPackages.d.ts +44 -0
  60. package/dist/definitions/helpers/parsePnpmLockPackages.d.ts.map +1 -0
  61. package/dist/definitions/helpers/parsePnpmLockPackages.test.d.ts +2 -0
  62. package/dist/definitions/helpers/parsePnpmLockPackages.test.d.ts.map +1 -0
  63. package/dist/definitions/helpers/pnpmProtocol.d.ts +2 -0
  64. package/dist/definitions/helpers/pnpmProtocol.d.ts.map +1 -0
  65. package/dist/definitions/helpers/pnpmVersion.d.ts +11 -0
  66. package/dist/definitions/helpers/pnpmVersion.d.ts.map +1 -0
  67. package/dist/definitions/helpers/pnpmWorkspaceYaml.d.ts +23 -0
  68. package/dist/definitions/helpers/pnpmWorkspaceYaml.d.ts.map +1 -0
  69. package/dist/definitions/helpers/pnpmWorkspaceYaml.test.d.ts +2 -0
  70. package/dist/definitions/helpers/pnpmWorkspaceYaml.test.d.ts.map +1 -0
  71. package/dist/definitions/helpers/projectDir.d.ts +3 -0
  72. package/dist/definitions/helpers/projectDir.d.ts.map +1 -0
  73. package/dist/definitions/helpers/readInstalledManifest.d.ts +32 -0
  74. package/dist/definitions/helpers/readInstalledManifest.d.ts.map +1 -0
  75. package/dist/definitions/helpers/readInstalledManifest.test.d.ts +2 -0
  76. package/dist/definitions/helpers/readInstalledManifest.test.d.ts.map +1 -0
  77. package/dist/definitions/helpers/runPnpm.d.ts +11 -0
  78. package/dist/definitions/helpers/runPnpm.d.ts.map +1 -0
  79. package/dist/definitions/helpers/tempProjects.d.ts +8 -0
  80. package/dist/definitions/helpers/tempProjects.d.ts.map +1 -0
  81. package/dist/definitions/helpers/toLockstepGraph.d.ts +12 -0
  82. package/dist/definitions/helpers/toLockstepGraph.d.ts.map +1 -0
  83. package/dist/definitions/helpers/toLockstepGraph.test.d.ts +2 -0
  84. package/dist/definitions/helpers/toLockstepGraph.test.d.ts.map +1 -0
  85. package/dist/definitions/helpers/versionsSnapshot.d.ts +3 -0
  86. package/dist/definitions/helpers/versionsSnapshot.d.ts.map +1 -0
  87. package/dist/definitions/identifyClusterFixes.d.ts +7 -0
  88. package/dist/definitions/identifyClusterFixes.d.ts.map +1 -0
  89. package/dist/definitions/identifyClusterFixes.test.d.ts +2 -0
  90. package/dist/definitions/identifyClusterFixes.test.d.ts.map +1 -0
  91. package/dist/definitions/identifyResolutionFixes.test.d.ts +2 -0
  92. package/dist/definitions/identifyResolutionFixes.test.d.ts.map +1 -0
  93. package/dist/definitions/index.d.ts +25 -0
  94. package/dist/definitions/index.d.ts.map +1 -0
  95. package/dist/definitions/pnpmLockTypes.d.ts +42 -0
  96. package/dist/definitions/pnpmLockTypes.d.ts.map +1 -0
  97. package/dist/definitions/readPnpmLock.d.ts +3 -0
  98. package/dist/definitions/readPnpmLock.d.ts.map +1 -0
  99. package/dist/displayMany.js +56 -0
  100. package/dist/displayMany.js.map +1 -0
  101. package/dist/displayMany.test.js +146 -0
  102. package/dist/displayMany.test.js.map +1 -0
  103. package/dist/helpers/buildIdentifiedFixesMap.test.js +27 -0
  104. package/dist/helpers/buildIdentifiedFixesMap.test.js.map +1 -0
  105. package/dist/helpers/buildPnpmPackagesMap.js +23 -0
  106. package/dist/helpers/buildPnpmPackagesMap.js.map +1 -0
  107. package/dist/helpers/buildPnpmPackagesMap.test.js +68 -0
  108. package/dist/helpers/buildPnpmPackagesMap.test.js.map +1 -0
  109. package/dist/helpers/collectDependentRanges.js +118 -0
  110. package/dist/helpers/collectDependentRanges.js.map +1 -0
  111. package/dist/helpers/collectDependentRanges.test.js +211 -0
  112. package/dist/helpers/collectDependentRanges.test.js.map +1 -0
  113. package/dist/helpers/collectPnpmDependents.js +55 -0
  114. package/dist/helpers/collectPnpmDependents.js.map +1 -0
  115. package/dist/helpers/collectPnpmDependents.test.js +78 -0
  116. package/dist/helpers/collectPnpmDependents.test.js.map +1 -0
  117. package/dist/helpers/duplicateSnapshot.js +5 -0
  118. package/dist/helpers/duplicateSnapshot.js.map +1 -0
  119. package/dist/helpers/parsePnpmLockPackages.js +62 -0
  120. package/dist/helpers/parsePnpmLockPackages.js.map +1 -0
  121. package/dist/helpers/parsePnpmLockPackages.test.js +62 -0
  122. package/dist/helpers/parsePnpmLockPackages.test.js.map +1 -0
  123. package/dist/helpers/pnpmProtocol.js +2 -0
  124. package/dist/helpers/pnpmProtocol.js.map +1 -0
  125. package/dist/helpers/pnpmVersion.js +23 -0
  126. package/dist/helpers/pnpmVersion.js.map +1 -0
  127. package/dist/helpers/pnpmWorkspaceYaml.js +48 -0
  128. package/dist/helpers/pnpmWorkspaceYaml.js.map +1 -0
  129. package/dist/helpers/pnpmWorkspaceYaml.test.js +91 -0
  130. package/dist/helpers/pnpmWorkspaceYaml.test.js.map +1 -0
  131. package/dist/helpers/projectDir.js +10 -0
  132. package/dist/helpers/projectDir.js.map +1 -0
  133. package/dist/helpers/readInstalledManifest.js +139 -0
  134. package/dist/helpers/readInstalledManifest.js.map +1 -0
  135. package/dist/helpers/readInstalledManifest.test.js +83 -0
  136. package/dist/helpers/readInstalledManifest.test.js.map +1 -0
  137. package/dist/helpers/runPnpm.js +16 -0
  138. package/dist/helpers/runPnpm.js.map +1 -0
  139. package/dist/helpers/tempProjects.js +85 -0
  140. package/dist/helpers/tempProjects.js.map +1 -0
  141. package/dist/helpers/toLockstepGraph.js +34 -0
  142. package/dist/helpers/toLockstepGraph.js.map +1 -0
  143. package/dist/helpers/toLockstepGraph.test.js +81 -0
  144. package/dist/helpers/toLockstepGraph.test.js.map +1 -0
  145. package/dist/helpers/versionsSnapshot.js +5 -0
  146. package/dist/helpers/versionsSnapshot.js.map +1 -0
  147. package/dist/identifyClusterFixes.js +53 -0
  148. package/dist/identifyClusterFixes.js.map +1 -0
  149. package/dist/identifyClusterFixes.test.js +307 -0
  150. package/dist/identifyClusterFixes.test.js.map +1 -0
  151. package/dist/identifyResolutionFixes.test.js +49 -0
  152. package/dist/identifyResolutionFixes.test.js.map +1 -0
  153. package/dist/index.js +86 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/pnpmLockTypes.js +7 -0
  156. package/dist/pnpmLockTypes.js.map +1 -0
  157. package/dist/readPnpmLock.js +6 -0
  158. package/dist/readPnpmLock.js.map +1 -0
  159. package/package.json +71 -0
  160. package/src/applyClusterFixes.test.ts +560 -0
  161. package/src/applyClusterFixes.ts +466 -0
  162. package/src/bin/pnpm-dedupe.ts +44 -0
  163. package/src/bin/pnpm-why-duplicate.test.ts +79 -0
  164. package/src/bin/pnpm-why-duplicate.ts +34 -0
  165. package/src/dedupe.ts +124 -0
  166. package/src/dedupeCheckConsistency.test.ts +218 -0
  167. package/src/dedupeEndToEnd.test.ts +168 -0
  168. package/src/dedupePeers.test.ts +175 -0
  169. package/src/displayMany.test.ts +208 -0
  170. package/src/displayMany.ts +104 -0
  171. package/src/helpers/buildIdentifiedFixesMap.test.ts +33 -0
  172. package/src/helpers/buildPnpmPackagesMap.test.ts +119 -0
  173. package/src/helpers/buildPnpmPackagesMap.ts +53 -0
  174. package/src/helpers/collectDependentRanges.test.ts +311 -0
  175. package/src/helpers/collectDependentRanges.ts +185 -0
  176. package/src/helpers/collectPnpmDependents.test.ts +112 -0
  177. package/src/helpers/collectPnpmDependents.ts +79 -0
  178. package/src/helpers/duplicateSnapshot.ts +18 -0
  179. package/src/helpers/parsePnpmLockPackages.test.ts +93 -0
  180. package/src/helpers/parsePnpmLockPackages.ts +99 -0
  181. package/src/helpers/pnpmProtocol.ts +12 -0
  182. package/src/helpers/pnpmVersion.ts +28 -0
  183. package/src/helpers/pnpmWorkspaceYaml.test.ts +140 -0
  184. package/src/helpers/pnpmWorkspaceYaml.ts +77 -0
  185. package/src/helpers/projectDir.ts +14 -0
  186. package/src/helpers/readInstalledManifest.test.ts +124 -0
  187. package/src/helpers/readInstalledManifest.ts +202 -0
  188. package/src/helpers/runPnpm.ts +34 -0
  189. package/src/helpers/tempProjects.ts +94 -0
  190. package/src/helpers/toLockstepGraph.test.ts +98 -0
  191. package/src/helpers/toLockstepGraph.ts +52 -0
  192. package/src/helpers/versionsSnapshot.ts +9 -0
  193. package/src/identifyClusterFixes.test.ts +430 -0
  194. package/src/identifyClusterFixes.ts +114 -0
  195. package/src/identifyResolutionFixes.test.ts +82 -0
  196. package/src/index.ts +159 -0
  197. package/src/pnpmLockTypes.ts +44 -0
  198. package/src/readPnpmLock.ts +7 -0
@@ -0,0 +1,139 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { stripPeerSuffix } from "./parsePnpmLockPackages.js";
5
+ const readManifestFile = (path) => {
6
+ try {
7
+ return JSON.parse(readFileSync(path, "utf8"));
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ };
13
+ const readdirOrEmpty = (path) => {
14
+ try {
15
+ return readdirSync(path);
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ };
21
+ const readModulesState = (nodeModulesDir) => {
22
+ const hoistedLocations = new Map();
23
+ const parsed = (() => {
24
+ try {
25
+ const content = parseYaml(readFileSync(join(nodeModulesDir, ".modules.yaml"), "utf8"));
26
+ return typeof content === "object" && content !== null
27
+ ? content
28
+ : undefined;
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ })();
34
+ const hoisted = parsed?.hoistedLocations;
35
+ if (typeof hoisted === "object" && hoisted !== null) {
36
+ for (const [depPath, paths] of Object.entries(hoisted)) {
37
+ if (!Array.isArray(paths))
38
+ continue;
39
+ const key = stripPeerSuffix(depPath);
40
+ hoistedLocations.set(key, [
41
+ ...(hoistedLocations.get(key) ?? []),
42
+ ...paths.filter((path) => typeof path === "string"),
43
+ ]);
44
+ }
45
+ }
46
+ const virtualStoreDir = parsed?.virtualStoreDir;
47
+ return {
48
+ virtualStoreDir: typeof virtualStoreDir === "string" ? virtualStoreDir : ".pnpm",
49
+ hoistedLocations,
50
+ };
51
+ };
52
+ /**
53
+ * Build a reader that locates an installed package's manifest, whichever
54
+ * node-linker the project uses:
55
+ *
56
+ * - virtual store (pnpm's default): the dep path gives the directory,
57
+ * `node_modules/.pnpm/<name with / replaced by +>@<version>[(peers)|_hash]/node_modules/<name>`;
58
+ * - `nodeLinker: hoisted`: the layout is npm-shaped and placement is a hoisting
59
+ * decision, so `node_modules/.modules.yaml` is consulted for the exact paths;
60
+ * a flat `node_modules/<name>` and one level of nesting are tried as a
61
+ * fallback when that file is missing.
62
+ *
63
+ * `stats` tells callers whether anything was readable at all: every declared
64
+ * range comes from these manifests, so a reader that finds nothing silently
65
+ * turns every dependent's range into an exact pin and hides every fix.
66
+ */
67
+ export const createManifestReaderWithStats = (projectDir) => {
68
+ const nodeModulesDir = join(projectDir, "node_modules");
69
+ const modules = readModulesState(nodeModulesDir);
70
+ // `virtualStoreDir` is configurable and recorded in `.modules.yaml`, relative
71
+ // to node_modules unless absolute
72
+ const pnpmDir = isAbsolute(modules.virtualStoreDir)
73
+ ? modules.virtualStoreDir
74
+ : join(nodeModulesDir, modules.virtualStoreDir);
75
+ const cache = new Map();
76
+ const stats = { found: 0, missed: 0 };
77
+ const virtualStoreEntries = readdirOrEmpty(pnpmDir);
78
+ const hoistedLocations = modules.hoistedLocations;
79
+ // top-level package dirs, for the nested lookup of the hoisted layout
80
+ const hoistedDependents = (() => {
81
+ let dirs;
82
+ return () => {
83
+ dirs ??= readdirOrEmpty(nodeModulesDir).flatMap((entry) => entry.startsWith("@")
84
+ ? readdirOrEmpty(join(nodeModulesDir, entry)).map((scoped) => join(entry, scoped))
85
+ : [entry]);
86
+ return dirs;
87
+ };
88
+ })();
89
+ const fromVirtualStore = (name, version) => {
90
+ const target = `${name.replaceAll("/", "+")}@${version}`;
91
+ const dir = virtualStoreEntries.find((entry) => entry === target ||
92
+ entry.startsWith(`${target}(`) ||
93
+ entry.startsWith(`${target}_`));
94
+ return dir
95
+ ? readManifestFile(join(pnpmDir, dir, "node_modules", name, "package.json"))
96
+ : undefined;
97
+ };
98
+ const fromHoisted = (name, version) => {
99
+ const atVersion = (path) => {
100
+ const manifest = readManifestFile(path);
101
+ return manifest?.version === version ? manifest : undefined;
102
+ };
103
+ // the recorded locations are relative to the project directory
104
+ for (const location of hoistedLocations.get(`${name}@${version}`) ?? []) {
105
+ const recorded = atVersion(join(projectDir, location, "package.json"));
106
+ if (recorded)
107
+ return recorded;
108
+ }
109
+ const hoisted = atVersion(join(nodeModulesDir, name, "package.json"));
110
+ if (hoisted)
111
+ return hoisted;
112
+ // no `.modules.yaml`: a version that could not be hoisted lives under
113
+ // whichever dependent needed it, so try one level of nesting
114
+ for (const dependent of hoistedDependents()) {
115
+ const nested = atVersion(join(nodeModulesDir, dependent, "node_modules", name, "package.json"));
116
+ if (nested)
117
+ return nested;
118
+ }
119
+ return undefined;
120
+ };
121
+ const readManifest = (name, version) => {
122
+ const cacheKey = `${name}@${version}`;
123
+ if (cache.has(cacheKey)) {
124
+ return cache.get(cacheKey);
125
+ }
126
+ const manifest = fromVirtualStore(name, version) ?? fromHoisted(name, version);
127
+ if (manifest) {
128
+ stats.found += 1;
129
+ }
130
+ else {
131
+ stats.missed += 1;
132
+ }
133
+ cache.set(cacheKey, manifest);
134
+ return manifest;
135
+ };
136
+ return { readManifest, stats: () => ({ ...stats }) };
137
+ };
138
+ export const createManifestReader = (projectDir) => createManifestReaderWithStats(projectDir).readManifest;
139
+ //# sourceMappingURL=readInstalledManifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readInstalledManifest.js","sourceRoot":"","sources":["../../src/helpers/readInstalledManifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { describe, it } from "bun:test";
2
+ import { deepStrictEqual, strictEqual } from "node:assert/strict";
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { createManifestReaderWithStats } from "./readInstalledManifest.js";
8
+ const hoistedFixture = fileURLToPath(new URL("../../test/fixtures/hoisted-node-linker", import.meta.url));
9
+ describe("createManifestReaderWithStats", () => {
10
+ it("reads a hoisted layout, top-level and nested", () => {
11
+ const { readManifest, stats } = createManifestReaderWithStats(hoistedFixture);
12
+ // hoisted at the top of node_modules
13
+ deepStrictEqual(readManifest("mini-metro", "0.84.5")?.dependencies, {
14
+ "mini-metro-config": "0.84.5",
15
+ });
16
+ // the version that lost the hoist, nested under its dependent
17
+ deepStrictEqual(readManifest("mini-metro", "0.87.0")?.dependencies, {
18
+ "mini-metro-config": "0.87.0",
19
+ });
20
+ // the open range this whole scenario turns on
21
+ deepStrictEqual(readManifest("mini-plugin", "1.0.0")?.dependencies, {
22
+ "mini-metro-config": "*",
23
+ });
24
+ deepStrictEqual(stats(), { found: 3, missed: 0 });
25
+ });
26
+ it("resolves a location only pnpm's own index knows", () => {
27
+ const { readManifest } = createManifestReaderWithStats(hoistedFixture);
28
+ // three levels down: reachable from `.modules.yaml`'s hoistedLocations,
29
+ // not from scanning node_modules one level deep
30
+ deepStrictEqual(readManifest("mini-deep", "2.0.0")?.dependencies, {
31
+ "mini-metro-config": "*",
32
+ });
33
+ });
34
+ it("does not return a manifest whose version differs", () => {
35
+ const { readManifest, stats } = createManifestReaderWithStats(hoistedFixture);
36
+ strictEqual(readManifest("mini-metro", "9.9.9"), undefined);
37
+ deepStrictEqual(stats(), { found: 0, missed: 1 });
38
+ });
39
+ it("reads pnpm's virtual store layout, peer suffix included", () => {
40
+ const dir = mkdtempSync(join(tmpdir(), "pnpm-dedup-virtual-store-"));
41
+ try {
42
+ const packageDir = join(dir, "node_modules", ".pnpm", "@scope+pkg@1.2.3(react@19.0.0)", "node_modules", "@scope", "pkg");
43
+ mkdirSync(packageDir, { recursive: true });
44
+ writeFileSync(join(packageDir, "package.json"), JSON.stringify({
45
+ name: "@scope/pkg",
46
+ version: "1.2.3",
47
+ peerDependencies: { react: "*" },
48
+ }));
49
+ const { readManifest, stats } = createManifestReaderWithStats(dir);
50
+ deepStrictEqual(readManifest("@scope/pkg", "1.2.3")?.peerDependencies, {
51
+ react: "*",
52
+ });
53
+ deepStrictEqual(stats(), { found: 1, missed: 0 });
54
+ }
55
+ finally {
56
+ rmSync(dir, { recursive: true, force: true });
57
+ }
58
+ });
59
+ it("honors a relocated virtual store", () => {
60
+ const dir = mkdtempSync(join(tmpdir(), "pnpm-dedup-relocated-store-"));
61
+ try {
62
+ const packageDir = join(dir, "node_modules", ".pnpm-store", "pkg@1.0.0", "node_modules", "pkg");
63
+ mkdirSync(packageDir, { recursive: true });
64
+ writeFileSync(join(packageDir, "package.json"), JSON.stringify({ name: "pkg", version: "1.0.0", dependencies: {} }));
65
+ writeFileSync(join(dir, "node_modules", ".modules.yaml"), JSON.stringify({
66
+ nodeLinker: "isolated",
67
+ virtualStoreDir: ".pnpm-store",
68
+ }));
69
+ const { readManifest } = createManifestReaderWithStats(dir);
70
+ strictEqual(readManifest("pkg", "1.0.0")?.version, "1.0.0");
71
+ }
72
+ finally {
73
+ rmSync(dir, { recursive: true, force: true });
74
+ }
75
+ });
76
+ it("reports every read as missed when nothing is installed", () => {
77
+ const { readManifest, stats } = createManifestReaderWithStats(join(tmpdir(), "pnpm-dedup-not-installed"));
78
+ strictEqual(readManifest("mini-metro", "0.84.5"), undefined);
79
+ strictEqual(readManifest("mini-plugin", "1.0.0"), undefined);
80
+ deepStrictEqual(stats(), { found: 0, missed: 2 });
81
+ });
82
+ });
83
+ //# sourceMappingURL=readInstalledManifest.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readInstalledManifest.test.js","sourceRoot":"","sources":["../../src/helpers/readInstalledManifest.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export const runPnpm = (args, { cwd, stdio = "inherit" } = {}) => {
3
+ const result = spawnSync("pnpm", args, {
4
+ cwd,
5
+ stdio,
6
+ encoding: "utf8",
7
+ });
8
+ if (result.error) {
9
+ throw result.error;
10
+ }
11
+ return {
12
+ status: result.status,
13
+ output: stdio === "pipe" ? `${result.stdout}\n${result.stderr}` : "",
14
+ };
15
+ };
16
+ //# sourceMappingURL=runPnpm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runPnpm.js","sourceRoot":"","sources":["../../src/helpers/runPnpm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAkB/C,MAAM,CAAC,MAAM,OAAO,GAAe,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,GAAG,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE;IAC3E,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE;QACrC,GAAG;QACH,KAAK;QACL,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,MAAM,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE;KACrE,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { mkdtempSync, readdirSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ // Scratch projects for the tests that need a real directory on disk. The
5
+ // end-to-end ones run an actual install in theirs, so a leaked directory is
6
+ // tens of megabytes.
7
+ //
8
+ // `afterEach` covers the normal path — a passing, failing or timed-out test all
9
+ // reach it — but not a signal: on Ctrl-C the test runner exits without running
10
+ // the remaining hooks, and on SIGKILL nothing runs at all. So cleanup does not
11
+ // rest on it alone:
12
+ // - the directory name carries the pid that owns it, and every run first
13
+ // reaps the leftovers of pids that are gone,
14
+ // - SIGINT/SIGTERM clean up before the signal is allowed through.
15
+ const isRunning = (pid) => {
16
+ try {
17
+ process.kill(pid, 0);
18
+ return true;
19
+ }
20
+ catch (error) {
21
+ // EPERM: alive, but owned by another user — not ours to reap either way
22
+ return error.code === "EPERM";
23
+ }
24
+ };
25
+ const remove = (dir) => {
26
+ try {
27
+ rmSync(dir, { recursive: true, force: true });
28
+ }
29
+ catch {
30
+ // a directory a still-running install is writing into can fail to unlink;
31
+ // the next run reaps it once that pid is gone
32
+ }
33
+ };
34
+ // the `${pid}-${mkdtemp suffix}` a `create` call leaves after the prefix. Names
35
+ // that do not match are left alone: a random suffix read as a pid would reap
36
+ // directories this helper never made.
37
+ const ownerPidPattern = /^(?<pid>[1-9]\d*)-/u;
38
+ const reapAbandoned = (prefix) => {
39
+ const root = tmpdir();
40
+ for (const name of readdirSync(root)) {
41
+ if (!name.startsWith(prefix))
42
+ continue;
43
+ const owner = ownerPidPattern.exec(name.slice(prefix.length))?.groups?.pid;
44
+ if (!owner || isRunning(Number(owner)))
45
+ continue;
46
+ remove(join(root, name));
47
+ }
48
+ };
49
+ const pendingCleanups = new Set();
50
+ let signalsHooked = false;
51
+ const hookSignals = () => {
52
+ if (signalsHooked)
53
+ return;
54
+ signalsHooked = true;
55
+ for (const signal of ["SIGINT", "SIGTERM"]) {
56
+ const handler = () => {
57
+ for (const cleanup of pendingCleanups)
58
+ cleanup();
59
+ // a listener suppresses the default termination: drop it and re-raise so
60
+ // the process still dies of the signal it was sent
61
+ process.removeListener(signal, handler);
62
+ process.kill(process.pid, signal);
63
+ };
64
+ process.on(signal, handler);
65
+ }
66
+ };
67
+ export const createTempProjects = (prefix) => {
68
+ reapAbandoned(prefix);
69
+ hookSignals();
70
+ const dirs = [];
71
+ const cleanup = () => {
72
+ for (const dir of dirs.splice(0))
73
+ remove(dir);
74
+ };
75
+ pendingCleanups.add(cleanup);
76
+ return {
77
+ create: () => {
78
+ const dir = mkdtempSync(join(tmpdir(), `${prefix}${process.pid}-`));
79
+ dirs.push(dir);
80
+ return dir;
81
+ },
82
+ cleanup,
83
+ };
84
+ };
85
+ //# sourceMappingURL=tempProjects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempProjects.js","sourceRoot":"","sources":["../../src/helpers/tempProjects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { resolveSnapshotDependency } from "./parsePnpmLockPackages.js";
2
+ const snapshotDepTypes = ["dependencies", "optionalDependencies"];
3
+ const snapshotDependencies = (lock, snapshotKey) => {
4
+ const snapshot = lock.snapshots?.[snapshotKey];
5
+ const dependencies = {};
6
+ snapshotDepTypes.forEach((depType) => {
7
+ for (const [depName, depValue] of Object.entries(snapshot?.[depType] ?? {})) {
8
+ const resolved = resolveSnapshotDependency(depName, depValue);
9
+ dependencies[resolved.name] = resolved.version;
10
+ }
11
+ });
12
+ return dependencies;
13
+ };
14
+ /**
15
+ * Adapt the pnpm lockfile model into the package-manager-neutral graph consumed
16
+ * by `buildLockstepClusters`. pnpm snapshots store the *resolved* version of
17
+ * each dependency instead of the requested range, which co-version detection
18
+ * handles; one entry is emitted per installation, so a resolution installed in
19
+ * several peer contexts contributes every context it appears in.
20
+ */
21
+ export const toLockstepGraph = (lock, packagesMap) => Object.fromEntries(Object.entries(packagesMap).map(([name, resolutions]) => [
22
+ name,
23
+ resolutions.flatMap((resolution) => {
24
+ if (resolution.package.type !== "npm") {
25
+ return [{ version: "", isNpm: false, dependencies: {} }];
26
+ }
27
+ return resolution.installations.map((snapshotKey) => ({
28
+ version: resolution.package.version,
29
+ isNpm: true,
30
+ dependencies: snapshotDependencies(lock, snapshotKey),
31
+ }));
32
+ }),
33
+ ]));
34
+ //# sourceMappingURL=toLockstepGraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toLockstepGraph.js","sourceRoot":"","sources":["../../src/helpers/toLockstepGraph.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAEvE,MAAM,gBAAgB,GAAG,CAAC,cAAc,EAAE,sBAAsB,CAAU,CAAC;AAE3E,MAAM,oBAAoB,GAAG,CAC3B,IAAkB,EAClB,WAAmB,EACK,EAAE;IAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC;IAC/C,MAAM,YAAY,GAA2B,EAAE,CAAC;IAEhD,gBAAgB,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QACnC,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAC9C,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAC1B,EAAE,CAAC;YACF,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC9D,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,IAAkB,EAClB,WAAwB,EACT,EAAE,CACjB,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC;IACvD,IAAI;IACJ,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAwB,EAAE;QACvD,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACtC,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,UAAU,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO;YACnC,KAAK,EAAE,IAAI;YACX,YAAY,EAAE,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC;SACtD,CAAC,CAAC,CAAC;IACN,CAAC,CAAC;CACH,CAAC,CACH,CAAC"}
@@ -0,0 +1,81 @@
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 } from "./buildPnpmPackagesMap.js";
6
+ import { parsePnpmLockPackages } from "./parsePnpmLockPackages.js";
7
+ import { toLockstepGraph } from "./toLockstepGraph.js";
8
+ const fixturesBase = (rel) => fileURLToPath(new URL(rel, import.meta.url));
9
+ const graphOf = (lock) => toLockstepGraph(lock, buildPnpmPackagesMap(parsePnpmLockPackages(lock)));
10
+ const graphFor = (scenario) => graphOf(readPnpmLock(fixturesBase(`../../test/fixtures/${scenario}/pnpm-lock.yaml`)));
11
+ describe("toLockstepGraph", () => {
12
+ it("strips peer suffixes, resolves aliases, and keeps every installation", () => {
13
+ const graph = graphOf({
14
+ lockfileVersion: "9.0",
15
+ packages: {
16
+ "a@1.0.0": {},
17
+ "b@1.0.0": {},
18
+ "alias-target@2.0.0": {},
19
+ },
20
+ snapshots: {
21
+ "a@1.0.0(peer@1.0.0)": {
22
+ dependencies: {
23
+ b: "1.0.0(peer@1.0.0)",
24
+ aliased: "alias-target@2.0.0",
25
+ },
26
+ },
27
+ "a@1.0.0(peer@2.0.0)": {
28
+ dependencies: { b: "1.0.0" },
29
+ },
30
+ "b@1.0.0": {},
31
+ "alias-target@2.0.0": {},
32
+ },
33
+ });
34
+ // one entry per peer context, both carrying the same resolved version
35
+ deepStrictEqual(graph.a, [
36
+ {
37
+ version: "1.0.0",
38
+ isNpm: true,
39
+ dependencies: { b: "1.0.0", "alias-target": "2.0.0" },
40
+ },
41
+ { version: "1.0.0", isNpm: true, dependencies: { b: "1.0.0" } },
42
+ ]);
43
+ });
44
+ it("maps metro's exact self-pins to co-version edges", () => {
45
+ const metro = graphFor("wildcard-not-reused").metro;
46
+ ok(metro);
47
+ deepStrictEqual(metro.map((resolution) => resolution.version).toSorted(), [
48
+ "0.84.5",
49
+ "0.87.0",
50
+ ]);
51
+ for (const resolution of metro) {
52
+ strictEqual(resolution.isNpm, true);
53
+ strictEqual(resolution.dependencies["metro-config"], resolution.version);
54
+ // an unrelated dependency keeps its own version line
55
+ ok(resolution.dependencies.debug?.startsWith("4."));
56
+ }
57
+ });
58
+ // `aliased-swapped-names`: the keys the importer uses name each other's
59
+ // package. The graph is keyed by npm name, so `@typescript/typescript6` has to
60
+ // stay its own name and `tool`'s edge has to land on the real `typescript`.
61
+ it("keeps swapped alias keys apart", () => {
62
+ const graph = graphFor("aliased-swapped-names");
63
+ deepStrictEqual(Object.keys(graph).toSorted(), [
64
+ "@typescript/typescript6",
65
+ "tool",
66
+ "typescript",
67
+ ]);
68
+ deepStrictEqual(graph.tool?.[0]?.dependencies, { typescript: "5.9.3" });
69
+ strictEqual(graph.typescript?.length, 2);
70
+ });
71
+ it("marks non-npm resolutions so cluster detection skips them", () => {
72
+ const nonNpm = Object.values(graphFor("non-npm"))
73
+ .flat()
74
+ .filter((resolution) => !resolution.isNpm);
75
+ ok(nonNpm.length > 0);
76
+ for (const resolution of nonNpm) {
77
+ deepStrictEqual(resolution.dependencies, {});
78
+ }
79
+ });
80
+ });
81
+ //# sourceMappingURL=toLockstepGraph.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toLockstepGraph.test.js","sourceRoot":"","sources":["../../src/helpers/toLockstepGraph.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,EAAE,CACnC,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/C,MAAM,OAAO,GAAG,CAAC,IAAkB,EAAE,EAAE,CACrC,eAAe,CAAC,IAAI,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAE3E,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,EAAE,CACpC,OAAO,CACL,YAAY,CACV,YAAY,CAAC,uBAAuB,QAAQ,iBAAiB,CAAC,CAC/D,CACF,CAAC;AAEJ,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,KAAK,GAAG,OAAO,CAAC;YACpB,eAAe,EAAE,KAAK;YACtB,QAAQ,EAAE;gBACR,SAAS,EAAE,EAAE;gBACb,SAAS,EAAE,EAAE;gBACb,oBAAoB,EAAE,EAAE;aACzB;YACD,SAAS,EAAE;gBACT,qBAAqB,EAAE;oBACrB,YAAY,EAAE;wBACZ,CAAC,EAAE,mBAAmB;wBACtB,OAAO,EAAE,oBAAoB;qBAC9B;iBACF;gBACD,qBAAqB,EAAE;oBACrB,YAAY,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE;iBAC7B;gBACD,SAAS,EAAE,EAAE;gBACb,oBAAoB,EAAE,EAAE;aACzB;SACF,CAAC,CAAC;QAEH,sEAAsE;QACtE,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE;YACvB;gBACE,OAAO,EAAE,OAAO;gBAChB,KAAK,EAAE,IAAI;gBACX,YAAY,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE;aACtD;YACD,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE;SAChE,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,qBAAqB,CAAC,CAAC,KAAK,CAAC;QACpD,EAAE,CAAC,KAAK,CAAC,CAAC;QACV,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxE,QAAQ;YACR,QAAQ;SACT,CAAC,CAAC;QAEH,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;YAC/B,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACpC,WAAW,CAAC,UAAU,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;YACzE,qDAAqD;YACrD,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,wEAAwE;IACxE,+EAA+E;IAC/E,4EAA4E;IAC5E,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC;QAEhD,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7C,yBAAyB;YACzB,MAAM;YACN,YAAY;SACb,CAAC,CAAC;QACH,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,WAAW,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;aAC9C,IAAI,EAAE;aACN,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC7C,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACtB,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE,CAAC;YAChC,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { buildVersionsSnapshot } from "pm-dedup-core";
2
+ import { readPnpmLock } from "../readPnpmLock.js";
3
+ import { parsePnpmLockPackages } from "./parsePnpmLockPackages.js";
4
+ export const readVersionsSnapshot = (lockPath) => buildVersionsSnapshot(parsePnpmLockPackages(readPnpmLock(lockPath)).packages.values());
5
+ //# sourceMappingURL=versionsSnapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versionsSnapshot.js","sourceRoot":"","sources":["../../src/helpers/versionsSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,QAAgB,EAAoB,EAAE,CACzE,qBAAqB,CACnB,qBAAqB,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAChE,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { buildLockstepClusters, identifyLockstepClusterFixes, } from "pm-dedup-core";
2
+ import { collectDependentRanges } from "./helpers/collectDependentRanges.js";
3
+ import { toLockstepGraph } from "./helpers/toLockstepGraph.js";
4
+ // A lone duplicated package is a cluster of one: nothing co-versions with it,
5
+ // but it converges through the same mechanism — the dependents that hold its
6
+ // copies apart — and it is applied the same way, by an override the resolver
7
+ // then has to confirm.
8
+ const singletonClusters = (packagesMap, clustered) => Object.entries(packagesMap)
9
+ .filter(([name, resolutions]) => !clustered.has(name) &&
10
+ resolutions.filter((resolution) => resolution.package.type === "npm")
11
+ .length > 1)
12
+ .map(([name]) => [name]);
13
+ const toClusterMembers = (packagesMap, memberNames) => Object.fromEntries(memberNames.map((name) => {
14
+ const resolutions = packagesMap[name] ?? [];
15
+ return [
16
+ name,
17
+ {
18
+ npmVersions: resolutions
19
+ .filter((resolution) => resolution.package.type === "npm")
20
+ .map((resolution) => resolution.package.version),
21
+ resolutionCount: resolutions.length,
22
+ },
23
+ ];
24
+ }));
25
+ // The pnpm lockfile stores resolved versions, not requested ranges, so the
26
+ // constraints have to come from `collectDependentRanges`, which recovers the
27
+ // declared range from each requester's installed manifest.
28
+ const toClusterDependents = (lock, memberNames, readManifest) => {
29
+ const ranges = collectDependentRanges(lock, new Set(memberNames), readManifest);
30
+ return new Map([...ranges].map(([name, dependents]) => [
31
+ name,
32
+ dependents.map((dependent) => ({
33
+ requester: dependent.key,
34
+ requesterName: dependent.requesterName,
35
+ range: dependent.range,
36
+ isAlias: dependent.aliasKey !== undefined,
37
+ resolvedVersion: dependent.resolvedVersion,
38
+ workspace: dependent.workspace,
39
+ })),
40
+ ]));
41
+ };
42
+ export const identifyClusterFixes = (lock, packagesMap, readManifest) => {
43
+ const lockstepClusters = buildLockstepClusters(toLockstepGraph(lock, packagesMap));
44
+ const clusters = [
45
+ ...lockstepClusters,
46
+ ...singletonClusters(packagesMap, new Set(lockstepClusters.flat())),
47
+ ];
48
+ if (clusters.length === 0)
49
+ return [];
50
+ const memberNames = [...new Set(clusters.flat())];
51
+ return identifyLockstepClusterFixes(clusters, toClusterMembers(packagesMap, memberNames), toClusterDependents(lock, memberNames, readManifest)).filter((fix) => fix.members.length > 1 || fix.workspaceChanges.length === 0);
52
+ };
53
+ //# sourceMappingURL=identifyClusterFixes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifyClusterFixes.js","sourceRoot":"","sources":["../src/identifyClusterFixes.ts"],"names":[],"mappings":"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"}