pm-dedup-core 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 (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/dist/buildIdentifiedFixesMap.js +6 -0
  4. package/dist/buildIdentifiedFixesMap.js.map +1 -0
  5. package/dist/buildLockstepClusters.js +119 -0
  6. package/dist/buildLockstepClusters.js.map +1 -0
  7. package/dist/buildLockstepClusters.test.js +71 -0
  8. package/dist/buildLockstepClusters.test.js.map +1 -0
  9. package/dist/clusterLabel.js +59 -0
  10. package/dist/clusterLabel.js.map +1 -0
  11. package/dist/clusterLabel.test.js +56 -0
  12. package/dist/clusterLabel.test.js.map +1 -0
  13. package/dist/compareCandidateVersions.js +18 -0
  14. package/dist/compareCandidateVersions.js.map +1 -0
  15. package/dist/compareCandidateVersions.test.js +32 -0
  16. package/dist/compareCandidateVersions.test.js.map +1 -0
  17. package/dist/createPackageFilter.js +81 -0
  18. package/dist/createPackageFilter.js.map +1 -0
  19. package/dist/createPackageFilter.test.js +115 -0
  20. package/dist/createPackageFilter.test.js.map +1 -0
  21. package/dist/definitions/buildIdentifiedFixesMap.d.ts +4 -0
  22. package/dist/definitions/buildIdentifiedFixesMap.d.ts.map +1 -0
  23. package/dist/definitions/buildLockstepClusters.d.ts +8 -0
  24. package/dist/definitions/buildLockstepClusters.d.ts.map +1 -0
  25. package/dist/definitions/buildLockstepClusters.test.d.ts +2 -0
  26. package/dist/definitions/buildLockstepClusters.test.d.ts.map +1 -0
  27. package/dist/definitions/clusterLabel.d.ts +8 -0
  28. package/dist/definitions/clusterLabel.d.ts.map +1 -0
  29. package/dist/definitions/clusterLabel.test.d.ts +2 -0
  30. package/dist/definitions/clusterLabel.test.d.ts.map +1 -0
  31. package/dist/definitions/compareCandidateVersions.d.ts +10 -0
  32. package/dist/definitions/compareCandidateVersions.d.ts.map +1 -0
  33. package/dist/definitions/compareCandidateVersions.test.d.ts +2 -0
  34. package/dist/definitions/compareCandidateVersions.test.d.ts.map +1 -0
  35. package/dist/definitions/createPackageFilter.d.ts +45 -0
  36. package/dist/definitions/createPackageFilter.d.ts.map +1 -0
  37. package/dist/definitions/createPackageFilter.test.d.ts +2 -0
  38. package/dist/definitions/createPackageFilter.test.d.ts.map +1 -0
  39. package/dist/definitions/duplicateSnapshot.d.ts +8 -0
  40. package/dist/definitions/duplicateSnapshot.d.ts.map +1 -0
  41. package/dist/definitions/fileSnapshot.d.ts +7 -0
  42. package/dist/definitions/fileSnapshot.d.ts.map +1 -0
  43. package/dist/definitions/findProjectRoot.d.ts +27 -0
  44. package/dist/definitions/findProjectRoot.d.ts.map +1 -0
  45. package/dist/definitions/findProjectRoot.test.d.ts +2 -0
  46. package/dist/definitions/findProjectRoot.test.d.ts.map +1 -0
  47. package/dist/definitions/identifyLockstepClusterFixes.d.ts +89 -0
  48. package/dist/definitions/identifyLockstepClusterFixes.d.ts.map +1 -0
  49. package/dist/definitions/identifyLockstepClusterFixes.test.d.ts +2 -0
  50. package/dist/definitions/identifyLockstepClusterFixes.test.d.ts.map +1 -0
  51. package/dist/definitions/identifyResolutionFixes.d.ts +28 -0
  52. package/dist/definitions/identifyResolutionFixes.d.ts.map +1 -0
  53. package/dist/definitions/identifyResolutionFixes.test.d.ts +2 -0
  54. package/dist/definitions/identifyResolutionFixes.test.d.ts.map +1 -0
  55. package/dist/definitions/index.d.ts +44 -0
  56. package/dist/definitions/index.d.ts.map +1 -0
  57. package/dist/definitions/packageDependenciesUtils.d.ts +47 -0
  58. package/dist/definitions/packageDependenciesUtils.d.ts.map +1 -0
  59. package/dist/definitions/packageDependenciesUtils.test.d.ts +2 -0
  60. package/dist/definitions/packageDependenciesUtils.test.d.ts.map +1 -0
  61. package/dist/definitions/packageFilterArgs.d.ts +74 -0
  62. package/dist/definitions/packageFilterArgs.d.ts.map +1 -0
  63. package/dist/definitions/packageFilterArgs.test.d.ts +2 -0
  64. package/dist/definitions/packageFilterArgs.test.d.ts.map +1 -0
  65. package/dist/definitions/packageStyles.d.ts +14 -0
  66. package/dist/definitions/packageStyles.d.ts.map +1 -0
  67. package/dist/definitions/packageStyles.test.d.ts +2 -0
  68. package/dist/definitions/packageStyles.test.d.ts.map +1 -0
  69. package/dist/definitions/parseBinArgs.d.ts +14 -0
  70. package/dist/definitions/parseBinArgs.d.ts.map +1 -0
  71. package/dist/definitions/parseBinArgs.test.d.ts +2 -0
  72. package/dist/definitions/parseBinArgs.test.d.ts.map +1 -0
  73. package/dist/definitions/planClusterApply.d.ts +39 -0
  74. package/dist/definitions/planClusterApply.d.ts.map +1 -0
  75. package/dist/definitions/planClusterApply.test.d.ts +2 -0
  76. package/dist/definitions/planClusterApply.test.d.ts.map +1 -0
  77. package/dist/definitions/renderApplyPlan.d.ts +22 -0
  78. package/dist/definitions/renderApplyPlan.d.ts.map +1 -0
  79. package/dist/definitions/renderApplyPlan.test.d.ts +2 -0
  80. package/dist/definitions/renderApplyPlan.test.d.ts.map +1 -0
  81. package/dist/definitions/renderDedupeSummary.d.ts +22 -0
  82. package/dist/definitions/renderDedupeSummary.d.ts.map +1 -0
  83. package/dist/definitions/renderDedupeSummary.test.d.ts +2 -0
  84. package/dist/definitions/renderDedupeSummary.test.d.ts.map +1 -0
  85. package/dist/definitions/renderDuplicatesReport.d.ts +39 -0
  86. package/dist/definitions/renderDuplicatesReport.d.ts.map +1 -0
  87. package/dist/definitions/renderDuplicatesReport.test.d.ts +2 -0
  88. package/dist/definitions/renderDuplicatesReport.test.d.ts.map +1 -0
  89. package/dist/definitions/reportColors.d.ts +25 -0
  90. package/dist/definitions/reportColors.d.ts.map +1 -0
  91. package/dist/definitions/reportColors.test.d.ts +2 -0
  92. package/dist/definitions/reportColors.test.d.ts.map +1 -0
  93. package/dist/definitions/reportText.d.ts +29 -0
  94. package/dist/definitions/reportText.d.ts.map +1 -0
  95. package/dist/definitions/selectExplainedPackages.d.ts +22 -0
  96. package/dist/definitions/selectExplainedPackages.d.ts.map +1 -0
  97. package/dist/definitions/selectExplainedPackages.test.d.ts +2 -0
  98. package/dist/definitions/selectExplainedPackages.test.d.ts.map +1 -0
  99. package/dist/definitions/unconditionalOverrides.d.ts +24 -0
  100. package/dist/definitions/unconditionalOverrides.d.ts.map +1 -0
  101. package/dist/definitions/unconditionalOverrides.test.d.ts +2 -0
  102. package/dist/definitions/unconditionalOverrides.test.d.ts.map +1 -0
  103. package/dist/definitions/versionsSnapshot.d.ts +40 -0
  104. package/dist/definitions/versionsSnapshot.d.ts.map +1 -0
  105. package/dist/definitions/versionsSnapshot.test.d.ts +2 -0
  106. package/dist/definitions/versionsSnapshot.test.d.ts.map +1 -0
  107. package/dist/definitions/workspaceManifest.d.ts +21 -0
  108. package/dist/definitions/workspaceManifest.d.ts.map +1 -0
  109. package/dist/definitions/workspaceManifest.test.d.ts +2 -0
  110. package/dist/definitions/workspaceManifest.test.d.ts.map +1 -0
  111. package/dist/duplicateSnapshot.js +10 -0
  112. package/dist/duplicateSnapshot.js.map +1 -0
  113. package/dist/fileSnapshot.js +21 -0
  114. package/dist/fileSnapshot.js.map +1 -0
  115. package/dist/findProjectRoot.js +48 -0
  116. package/dist/findProjectRoot.js.map +1 -0
  117. package/dist/findProjectRoot.test.js +53 -0
  118. package/dist/findProjectRoot.test.js.map +1 -0
  119. package/dist/identifyLockstepClusterFixes.js +352 -0
  120. package/dist/identifyLockstepClusterFixes.js.map +1 -0
  121. package/dist/identifyLockstepClusterFixes.test.js +372 -0
  122. package/dist/identifyLockstepClusterFixes.test.js.map +1 -0
  123. package/dist/identifyResolutionFixes.js +102 -0
  124. package/dist/identifyResolutionFixes.js.map +1 -0
  125. package/dist/identifyResolutionFixes.test.js +70 -0
  126. package/dist/identifyResolutionFixes.test.js.map +1 -0
  127. package/dist/index.js +24 -0
  128. package/dist/index.js.map +1 -0
  129. package/dist/packageDependenciesUtils.js +91 -0
  130. package/dist/packageDependenciesUtils.js.map +1 -0
  131. package/dist/packageDependenciesUtils.test.js +539 -0
  132. package/dist/packageDependenciesUtils.test.js.map +1 -0
  133. package/dist/packageFilterArgs.js +65 -0
  134. package/dist/packageFilterArgs.js.map +1 -0
  135. package/dist/packageFilterArgs.test.js +93 -0
  136. package/dist/packageFilterArgs.test.js.map +1 -0
  137. package/dist/packageStyles.js +33 -0
  138. package/dist/packageStyles.js.map +1 -0
  139. package/dist/packageStyles.test.js +58 -0
  140. package/dist/packageStyles.test.js.map +1 -0
  141. package/dist/parseBinArgs.js +28 -0
  142. package/dist/parseBinArgs.js.map +1 -0
  143. package/dist/parseBinArgs.test.js +62 -0
  144. package/dist/parseBinArgs.test.js.map +1 -0
  145. package/dist/planClusterApply.js +69 -0
  146. package/dist/planClusterApply.js.map +1 -0
  147. package/dist/planClusterApply.test.js +152 -0
  148. package/dist/planClusterApply.test.js.map +1 -0
  149. package/dist/renderApplyPlan.js +51 -0
  150. package/dist/renderApplyPlan.js.map +1 -0
  151. package/dist/renderApplyPlan.test.js +106 -0
  152. package/dist/renderApplyPlan.test.js.map +1 -0
  153. package/dist/renderDedupeSummary.js +38 -0
  154. package/dist/renderDedupeSummary.js.map +1 -0
  155. package/dist/renderDedupeSummary.test.js +91 -0
  156. package/dist/renderDedupeSummary.test.js.map +1 -0
  157. package/dist/renderDuplicatesReport.js +375 -0
  158. package/dist/renderDuplicatesReport.js.map +1 -0
  159. package/dist/renderDuplicatesReport.test.js +598 -0
  160. package/dist/renderDuplicatesReport.test.js.map +1 -0
  161. package/dist/reportColors.js +51 -0
  162. package/dist/reportColors.js.map +1 -0
  163. package/dist/reportColors.test.js +49 -0
  164. package/dist/reportColors.test.js.map +1 -0
  165. package/dist/reportText.js +29 -0
  166. package/dist/reportText.js.map +1 -0
  167. package/dist/selectExplainedPackages.js +42 -0
  168. package/dist/selectExplainedPackages.js.map +1 -0
  169. package/dist/selectExplainedPackages.test.js +55 -0
  170. package/dist/selectExplainedPackages.test.js.map +1 -0
  171. package/dist/unconditionalOverrides.js +36 -0
  172. package/dist/unconditionalOverrides.js.map +1 -0
  173. package/dist/unconditionalOverrides.test.js +71 -0
  174. package/dist/unconditionalOverrides.test.js.map +1 -0
  175. package/dist/versionsSnapshot.js +49 -0
  176. package/dist/versionsSnapshot.js.map +1 -0
  177. package/dist/versionsSnapshot.test.js +79 -0
  178. package/dist/versionsSnapshot.test.js.map +1 -0
  179. package/dist/workspaceManifest.js +63 -0
  180. package/dist/workspaceManifest.js.map +1 -0
  181. package/dist/workspaceManifest.test.js +159 -0
  182. package/dist/workspaceManifest.test.js.map +1 -0
  183. package/package.json +68 -0
  184. package/src/buildIdentifiedFixesMap.ts +22 -0
  185. package/src/buildLockstepClusters.test.ts +90 -0
  186. package/src/buildLockstepClusters.ts +155 -0
  187. package/src/clusterLabel.test.ts +79 -0
  188. package/src/clusterLabel.ts +75 -0
  189. package/src/compareCandidateVersions.test.ts +39 -0
  190. package/src/compareCandidateVersions.ts +23 -0
  191. package/src/createPackageFilter.test.ts +156 -0
  192. package/src/createPackageFilter.ts +142 -0
  193. package/src/duplicateSnapshot.ts +24 -0
  194. package/src/fileSnapshot.ts +28 -0
  195. package/src/findProjectRoot.test.ts +66 -0
  196. package/src/findProjectRoot.ts +61 -0
  197. package/src/identifyLockstepClusterFixes.test.ts +462 -0
  198. package/src/identifyLockstepClusterFixes.ts +602 -0
  199. package/src/identifyResolutionFixes.test.ts +133 -0
  200. package/src/identifyResolutionFixes.ts +183 -0
  201. package/src/index.ts +123 -0
  202. package/src/packageDependenciesUtils.test.ts +593 -0
  203. package/src/packageDependenciesUtils.ts +156 -0
  204. package/src/packageFilterArgs.test.ts +116 -0
  205. package/src/packageFilterArgs.ts +98 -0
  206. package/src/packageStyles.test.ts +77 -0
  207. package/src/packageStyles.ts +42 -0
  208. package/src/parseBinArgs.test.ts +73 -0
  209. package/src/parseBinArgs.ts +33 -0
  210. package/src/planClusterApply.test.ts +163 -0
  211. package/src/planClusterApply.ts +105 -0
  212. package/src/renderApplyPlan.test.ts +136 -0
  213. package/src/renderApplyPlan.ts +95 -0
  214. package/src/renderDedupeSummary.test.ts +121 -0
  215. package/src/renderDedupeSummary.ts +78 -0
  216. package/src/renderDuplicatesReport.test.ts +724 -0
  217. package/src/renderDuplicatesReport.ts +645 -0
  218. package/src/reportColors.test.ts +57 -0
  219. package/src/reportColors.ts +78 -0
  220. package/src/reportText.ts +58 -0
  221. package/src/selectExplainedPackages.test.ts +67 -0
  222. package/src/selectExplainedPackages.ts +79 -0
  223. package/src/unconditionalOverrides.test.ts +99 -0
  224. package/src/unconditionalOverrides.ts +60 -0
  225. package/src/versionsSnapshot.test.ts +146 -0
  226. package/src/versionsSnapshot.ts +93 -0
  227. package/src/workspaceManifest.test.ts +220 -0
  228. package/src/workspaceManifest.ts +100 -0
@@ -0,0 +1,51 @@
1
+ // Backported to node 22.13: allowed in eslint.config.js.
2
+ import { styleText } from "node:util";
3
+ // One hue for package identifiers, the scope a shade deeper than the name it
4
+ // prefixes. `styleText` only knows the 16 named colours, hence the raw codes.
5
+ const palette = {
6
+ scope: { hex: "#d75f00", ansi256: 166 },
7
+ packageName: { hex: "#d7875f", ansi256: 173 },
8
+ };
9
+ /**
10
+ * `styleText`'s own stream detection always looks at `process.stdout`, and bun
11
+ * has emitted escape codes regardless of it in the past. The bins run under
12
+ * both runtimes and also report on stderr, so the decision is taken here and
13
+ * `styleText` is called with `validateStream: false`.
14
+ */
15
+ export const shouldColorize = (stream = process.stdout) => {
16
+ const { FORCE_COLOR, NO_COLOR, TERM } = process.env;
17
+ if (FORCE_COLOR !== undefined && FORCE_COLOR !== "" && FORCE_COLOR !== "0") {
18
+ return true;
19
+ }
20
+ if (NO_COLOR !== undefined && NO_COLOR !== "")
21
+ return false;
22
+ if (TERM === "dumb")
23
+ return false;
24
+ return stream.isTTY === true;
25
+ };
26
+ const plain = (_styles, text) => text;
27
+ const isPaletteStyle = (style) => style in palette;
28
+ // 24-bit when the terminal advertises it, the 256-colour approximation
29
+ // otherwise.
30
+ const paletteCode = (style) => {
31
+ const { hex, ansi256 } = palette[style];
32
+ const { COLORTERM } = process.env;
33
+ if (COLORTERM !== "truecolor" && COLORTERM !== "24bit") {
34
+ return `38;5;${ansi256}`;
35
+ }
36
+ const channels = [1, 3, 5].map((start) => Number.parseInt(hex.slice(start, start + 2), 16));
37
+ return `38;2;${channels.join(";")}`;
38
+ };
39
+ // The palette colour wraps whatever `styleText` produced: its own reset (`22m`
40
+ // for bold) leaves the colour standing, where the reverse order would not.
41
+ const styled = (styles, text) => {
42
+ const requested = Array.isArray(styles) ? styles : [styles];
43
+ const named = requested.filter((style) => !isPaletteStyle(style));
44
+ const inner = named.length > 0 ? styleText(named, text, { validateStream: false }) : text;
45
+ const paletteStyle = requested.find(isPaletteStyle);
46
+ return paletteStyle === undefined
47
+ ? inner
48
+ : `\u001B[${paletteCode(paletteStyle)}m${inner}\u001B[39m`;
49
+ };
50
+ export const createColorize = (enabled) => enabled ? styled : plain;
51
+ //# sourceMappingURL=reportColors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportColors.js","sourceRoot":"","sources":["../src/reportColors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { afterEach, describe, it } from "bun:test";
2
+ import { ok, strictEqual } from "node:assert/strict";
3
+ import { createColorize, shouldColorize } from "./reportColors.js";
4
+ const environment = { ...process.env };
5
+ afterEach(() => {
6
+ process.env = { ...environment };
7
+ });
8
+ describe("createColorize", () => {
9
+ it("returns the text untouched when disabled", () => {
10
+ strictEqual(createColorize(false)(["bold", "cyan"], "metro"), "metro");
11
+ });
12
+ it("wraps the text when enabled", () => {
13
+ const wrapped = createColorize(true)("dim", "metro");
14
+ ok(wrapped.includes("metro"));
15
+ ok(wrapped.length > "metro".length);
16
+ });
17
+ });
18
+ describe("shouldColorize", () => {
19
+ it("follows the stream when nothing forces the decision", () => {
20
+ delete process.env.FORCE_COLOR;
21
+ delete process.env.NO_COLOR;
22
+ delete process.env.TERM;
23
+ strictEqual(shouldColorize({ isTTY: true }), true);
24
+ strictEqual(shouldColorize({ isTTY: false }), false);
25
+ strictEqual(shouldColorize({}), false);
26
+ });
27
+ it("honours FORCE_COLOR over a piped stream", () => {
28
+ delete process.env.NO_COLOR;
29
+ process.env.FORCE_COLOR = "1";
30
+ strictEqual(shouldColorize({ isTTY: false }), true);
31
+ });
32
+ it("ignores FORCE_COLOR=0", () => {
33
+ delete process.env.NO_COLOR;
34
+ process.env.FORCE_COLOR = "0";
35
+ strictEqual(shouldColorize({ isTTY: false }), false);
36
+ });
37
+ it("honours NO_COLOR over a TTY", () => {
38
+ delete process.env.FORCE_COLOR;
39
+ process.env.NO_COLOR = "1";
40
+ strictEqual(shouldColorize({ isTTY: true }), false);
41
+ });
42
+ it("refuses a dumb terminal", () => {
43
+ delete process.env.FORCE_COLOR;
44
+ delete process.env.NO_COLOR;
45
+ process.env.TERM = "dumb";
46
+ strictEqual(shouldColorize({ isTTY: true }), false);
47
+ });
48
+ });
49
+ //# sourceMappingURL=reportColors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportColors.test.js","sourceRoot":"","sources":["../src/reportColors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The wording every report shares. Kept in one place so `why-duplicate` and the
3
+ * summary of a dedupe run cannot drift on how they count versions: both mean
4
+ * "this many remain", never "this many were merged".
5
+ */
6
+ export const plural = (count, singular, pluralForm) => `${count} ${count === 1 ? singular : pluralForm}`;
7
+ /**
8
+ * How wide a left column may get before it starts pushing what follows off the
9
+ * right of the screen. A value longer than the budget keeps its own line width
10
+ * — the column is sized on the longest one that fits, so the outlier overflows
11
+ * alone instead of clamping the column below what every other row needs.
12
+ */
13
+ export const maxColumnWidth = 44;
14
+ // Beyond this a census stops being readable and the tail is counted instead.
15
+ export const maxItemsListed = 5;
16
+ export const columnWidth = (values, budget = maxColumnWidth) => Math.max(0, ...values.map((value) => value.length).filter((length) => length <= budget));
17
+ // padded on the plain value: the styled one carries escape codes
18
+ export const padTo = (width, value) => " ".repeat(Math.max(0, width - value.length));
19
+ /**
20
+ * `a, b, c, +2 more` — the tail counted rather than dropped, so a truncated
21
+ * list never reads as a complete one.
22
+ */
23
+ export const listText = ({ items, render = (item) => item, limit = maxItemsListed, more, }) => {
24
+ const listed = items.slice(0, limit);
25
+ const rest = items.length - listed.length;
26
+ const suffix = rest === 0 ? "" : more(rest);
27
+ return `${listed.map(render).join(", ")}${suffix}`;
28
+ };
29
+ //# sourceMappingURL=reportText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportText.js","sourceRoot":"","sources":["../src/reportText.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,KAAa,EACb,QAAgB,EAChB,UAAkB,EACV,EAAE,CAAC,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,MAAgB,EAChB,MAAM,GAAW,cAAc,EACvB,EAAE,CACV,IAAI,CAAC,GAAG,CACN,CAAC,EACD,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,IAAI,MAAM,CAAC,CAC5E,CAAC;AAEJ,iEAAiE;AACjE,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,KAAa,EAAU,EAAE,CAC5D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;AAShD;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,KAAK,EACL,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EACvB,KAAK,GAAG,cAAc,EACtB,IAAI,GACY,EAAU,EAAE;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;AACrD,CAAC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { listText, plural } from "./reportText.js";
2
+ const singleVersionNotice = (names) => {
3
+ const shown = listText({
4
+ items: names,
5
+ more: (rest) => `, +${rest} more`,
6
+ });
7
+ return names.length === 1
8
+ ? `${shown} is not duplicated. Showing its dependents:`
9
+ : `None of the ${plural(names.length, "match", "matches")} is duplicated (${shown}). Showing their dependents:`;
10
+ };
11
+ /**
12
+ * The packages a `why-duplicate` run reports on. Naming a package is asking why
13
+ * it is duplicated, and the honest answer to "it is not" is its dependents, not
14
+ * an empty report: a selection holding no duplicate is kept whole, and the
15
+ * notice says why it is being shown.
16
+ *
17
+ * `--all` asks for that unconditionally, so it never needs the fallback.
18
+ */
19
+ export const selectExplainedPackages = ({ packagesMap, filter, all, }) => {
20
+ const selected = Object.entries(packagesMap).filter(([packageName]) => filter.selects(packageName));
21
+ if (all) {
22
+ return {
23
+ packages: Object.fromEntries(selected),
24
+ title: "matches",
25
+ notice: undefined,
26
+ };
27
+ }
28
+ const duplicated = selected.filter(([, resolutions]) => resolutions.length > 1);
29
+ if (duplicated.length > 0 || selected.length === 0) {
30
+ return {
31
+ packages: Object.fromEntries(duplicated),
32
+ title: "duplicates",
33
+ notice: undefined,
34
+ };
35
+ }
36
+ return {
37
+ packages: Object.fromEntries(selected),
38
+ title: "matches",
39
+ notice: singleVersionNotice(selected.map(([packageName]) => packageName)),
40
+ };
41
+ };
42
+ //# sourceMappingURL=selectExplainedPackages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectExplainedPackages.js","sourceRoot":"","sources":["../src/selectExplainedPackages.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAsBnD,MAAM,mBAAmB,GAAG,CAAC,KAAe,EAAU,EAAE;IACtD,MAAM,KAAK,GAAG,QAAQ,CAAC;QACrB,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,IAAI,OAAO;KAClC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;QACvB,CAAC,CAAC,GAAG,KAAK,6CAA6C;QACvD,CAAC,CAAC,eAAe,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,mBAAmB,KAAK,8BAA8B,CAAC;AACpH,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAErC,EACA,WAAW,EACX,MAAM,EACN,GAAG,GACyC,EAA2C,EAAE;IACzF,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CACpE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAC5B,CAAC;IAEF,IAAI,GAAG,EAAE,CAAC;QACR,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC;YACtC,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAC5C,CAAC;IAEF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC;YACxC,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC;QACtC,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC;KAC1E,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { describe, it } from "bun:test";
2
+ import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
3
+ import { createPackageFilter } from "./createPackageFilter.js";
4
+ import { selectExplainedPackages } from "./selectExplainedPackages.js";
5
+ const packagesMap = {
6
+ metro: ["metro@0.84.5", "metro@0.87.0"],
7
+ lodash: ["lodash@4.17.21"],
8
+ semver: ["semver@7.6.0"],
9
+ };
10
+ const select = (filterOptions, all = false) => selectExplainedPackages({
11
+ packagesMap,
12
+ filter: createPackageFilter(filterOptions),
13
+ all,
14
+ });
15
+ describe("selectExplainedPackages", () => {
16
+ it("keeps only the duplicated packages of the selection", () => {
17
+ const { packages, title, notice } = select({});
18
+ deepStrictEqual(Object.keys(packages), ["metro"]);
19
+ strictEqual(title, "duplicates");
20
+ strictEqual(notice, undefined);
21
+ });
22
+ it("keeps a named package that is not duplicated, to show its dependents", () => {
23
+ const { packages, title, notice } = select({ include: ["lodash"] });
24
+ deepStrictEqual(Object.keys(packages), ["lodash"]);
25
+ strictEqual(title, "matches");
26
+ strictEqual(notice, "lodash is not duplicated. Showing its dependents:");
27
+ });
28
+ it("names every match when several are selected and none is duplicated", () => {
29
+ const { packages, notice } = select({ include: ["lodash", "semver"] });
30
+ deepStrictEqual(Object.keys(packages), ["lodash", "semver"]);
31
+ ok(notice?.startsWith("None of the 2 matches is duplicated"));
32
+ ok(notice?.includes("lodash, semver"));
33
+ });
34
+ it("drops the single-version matches as soon as one selection is duplicated", () => {
35
+ const { packages, title, notice } = select({
36
+ include: ["lodash", "metro"],
37
+ });
38
+ deepStrictEqual(Object.keys(packages), ["metro"]);
39
+ strictEqual(title, "duplicates");
40
+ strictEqual(notice, undefined);
41
+ });
42
+ it("reports nothing for a package the lockfile does not hold", () => {
43
+ const { packages, title, notice } = select({ include: ["missing"] });
44
+ deepStrictEqual(Object.keys(packages), []);
45
+ strictEqual(title, "duplicates");
46
+ strictEqual(notice, undefined);
47
+ });
48
+ it("keeps every match under --all without a notice", () => {
49
+ const { packages, title, notice } = select({}, true);
50
+ deepStrictEqual(Object.keys(packages), ["metro", "lodash", "semver"]);
51
+ strictEqual(title, "matches");
52
+ strictEqual(notice, undefined);
53
+ });
54
+ });
55
+ //# sourceMappingURL=selectExplainedPackages.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectExplainedPackages.test.js","sourceRoot":"","sources":["../src/selectExplainedPackages.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import semver from "semver";
2
+ // A selector semver cannot read (`workspace:`, a git url) says nothing about the
3
+ // version, so it is no reason to drop the override. `semver.satisfies` answers
4
+ // `false` rather than throwing on one, hence the explicit check.
5
+ const accepts = (version, range) => semver.validRange(range) === null ||
6
+ semver.satisfies(version, range, { includePrerelease: true });
7
+ /**
8
+ * Split the planned overrides for a package manager whose overrides apply to
9
+ * every requester of the package, whatever range it declares (bun's
10
+ * `overrides`, pnpm's plain ones). Such an override cannot spare the dependents
11
+ * a conditional one would — pnpm's convergence overrides only repoint the edges
12
+ * that accept the version — so one a third-party range rejects would force that
13
+ * dependent onto a version it never allowed.
14
+ *
15
+ * The `converge` overrides are safe by construction: the detector only converges
16
+ * a member no third-party range holds elsewhere. A `reuse` override is derived
17
+ * from a single requester's range, and another requester can still reject it.
18
+ */
19
+ export const partitionUnconditionalOverrides = (fixes, overrides) => {
20
+ const constraints = fixes.flatMap((fix) => fix.externalConstraints);
21
+ const safe = [];
22
+ const rejected = [];
23
+ for (const override of overrides) {
24
+ const rejectedBy = constraints.filter((constraint) => constraint.packageName === override.packageName &&
25
+ constraint.requesterName !== undefined &&
26
+ !accepts(override.version, constraint.range));
27
+ if (rejectedBy.length === 0) {
28
+ safe.push(override);
29
+ }
30
+ else {
31
+ rejected.push({ override, rejectedBy });
32
+ }
33
+ }
34
+ return { safe, rejected };
35
+ };
36
+ //# sourceMappingURL=unconditionalOverrides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unconditionalOverrides.js","sourceRoot":"","sources":["../src/unconditionalOverrides.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAiB5B,iFAAiF;AACjF,+EAA+E;AAC/E,iEAAiE;AACjE,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,KAAa,EAAW,EAAE,CAC1D,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,IAAI;IACjC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAC7C,KAAmB,EACnB,SAA4B,EACN,EAAE;IACxB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACpE,MAAM,IAAI,GAAsB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAuB,EAAE,CAAC;IAExC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CACnC,CAAC,UAAU,EAAE,EAAE,CACb,UAAU,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW;YAC/C,UAAU,CAAC,aAAa,KAAK,SAAS;YACtC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,CAC/C,CAAC;QACF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { partitionUnconditionalOverrides } from "./unconditionalOverrides.js";
3
+ const constraint = (range) => ({
4
+ requester: "requester",
5
+ requesterName: "requester",
6
+ packageName: "lib",
7
+ range,
8
+ });
9
+ const workspaceConstraint = (range) => ({
10
+ ...constraint(range),
11
+ requester: "package.json in dependencies",
12
+ requesterName: undefined,
13
+ });
14
+ // only `externalConstraints` is read; the rest is an empty cluster
15
+ const fixWith = (constraints) => ({
16
+ members: ["lib"],
17
+ duplicatedMembers: ["lib"],
18
+ memberVersions: {},
19
+ target: null,
20
+ direction: "none",
21
+ convergentMembers: [],
22
+ driverMembers: [],
23
+ excludedMembers: [],
24
+ anchor: null,
25
+ reuseFixes: [],
26
+ floatingMembers: [],
27
+ workspaceChanges: [],
28
+ reResolutionSet: [],
29
+ externalConstraints: constraints,
30
+ needsRoundTrip: false,
31
+ applicable: false,
32
+ });
33
+ const override = {
34
+ packageName: "lib",
35
+ version: "2.0.0",
36
+ reason: "converge",
37
+ };
38
+ describe("partitionUnconditionalOverrides", () => {
39
+ it("rejects an override a third-party range does not accept", () => {
40
+ const { safe, rejected } = partitionUnconditionalOverrides([fixWith([constraint("^1.0.0")])], [override]);
41
+ expect(safe).toEqual([]);
42
+ expect(rejected).toEqual([
43
+ { override, rejectedBy: [constraint("^1.0.0")] },
44
+ ]);
45
+ });
46
+ it("keeps an override every range accepts", () => {
47
+ const { safe, rejected } = partitionUnconditionalOverrides([fixWith([constraint("^2.0.0")])], [override]);
48
+ expect(safe).toEqual([override]);
49
+ expect(rejected).toEqual([]);
50
+ });
51
+ // `semver.satisfies` answers `false` for a range it cannot read rather than
52
+ // throwing, so a selector naming something other than a version — which says
53
+ // nothing about the version at all — would otherwise reject every override.
54
+ it("keeps an override against a selector semver cannot read", () => {
55
+ const { safe, rejected } = partitionUnconditionalOverrides([
56
+ fixWith([
57
+ constraint("workspace:*"),
58
+ constraint("git+ssh://git@host/r.git#v1"),
59
+ constraint("catalog:default"),
60
+ ]),
61
+ ], [override]);
62
+ expect(safe).toEqual([override]);
63
+ expect(rejected).toEqual([]);
64
+ });
65
+ // A workspace range belongs to the user, who is the one asking for the dedupe.
66
+ it("ignores a workspace constraint", () => {
67
+ const { safe } = partitionUnconditionalOverrides([fixWith([workspaceConstraint("^1.0.0")])], [override]);
68
+ expect(safe).toEqual([override]);
69
+ });
70
+ });
71
+ //# sourceMappingURL=unconditionalOverrides.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unconditionalOverrides.test.js","sourceRoot":"","sources":["../src/unconditionalOverrides.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import semver from "semver";
2
+ const versionLabel = (pkg) => pkg.type === "npm" && pkg.version !== undefined && pkg.version !== ""
3
+ ? pkg.version
4
+ : pkg.resolution;
5
+ const compareVersions = (a, b) => semver.valid(a) !== null && semver.valid(b) !== null
6
+ ? semver.compare(a, b)
7
+ : a.localeCompare(b);
8
+ export const buildVersionsSnapshot = (packages) => {
9
+ const snapshot = new Map();
10
+ for (const pkg of packages) {
11
+ const version = versionLabel(pkg);
12
+ const versions = snapshot.get(pkg.name);
13
+ if (versions === undefined) {
14
+ snapshot.set(pkg.name, [version]);
15
+ }
16
+ else if (!versions.includes(version)) {
17
+ versions.push(version);
18
+ }
19
+ }
20
+ for (const versions of snapshot.values())
21
+ versions.sort(compareVersions);
22
+ return snapshot;
23
+ };
24
+ /**
25
+ * What the two snapshots say a run collapsed, one entry per package: both sides
26
+ * whole, so a family converging on an older release reads as the two copies
27
+ * becoming one rather than as a downgrade.
28
+ *
29
+ * Only a package resolved fewer times than before is reported. A version that
30
+ * moved without a copy going away is a re-resolution, not a dedupe — the pass
31
+ * that made the edit already said so — and a package that left the lockfile
32
+ * entirely is neither: the in-memory rewrite drops the private subtrees of the
33
+ * versions it replaced, entries the next install resolves again.
34
+ */
35
+ export const diffVersionsSnapshots = (before, after) => {
36
+ const deduped = [];
37
+ for (const [packageName, versions] of before) {
38
+ const remaining = after.get(packageName);
39
+ if (remaining === undefined || remaining.length === 0)
40
+ continue;
41
+ if (remaining.length >= versions.length)
42
+ continue;
43
+ deduped.push({ packageName, before: versions, after: remaining });
44
+ }
45
+ return deduped.toSorted((a, b) => a.packageName.localeCompare(b.packageName));
46
+ };
47
+ // Packages the lockfile still resolves more than once.
48
+ export const countDuplicatedPackages = (snapshot) => [...snapshot.values()].filter((versions) => versions.length > 1).length;
49
+ //# sourceMappingURL=versionsSnapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versionsSnapshot.js","sourceRoot":"","sources":["../src/versionsSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAwB5B,MAAM,YAAY,GAAG,CAAC,GAAoB,EAAU,EAAE,CACpD,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,EAAE;IACnE,CAAC,CAAC,GAAG,CAAC,OAAO;IACb,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;AAErB,MAAM,eAAe,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CACvD,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;IAClD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAEzB,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,QAAmC,EACjB,EAAE;IACpB,MAAM,QAAQ,GAAqB,IAAI,GAAG,EAAE,CAAC;IAE7C,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACpC,CAAC;aAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAEzE,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAUF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,MAAwB,EACxB,KAAuB,EACL,EAAE;IACpB,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC;QAC7C,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAChE,IAAI,SAAS,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;YAAE,SAAS;QAElD,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF,uDAAuD;AACvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAA0B,EAAU,EAAE,CAC5E,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { describe, it } from "bun:test";
2
+ import { deepStrictEqual, strictEqual } from "node:assert/strict";
3
+ import { buildVersionsSnapshot, countDuplicatedPackages, diffVersionsSnapshots, } from "./versionsSnapshot.js";
4
+ const npm = (name, version) => ({
5
+ type: "npm",
6
+ name,
7
+ version,
8
+ resolution: `${name}@${version}`,
9
+ });
10
+ const snapshot = (entries) => new Map(Object.entries(entries));
11
+ describe("buildVersionsSnapshot", () => {
12
+ it("groups versions per package, sorted by semver", () => {
13
+ deepStrictEqual([
14
+ ...buildVersionsSnapshot([
15
+ npm("semver", "7.10.0"),
16
+ npm("semver", "7.9.0"),
17
+ npm("lodash", "4.17.21"),
18
+ ]),
19
+ ], [
20
+ ["semver", ["7.9.0", "7.10.0"]],
21
+ ["lodash", ["4.17.21"]],
22
+ ]);
23
+ });
24
+ it("counts one version per resolution, whatever it is installed at", () => {
25
+ deepStrictEqual(buildVersionsSnapshot([npm("semver", "7.9.0"), npm("semver", "7.9.0")]), snapshot({ semver: ["7.9.0"] }));
26
+ });
27
+ it("identifies a non-npm package by its resolution", () => {
28
+ deepStrictEqual(buildVersionsSnapshot([
29
+ { type: "git", name: "psc", resolution: "psc@git:abcdef" },
30
+ ]), snapshot({ psc: ["psc@git:abcdef"] }));
31
+ });
32
+ });
33
+ describe("diffVersionsSnapshots", () => {
34
+ it("keeps both sides whole, so the collapse reads as one", () => {
35
+ deepStrictEqual(diffVersionsSnapshots(snapshot({ "range-parser": ["1.2.1", "1.3.0"] }), snapshot({ "range-parser": ["1.2.1"] })), [
36
+ {
37
+ packageName: "range-parser",
38
+ before: ["1.2.1", "1.3.0"],
39
+ after: ["1.2.1"],
40
+ },
41
+ ]);
42
+ });
43
+ it("reports a package only partly collapsed with what is left", () => {
44
+ deepStrictEqual(diffVersionsSnapshots(snapshot({ semver: ["7.5.4", "7.6.0", "7.7.1"] }), snapshot({ semver: ["7.6.0", "7.7.1"] })), [
45
+ {
46
+ packageName: "semver",
47
+ before: ["7.5.4", "7.6.0", "7.7.1"],
48
+ after: ["7.6.0", "7.7.1"],
49
+ },
50
+ ]);
51
+ });
52
+ it("says nothing of a package whose versions did not move", () => {
53
+ deepStrictEqual(diffVersionsSnapshots(snapshot({ semver: ["7.7.1"] }), snapshot({ semver: ["7.7.1"], lodash: ["4.17.21"] })), []);
54
+ });
55
+ // the cluster pass that made the edit reports it; no copy went away, so it is
56
+ // not a dedupe
57
+ it("says nothing of a version that moved without a copy going away", () => {
58
+ deepStrictEqual(diffVersionsSnapshots(snapshot({ metro: ["0.84.5"] }), snapshot({ metro: ["0.87.0"] })), []);
59
+ });
60
+ // the rewritten copy drops the private subtrees of the versions it replaced,
61
+ // and the next install resolves them again: gone from the lockfile is not
62
+ // deduplicated
63
+ it("says nothing of a package that left the lockfile entirely", () => {
64
+ deepStrictEqual(diffVersionsSnapshots(snapshot({ "metro-config": ["0.84.5"] }), snapshot({})), []);
65
+ });
66
+ it("sorts the entries by package name", () => {
67
+ deepStrictEqual(diffVersionsSnapshots(snapshot({ semver: ["1.0.0", "2.0.0"], lodash: ["3.0.0", "4.0.0"] }), snapshot({ semver: ["2.0.0"], lodash: ["4.0.0"] })).map((entry) => entry.packageName), ["lodash", "semver"]);
68
+ });
69
+ });
70
+ describe("countDuplicatedPackages", () => {
71
+ it("counts the packages resolved more than once", () => {
72
+ strictEqual(countDuplicatedPackages(snapshot({
73
+ semver: ["7.5.4", "7.7.1"],
74
+ lodash: ["4.17.21"],
75
+ react: ["18.0.0", "19.0.0"],
76
+ })), 2);
77
+ });
78
+ });
79
+ //# sourceMappingURL=versionsSnapshot.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versionsSnapshot.test.js","sourceRoot":"","sources":["../src/versionsSnapshot.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,OAAe,EAAmB,EAAE,CAAC,CAAC;IAC/D,IAAI,EAAE,KAAK;IACX,IAAI;IACJ,OAAO;IACP,UAAU,EAAE,GAAG,IAAI,IAAI,OAAO,EAAE;CACjC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,CAAC,OAAiC,EAAoB,EAAE,CACvE,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AAEnC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,eAAe,CACb;YACE,GAAG,qBAAqB,CAAC;gBACvB,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC;gBACvB,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC;gBACtB,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC;aACzB,CAAC;SACH,EACD;YACE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC/B,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC;SACxB,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,eAAe,CACb,qBAAqB,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EACvE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAChC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,eAAe,CACb,qBAAqB,CAAC;YACpB,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE;SAC3D,CAAC,EACF,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CACtC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,eAAe,CACb,qBAAqB,CACnB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,EAChD,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CACxC,EACD;YACE;gBACE,WAAW,EAAE,cAAc;gBAC3B,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;gBAC1B,KAAK,EAAE,CAAC,OAAO,CAAC;aACjB;SACF,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,eAAe,CACb,qBAAqB,CACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,EACjD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CACzC,EACD;YACE;gBACE,WAAW,EAAE,QAAQ;gBACrB,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;gBACnC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;aAC1B;SACF,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,eAAe,CACb,qBAAqB,CACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAC/B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CACrD,EACD,EAAE,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,eAAe;IACf,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,eAAe,CACb,qBAAqB,CACnB,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAC/B,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAChC,EACD,EAAE,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,0EAA0E;IAC1E,eAAe;IACf,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,eAAe,CACb,qBAAqB,CACnB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EACxC,QAAQ,CAAC,EAAE,CAAC,CACb,EACD,EAAE,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,eAAe,CACb,qBAAqB,CACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,EACpE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CACnD,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,EACnC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CACrB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,WAAW,CACT,uBAAuB,CACrB,QAAQ,CAAC;YACP,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;YAC1B,MAAM,EAAE,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;SAC5B,CAAC,CACH,EACD,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,63 @@
1
+ import semver from "semver";
2
+ import { PackageDependencyDescriptorUtils } from "./packageDependenciesUtils.js";
3
+ /**
4
+ * Keep the user's range style when it still expresses the same intent: a caret
5
+ * or tilde range moved to the target line stays a caret or tilde range. Any
6
+ * other shape (`*`, `0.83 - 0.86`, `>=1`) has no target-line equivalent, so the
7
+ * exact version is the only faithful rewrite.
8
+ */
9
+ export const nextSelector = (range, to) => {
10
+ const prefix = range.slice(0, 1);
11
+ return (prefix === "^" || prefix === "~") && semver.valid(range.slice(1))
12
+ ? `${prefix}${to}`
13
+ : to;
14
+ };
15
+ const escapeRegExp = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
16
+ const findDepTypeBlock = (content, depType) => {
17
+ const header = new RegExp(`"${escapeRegExp(depType)}"\\s*:\\s*\\{`, "u").exec(content);
18
+ if (!header)
19
+ return undefined;
20
+ const start = header.index + header[0].length;
21
+ let depth = 1;
22
+ for (let index = start; index < content.length; index++) {
23
+ const char = content[index];
24
+ if (char === "{")
25
+ depth++;
26
+ if (char === "}") {
27
+ depth--;
28
+ if (depth === 0)
29
+ return { start, end: index };
30
+ }
31
+ }
32
+ return undefined;
33
+ };
34
+ const manifestKeyOf = (deps, packageName, range) => Object.entries(deps).find(([key, value]) => {
35
+ const parsed = PackageDependencyDescriptorUtils.parse(key, value);
36
+ return parsed.npmName === packageName && parsed.selector === range;
37
+ })?.[0];
38
+ /**
39
+ * Rewrite one declared range in a workspace `package.json`, as a targeted edit
40
+ * on the raw text: reserializing would reformat a file the tool does not own.
41
+ * Returns `undefined` when the declaration is not there any more, which is the
42
+ * caller's signal that the lockfile it read from is stale.
43
+ */
44
+ export const applyWorkspaceRangeEdit = (content, edit) => {
45
+ const manifest = JSON.parse(content);
46
+ const deps = manifest[edit.depType];
47
+ if (!deps)
48
+ return undefined;
49
+ const manifestKey = manifestKeyOf(deps, edit.packageName, edit.range);
50
+ if (manifestKey === undefined)
51
+ return undefined;
52
+ const block = findDepTypeBlock(content, edit.depType);
53
+ if (!block)
54
+ return undefined;
55
+ const [, declared] = PackageDependencyDescriptorUtils.stringify(PackageDependencyDescriptorUtils.make(PackageDependencyDescriptorUtils.parse(manifestKey, deps[manifestKey]), nextSelector(edit.range, edit.to)));
56
+ const declaration = new RegExp(`("${escapeRegExp(manifestKey)}"\\s*:\\s*)"[^"]*"`, "u");
57
+ const slice = content.slice(block.start, block.end);
58
+ const updated = slice.replace(declaration, `$1${JSON.stringify(declared)}`);
59
+ if (updated === slice)
60
+ return undefined;
61
+ return content.slice(0, block.start) + updated + content.slice(block.end);
62
+ };
63
+ //# sourceMappingURL=workspaceManifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaceManifest.js","sourceRoot":"","sources":["../src/workspaceManifest.ts"],"names":[],"mappings":"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"}