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,220 @@
1
+ import { describe, it } from "bun:test";
2
+ import { strictEqual } from "node:assert/strict";
3
+ import { applyWorkspaceRangeEdit, nextSelector } from "./workspaceManifest.ts";
4
+
5
+ const manifest = [
6
+ "{",
7
+ ' "name": "root",',
8
+ ' "dependencies": {',
9
+ ' "metro": "0.84.5"',
10
+ " },",
11
+ ' "devDependencies": {',
12
+ ' "metro": "^0.84.5",',
13
+ ' "lodash.merge": "~4.6.0",',
14
+ ' "aliased": "npm:metro@^0.84.5"',
15
+ " }",
16
+ "}",
17
+ "",
18
+ ].join("\n");
19
+
20
+ // a block whose other declarations carry protocols the parser has no list for:
21
+ // they have to parse so the target rewrite is not aborted by a neighbour
22
+ const manifestWithProtocols = [
23
+ "{",
24
+ ' "name": "root",',
25
+ ' "dependencies": {',
26
+ ' "local": "workspace:^1.0.0",',
27
+ ' "vendored": "someproto:whatever",',
28
+ ' "forked": "git+ssh://git@host/r.git#v1",',
29
+ ' "metro": "^0.84.5"',
30
+ " }",
31
+ "}",
32
+ "",
33
+ ].join("\n");
34
+
35
+ describe("nextSelector", () => {
36
+ it("keeps a caret or tilde on the target line", () => {
37
+ strictEqual(nextSelector("^0.84.5", "0.87.0"), "^0.87.0");
38
+ strictEqual(nextSelector("~4.6.0", "4.7.1"), "~4.7.1");
39
+ });
40
+
41
+ it("pins exactly when the range has no target-line equivalent", () => {
42
+ strictEqual(nextSelector("0.84.5", "0.87.0"), "0.87.0");
43
+ strictEqual(nextSelector("*", "0.87.0"), "0.87.0");
44
+ strictEqual(nextSelector("0.83 - 0.86", "0.87.0"), "0.87.0");
45
+ strictEqual(nextSelector("^0.83 - 0.86", "0.87.0"), "0.87.0");
46
+ });
47
+ });
48
+
49
+ describe("applyWorkspaceRangeEdit", () => {
50
+ it("edits only the declaration in the requested depType", () => {
51
+ strictEqual(
52
+ applyWorkspaceRangeEdit(manifest, {
53
+ packageName: "metro",
54
+ depType: "devDependencies",
55
+ range: "^0.84.5",
56
+ to: "0.87.0",
57
+ }),
58
+ manifest.replace('"metro": "^0.84.5"', '"metro": "^0.87.0"'),
59
+ );
60
+ });
61
+
62
+ it("edits the exact pin in dependencies without touching devDependencies", () => {
63
+ strictEqual(
64
+ applyWorkspaceRangeEdit(manifest, {
65
+ packageName: "metro",
66
+ depType: "dependencies",
67
+ range: "0.84.5",
68
+ to: "0.87.0",
69
+ }),
70
+ manifest.replace('"metro": "0.84.5"', '"metro": "0.87.0"'),
71
+ );
72
+ });
73
+
74
+ it("keeps the npm: alias prefix", () => {
75
+ strictEqual(
76
+ applyWorkspaceRangeEdit(manifest, {
77
+ packageName: "metro",
78
+ depType: "devDependencies",
79
+ range: "^0.84.5",
80
+ to: "0.87.0",
81
+ })?.includes('"aliased": "npm:metro@^0.84.5"'),
82
+ true,
83
+ );
84
+
85
+ strictEqual(
86
+ applyWorkspaceRangeEdit(manifest, {
87
+ packageName: "metro",
88
+ depType: "devDependencies",
89
+ range: "^0.84.5",
90
+ to: "0.87.0",
91
+ })?.includes('"metro": "^0.87.0"'),
92
+ true,
93
+ );
94
+ });
95
+
96
+ it("matches a dotted name literally", () => {
97
+ strictEqual(
98
+ applyWorkspaceRangeEdit(manifest, {
99
+ packageName: "lodash.merge",
100
+ depType: "devDependencies",
101
+ range: "~4.6.0",
102
+ to: "4.7.1",
103
+ }),
104
+ manifest.replace('"lodash.merge": "~4.6.0"', '"lodash.merge": "~4.7.1"'),
105
+ );
106
+ });
107
+
108
+ it("rewrites past siblings declared with protocols it does not know", () => {
109
+ strictEqual(
110
+ applyWorkspaceRangeEdit(manifestWithProtocols, {
111
+ packageName: "metro",
112
+ depType: "dependencies",
113
+ range: "^0.84.5",
114
+ to: "0.87.0",
115
+ }),
116
+ manifestWithProtocols.replace('"metro": "^0.84.5"', '"metro": "^0.87.0"'),
117
+ );
118
+ });
119
+
120
+ // the range reaches here already stripped of its protocol, and `stringify`
121
+ // puts it back: rewriting `workspace:^1.0.0` to a bare `2.0.0` would change
122
+ // which package the declaration points at
123
+ it("keeps a workspace protocol on the rewritten declaration", () => {
124
+ strictEqual(
125
+ applyWorkspaceRangeEdit(manifestWithProtocols, {
126
+ packageName: "local",
127
+ depType: "dependencies",
128
+ range: "^1.0.0",
129
+ to: "2.0.0",
130
+ }),
131
+ manifestWithProtocols.replace(
132
+ '"local": "workspace:^1.0.0"',
133
+ '"local": "workspace:^2.0.0"',
134
+ ),
135
+ );
136
+ });
137
+
138
+ it("edits an aliased declaration through its own key", () => {
139
+ strictEqual(
140
+ applyWorkspaceRangeEdit(
141
+ [
142
+ "{",
143
+ ' "dependencies": {',
144
+ ' "aliased": "npm:metro@^0.84.5"',
145
+ " }",
146
+ "}",
147
+ "",
148
+ ].join("\n"),
149
+ {
150
+ packageName: "metro",
151
+ depType: "dependencies",
152
+ range: "^0.84.5",
153
+ to: "0.87.0",
154
+ },
155
+ )?.includes('"aliased": "npm:metro@^0.87.0"'),
156
+ true,
157
+ );
158
+ });
159
+
160
+ // The manifest key `typescript` is an alias for another package entirely,
161
+ // while the real `typescript` is reached through `@typescript/native`. Keying
162
+ // the rewrite on the package name would edit the wrong line in both
163
+ // directions.
164
+ it("edits the key an alias reaches the package through, not its namesake", () => {
165
+ const swapped = [
166
+ "{",
167
+ ' "devDependencies": {',
168
+ ' "@typescript/native": "npm:typescript@^7.0.0",',
169
+ ' "typescript": "npm:@typescript/typescript6@6.0.2"',
170
+ " }",
171
+ "}",
172
+ "",
173
+ ].join("\n");
174
+
175
+ strictEqual(
176
+ applyWorkspaceRangeEdit(swapped, {
177
+ packageName: "typescript",
178
+ depType: "devDependencies",
179
+ range: "^7.0.0",
180
+ to: "7.1.0",
181
+ }),
182
+ swapped.replace('"npm:typescript@^7.0.0"', '"npm:typescript@^7.1.0"'),
183
+ );
184
+
185
+ strictEqual(
186
+ applyWorkspaceRangeEdit(swapped, {
187
+ packageName: "@typescript/typescript6",
188
+ depType: "devDependencies",
189
+ range: "6.0.2",
190
+ to: "6.1.0",
191
+ }),
192
+ swapped.replace(
193
+ '"npm:@typescript/typescript6@6.0.2"',
194
+ '"npm:@typescript/typescript6@6.1.0"',
195
+ ),
196
+ );
197
+ });
198
+
199
+ it("reports a declaration that moved on since the lockfile was read", () => {
200
+ strictEqual(
201
+ applyWorkspaceRangeEdit(manifest, {
202
+ packageName: "metro",
203
+ depType: "devDependencies",
204
+ range: "^0.80.0",
205
+ to: "0.87.0",
206
+ }),
207
+ undefined,
208
+ );
209
+
210
+ strictEqual(
211
+ applyWorkspaceRangeEdit(manifest, {
212
+ packageName: "metro",
213
+ depType: "optionalDependencies",
214
+ range: "^0.84.5",
215
+ to: "0.87.0",
216
+ }),
217
+ undefined,
218
+ );
219
+ });
220
+ });
@@ -0,0 +1,100 @@
1
+ import semver from "semver";
2
+ import { PackageDependencyDescriptorUtils } from "./packageDependenciesUtils.ts";
3
+
4
+ export interface WorkspaceRangeEdit {
5
+ // npm name of the dependency, which is not the manifest key for an alias
6
+ packageName: string;
7
+ depType: string;
8
+ // the selector currently declared, as `collectDependentRanges` read it
9
+ range: string;
10
+ to: string;
11
+ }
12
+
13
+ /**
14
+ * Keep the user's range style when it still expresses the same intent: a caret
15
+ * or tilde range moved to the target line stays a caret or tilde range. Any
16
+ * other shape (`*`, `0.83 - 0.86`, `>=1`) has no target-line equivalent, so the
17
+ * exact version is the only faithful rewrite.
18
+ */
19
+ export const nextSelector = (range: string, to: string): string => {
20
+ const prefix = range.slice(0, 1);
21
+ return (prefix === "^" || prefix === "~") && semver.valid(range.slice(1))
22
+ ? `${prefix}${to}`
23
+ : to;
24
+ };
25
+
26
+ const escapeRegExp = (value: string): string =>
27
+ value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
28
+
29
+ const findDepTypeBlock = (
30
+ content: string,
31
+ depType: string,
32
+ ): { start: number; end: number } | undefined => {
33
+ const header = new RegExp(`"${escapeRegExp(depType)}"\\s*:\\s*\\{`, "u").exec(
34
+ content,
35
+ );
36
+ if (!header) return undefined;
37
+
38
+ const start = header.index + header[0].length;
39
+ let depth = 1;
40
+ for (let index = start; index < content.length; index++) {
41
+ const char = content[index];
42
+ if (char === "{") depth++;
43
+ if (char === "}") {
44
+ depth--;
45
+ if (depth === 0) return { start, end: index };
46
+ }
47
+ }
48
+ return undefined;
49
+ };
50
+
51
+ const manifestKeyOf = (
52
+ deps: Record<string, string>,
53
+ packageName: string,
54
+ range: string,
55
+ ): string | undefined =>
56
+ Object.entries(deps).find(([key, value]) => {
57
+ const parsed = PackageDependencyDescriptorUtils.parse(key, value);
58
+ return parsed.npmName === packageName && parsed.selector === range;
59
+ })?.[0];
60
+
61
+ /**
62
+ * Rewrite one declared range in a workspace `package.json`, as a targeted edit
63
+ * on the raw text: reserializing would reformat a file the tool does not own.
64
+ * Returns `undefined` when the declaration is not there any more, which is the
65
+ * caller's signal that the lockfile it read from is stale.
66
+ */
67
+ export const applyWorkspaceRangeEdit = (
68
+ content: string,
69
+ edit: WorkspaceRangeEdit,
70
+ ): string | undefined => {
71
+ const manifest = JSON.parse(content) as Record<
72
+ string,
73
+ Record<string, string> | undefined
74
+ >;
75
+ const deps = manifest[edit.depType];
76
+ if (!deps) return undefined;
77
+
78
+ const manifestKey = manifestKeyOf(deps, edit.packageName, edit.range);
79
+ if (manifestKey === undefined) return undefined;
80
+
81
+ const block = findDepTypeBlock(content, edit.depType);
82
+ if (!block) return undefined;
83
+
84
+ const [, declared] = PackageDependencyDescriptorUtils.stringify(
85
+ PackageDependencyDescriptorUtils.make(
86
+ PackageDependencyDescriptorUtils.parse(manifestKey, deps[manifestKey]!),
87
+ nextSelector(edit.range, edit.to),
88
+ ),
89
+ );
90
+
91
+ const declaration = new RegExp(
92
+ `("${escapeRegExp(manifestKey)}"\\s*:\\s*)"[^"]*"`,
93
+ "u",
94
+ );
95
+ const slice = content.slice(block.start, block.end);
96
+ const updated = slice.replace(declaration, `$1${JSON.stringify(declared)}`);
97
+ if (updated === slice) return undefined;
98
+
99
+ return content.slice(0, block.start) + updated + content.slice(block.end);
100
+ };