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,593 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ PackageDependencyDescriptorUtils,
4
+ isNpmProtocol,
5
+ isSemverComparable,
6
+ } from "./packageDependenciesUtils.ts";
7
+ import type { PackageDependencyDescriptor } from "./packageDependenciesUtils.ts";
8
+
9
+ type ParseCase = readonly [
10
+ declaration: readonly [key: string, value: string],
11
+ expected: PackageDependencyDescriptor,
12
+ ];
13
+
14
+ const npmCases: ParseCase[] = [
15
+ [
16
+ ["name", "1.1.1"],
17
+ {
18
+ key: "name",
19
+ npmName: "name",
20
+ nameDescriptor: { name: "name" },
21
+ protocol: undefined,
22
+ isAlias: false,
23
+ selector: "1.1.1",
24
+ },
25
+ ],
26
+ [
27
+ ["name", "^1.0.0"],
28
+ {
29
+ key: "name",
30
+ npmName: "name",
31
+ nameDescriptor: { name: "name" },
32
+ protocol: undefined,
33
+ isAlias: false,
34
+ selector: "^1.0.0",
35
+ },
36
+ ],
37
+ [
38
+ ["name", "1.0.0 - 2.0.0"],
39
+ {
40
+ key: "name",
41
+ npmName: "name",
42
+ nameDescriptor: { name: "name" },
43
+ protocol: undefined,
44
+ isAlias: false,
45
+ selector: "1.0.0 - 2.0.0",
46
+ },
47
+ ],
48
+ [
49
+ ["name", ">=1"],
50
+ {
51
+ key: "name",
52
+ npmName: "name",
53
+ nameDescriptor: { name: "name" },
54
+ protocol: undefined,
55
+ isAlias: false,
56
+ selector: ">=1",
57
+ },
58
+ ],
59
+ [
60
+ ["name", "*"],
61
+ {
62
+ key: "name",
63
+ npmName: "name",
64
+ nameDescriptor: { name: "name" },
65
+ protocol: undefined,
66
+ isAlias: false,
67
+ selector: "*",
68
+ },
69
+ ],
70
+ [
71
+ ["name", ""],
72
+ {
73
+ key: "name",
74
+ npmName: "name",
75
+ nameDescriptor: { name: "name" },
76
+ protocol: undefined,
77
+ isAlias: false,
78
+ selector: "",
79
+ },
80
+ ],
81
+ [
82
+ ["@scope/name", "^1.0.0"],
83
+ {
84
+ key: "@scope/name",
85
+ npmName: "@scope/name",
86
+ nameDescriptor: { scope: "scope", name: "name" },
87
+ protocol: undefined,
88
+ isAlias: false,
89
+ selector: "^1.0.0",
90
+ },
91
+ ],
92
+ // yarn's spelling: `npm:` as a plain protocol on a range, not an alias
93
+ [
94
+ ["name", "npm:^1.0.0"],
95
+ {
96
+ key: "name",
97
+ npmName: "name",
98
+ nameDescriptor: { name: "name" },
99
+ protocol: "npm",
100
+ isAlias: false,
101
+ selector: "^1.0.0",
102
+ },
103
+ ],
104
+ [
105
+ ["name", "npm:*"],
106
+ {
107
+ key: "name",
108
+ npmName: "name",
109
+ nameDescriptor: { name: "name" },
110
+ protocol: "npm",
111
+ isAlias: false,
112
+ selector: "*",
113
+ },
114
+ ],
115
+ ];
116
+
117
+ const aliasCases: ParseCase[] = [
118
+ [
119
+ ["name3", "npm:name@1.1.1"],
120
+ {
121
+ key: "name3",
122
+ npmName: "name",
123
+ nameDescriptor: { name: "name" },
124
+ protocol: "npm",
125
+ isAlias: true,
126
+ selector: "1.1.1",
127
+ },
128
+ ],
129
+ [
130
+ ["name2", "npm:@scope/name@1.1.1"],
131
+ {
132
+ key: "name2",
133
+ npmName: "@scope/name",
134
+ nameDescriptor: { scope: "scope", name: "name" },
135
+ protocol: "npm",
136
+ isAlias: true,
137
+ selector: "1.1.1",
138
+ },
139
+ ],
140
+ [
141
+ ["psc", "npm:printable-shell-command@^5.0.0"],
142
+ {
143
+ key: "psc",
144
+ npmName: "printable-shell-command",
145
+ nameDescriptor: { name: "printable-shell-command" },
146
+ protocol: "npm",
147
+ isAlias: true,
148
+ selector: "^5.0.0",
149
+ },
150
+ ],
151
+ // a version-less alias: bun's own `non-npm` fixture declares this shape
152
+ [
153
+ ["bun-types", "npm:@types/bun"],
154
+ {
155
+ key: "bun-types",
156
+ npmName: "@types/bun",
157
+ nameDescriptor: { scope: "types", name: "bun" },
158
+ protocol: "npm",
159
+ isAlias: true,
160
+ selector: "",
161
+ },
162
+ ],
163
+ [
164
+ ["tag", "npm:latest"],
165
+ {
166
+ key: "tag",
167
+ npmName: "latest",
168
+ nameDescriptor: { name: "latest" },
169
+ protocol: "npm",
170
+ isAlias: true,
171
+ selector: "",
172
+ },
173
+ ],
174
+ // an alias whose key already matches the target: `isAlias` is what keeps the
175
+ // name in the value, since `npmName === key` cannot tell the two apart
176
+ [
177
+ ["semver", "npm:semver@^6.0.0"],
178
+ {
179
+ key: "semver",
180
+ npmName: "semver",
181
+ nameDescriptor: { name: "semver" },
182
+ protocol: "npm",
183
+ isAlias: true,
184
+ selector: "^6.0.0",
185
+ },
186
+ ],
187
+ ];
188
+
189
+ const otherProtocolCases: ParseCase[] = [
190
+ [
191
+ ["name", "workspace:*"],
192
+ {
193
+ key: "name",
194
+ npmName: "name",
195
+ nameDescriptor: { name: "name" },
196
+ protocol: "workspace",
197
+ isAlias: false,
198
+ selector: "*",
199
+ },
200
+ ],
201
+ [
202
+ ["name", "workspace:^1.0.0"],
203
+ {
204
+ key: "name",
205
+ npmName: "name",
206
+ nameDescriptor: { name: "name" },
207
+ protocol: "workspace",
208
+ isAlias: false,
209
+ selector: "^1.0.0",
210
+ },
211
+ ],
212
+ [
213
+ ["name", "file:../local"],
214
+ {
215
+ key: "name",
216
+ npmName: "name",
217
+ nameDescriptor: { name: "name" },
218
+ protocol: "file",
219
+ isAlias: false,
220
+ selector: "../local",
221
+ },
222
+ ],
223
+ [
224
+ ["name", "link:../local"],
225
+ {
226
+ key: "name",
227
+ npmName: "name",
228
+ nameDescriptor: { name: "name" },
229
+ protocol: "link",
230
+ isAlias: false,
231
+ selector: "../local",
232
+ },
233
+ ],
234
+ [
235
+ ["name", "github:user/repo"],
236
+ {
237
+ key: "name",
238
+ npmName: "name",
239
+ nameDescriptor: { name: "name" },
240
+ protocol: "github",
241
+ isAlias: false,
242
+ selector: "user/repo",
243
+ },
244
+ ],
245
+ [
246
+ ["name", "github:user/repo#semver:^1"],
247
+ {
248
+ key: "name",
249
+ npmName: "name",
250
+ nameDescriptor: { name: "name" },
251
+ protocol: "github",
252
+ isAlias: false,
253
+ selector: "user/repo#semver:^1",
254
+ },
255
+ ],
256
+ [
257
+ ["name", "jsr:^1.0.0"],
258
+ {
259
+ key: "name",
260
+ npmName: "name",
261
+ nameDescriptor: { name: "name" },
262
+ protocol: "jsr",
263
+ isAlias: false,
264
+ selector: "^1.0.0",
265
+ },
266
+ ],
267
+ [
268
+ ["name", "catalog:default"],
269
+ {
270
+ key: "name",
271
+ npmName: "name",
272
+ nameDescriptor: { name: "name" },
273
+ protocol: "catalog",
274
+ isAlias: false,
275
+ selector: "default",
276
+ },
277
+ ],
278
+ [
279
+ ["name", "patch:name@npm%3A1.0.0#~/p.patch"],
280
+ {
281
+ key: "name",
282
+ npmName: "name",
283
+ nameDescriptor: { name: "name" },
284
+ protocol: "patch",
285
+ isAlias: false,
286
+ selector: "name@npm%3A1.0.0#~/p.patch",
287
+ },
288
+ ],
289
+ [
290
+ ["name", "portal:../local"],
291
+ {
292
+ key: "name",
293
+ npmName: "name",
294
+ nameDescriptor: { name: "name" },
295
+ protocol: "portal",
296
+ isAlias: false,
297
+ selector: "../local",
298
+ },
299
+ ],
300
+ // a git url keeps its whole prefix so the round trip is byte-exact
301
+ [
302
+ ["name", "git+ssh://git@host/r.git#v1"],
303
+ {
304
+ key: "name",
305
+ npmName: "name",
306
+ nameDescriptor: { name: "name" },
307
+ protocol: "git+ssh",
308
+ isAlias: false,
309
+ selector: "//git@host/r.git#v1",
310
+ },
311
+ ],
312
+ [
313
+ ["name", "git+https://host/r.git"],
314
+ {
315
+ key: "name",
316
+ npmName: "name",
317
+ nameDescriptor: { name: "name" },
318
+ protocol: "git+https",
319
+ isAlias: false,
320
+ selector: "//host/r.git",
321
+ },
322
+ ],
323
+ [
324
+ ["name", "https://registry.npmjs.org/react/-/react-18.2.0.tgz"],
325
+ {
326
+ key: "name",
327
+ npmName: "name",
328
+ nameDescriptor: { name: "name" },
329
+ protocol: "https",
330
+ isAlias: false,
331
+ selector: "//registry.npmjs.org/react/-/react-18.2.0.tgz",
332
+ },
333
+ ],
334
+ // an unrecognised protocol parses rather than throwing, and stays out of npm
335
+ [
336
+ ["name", "someproto:whatever"],
337
+ {
338
+ key: "name",
339
+ npmName: "name",
340
+ nameDescriptor: { name: "name" },
341
+ protocol: "someproto",
342
+ isAlias: false,
343
+ selector: "whatever",
344
+ },
345
+ ],
346
+ ];
347
+
348
+ // no protocol-shaped prefix at position 0: `/`, `#`, `@` and `.` all stop the
349
+ // match, so these must not acquire a protocol
350
+ const shorthandCases: ParseCase[] = [
351
+ [
352
+ ["name", "user/repo"],
353
+ {
354
+ key: "name",
355
+ npmName: "name",
356
+ nameDescriptor: { name: "name" },
357
+ protocol: undefined,
358
+ isAlias: false,
359
+ selector: "user/repo",
360
+ },
361
+ ],
362
+ [
363
+ ["name", "user/repo#semver:^1"],
364
+ {
365
+ key: "name",
366
+ npmName: "name",
367
+ nameDescriptor: { name: "name" },
368
+ protocol: undefined,
369
+ isAlias: false,
370
+ selector: "user/repo#semver:^1",
371
+ },
372
+ ],
373
+ [
374
+ ["name", "./local/path"],
375
+ {
376
+ key: "name",
377
+ npmName: "name",
378
+ nameDescriptor: { name: "name" },
379
+ protocol: undefined,
380
+ isAlias: false,
381
+ selector: "./local/path",
382
+ },
383
+ ],
384
+ [
385
+ ["name", "git@github.com:moment/moment.git"],
386
+ {
387
+ key: "name",
388
+ npmName: "name",
389
+ nameDescriptor: { name: "name" },
390
+ protocol: undefined,
391
+ isAlias: false,
392
+ selector: "git@github.com:moment/moment.git",
393
+ },
394
+ ],
395
+ [
396
+ ["name", "latest"],
397
+ {
398
+ key: "name",
399
+ npmName: "name",
400
+ nameDescriptor: { name: "name" },
401
+ protocol: undefined,
402
+ isAlias: false,
403
+ selector: "latest",
404
+ },
405
+ ],
406
+ ];
407
+
408
+ const allCases: ParseCase[] = [
409
+ ...npmCases,
410
+ ...aliasCases,
411
+ ...otherProtocolCases,
412
+ ...shorthandCases,
413
+ ];
414
+
415
+ const expectParsed = (cases: ParseCase[]): void => {
416
+ for (const [[key, value], expected] of cases) {
417
+ expect(PackageDependencyDescriptorUtils.parse(key, value)).toEqual(
418
+ expected,
419
+ );
420
+ }
421
+ };
422
+
423
+ describe("PackageDependencyDescriptorUtils.parse", () => {
424
+ it("parses npm ranges", () => {
425
+ expectParsed(npmCases);
426
+ });
427
+
428
+ it("parses aliases", () => {
429
+ expectParsed(aliasCases);
430
+ });
431
+
432
+ it("parses the other protocols", () => {
433
+ expectParsed(otherProtocolCases);
434
+ });
435
+
436
+ it("parses declarations carrying no protocol", () => {
437
+ expectParsed(shorthandCases);
438
+ });
439
+
440
+ it("rejects a scoped name without a segment", () => {
441
+ expect(() =>
442
+ PackageDependencyDescriptorUtils.parse("name", "npm:@scope"),
443
+ ).toThrow("Invalid package name with scope");
444
+ });
445
+
446
+ it("does not read an alias out of a non-npm protocol", () => {
447
+ expect(
448
+ PackageDependencyDescriptorUtils.parse("name", "workspace:@scope/x@1"),
449
+ ).toEqual({
450
+ key: "name",
451
+ npmName: "name",
452
+ nameDescriptor: { name: "name" },
453
+ protocol: "workspace",
454
+ isAlias: false,
455
+ selector: "@scope/x@1",
456
+ });
457
+ });
458
+ });
459
+
460
+ describe("PackageDependencyDescriptorUtils.stringify", () => {
461
+ it("round trips every declaration byte for byte", () => {
462
+ for (const [[key, value]] of allCases) {
463
+ expect(
464
+ PackageDependencyDescriptorUtils.stringify(
465
+ PackageDependencyDescriptorUtils.parse(key, value),
466
+ ),
467
+ ).toEqual([key, value]);
468
+ }
469
+ });
470
+ });
471
+
472
+ describe("PackageDependencyDescriptorUtils.make", () => {
473
+ const swaps: readonly [
474
+ declaration: readonly [key: string, value: string],
475
+ selector: string,
476
+ expected: string,
477
+ ][] = [
478
+ [["name", "^1.0.0"], "2.0.0", "2.0.0"],
479
+ [["name", "npm:^1.0.0"], "^2.0.0", "npm:^2.0.0"],
480
+ [["name", "workspace:^1.0.0"], "^2.0.0", "workspace:^2.0.0"],
481
+ [
482
+ ["psc", "npm:printable-shell-command@^5.0.0"],
483
+ "^5.3.0",
484
+ "npm:printable-shell-command@^5.3.0",
485
+ ],
486
+ [["semver", "npm:semver@^6.0.0"], "^7.0.0", "npm:semver@^7.0.0"],
487
+ [["bun-types", "npm:@types/bun"], "^1.0.0", "npm:@types/bun@^1.0.0"],
488
+ ];
489
+
490
+ it("swaps the selector and keeps the protocol, the key and the alias target", () => {
491
+ for (const [[key, value], selector, expected] of swaps) {
492
+ expect(
493
+ PackageDependencyDescriptorUtils.stringify(
494
+ PackageDependencyDescriptorUtils.make(
495
+ PackageDependencyDescriptorUtils.parse(key, value),
496
+ selector,
497
+ ),
498
+ ),
499
+ ).toEqual([key, expected]);
500
+ }
501
+ });
502
+ });
503
+
504
+ describe("isNpmProtocol", () => {
505
+ it("accepts a bare declaration and the npm protocol", () => {
506
+ expect(isNpmProtocol(undefined)).toBe(true);
507
+ expect(isNpmProtocol("npm")).toBe(true);
508
+ });
509
+
510
+ it("rejects every other protocol", () => {
511
+ for (const protocol of [
512
+ "workspace",
513
+ "file",
514
+ "link",
515
+ "github",
516
+ "git",
517
+ "git+ssh",
518
+ "https",
519
+ "jsr",
520
+ "catalog",
521
+ "patch",
522
+ "portal",
523
+ "someproto",
524
+ // this parser strips the colon, unlike yarn's `structUtils.parseRange`
525
+ "npm:",
526
+ ]) {
527
+ expect(isNpmProtocol(protocol)).toBe(false);
528
+ }
529
+ });
530
+ });
531
+
532
+ describe("isSemverComparable", () => {
533
+ const comparableOf = (key: string, value: string): boolean =>
534
+ isSemverComparable(PackageDependencyDescriptorUtils.parse(key, value));
535
+
536
+ it("accepts npm ranges, aliases included", () => {
537
+ for (const value of [
538
+ "^1.0.0",
539
+ "1.x",
540
+ "1.0.0 - 2.0.0",
541
+ ">=1",
542
+ "*",
543
+ "npm:^1.0.0",
544
+ "npm:pkg@^1.0.0",
545
+ ]) {
546
+ expect(comparableOf("name", value)).toBe(true);
547
+ }
548
+ });
549
+
550
+ // a version-less alias accepts any version, and `semver` reads an empty range
551
+ // as `*`, so it needs no special case
552
+ it("accepts a version-less alias", () => {
553
+ expect(comparableOf("bun-types", "npm:@types/bun")).toBe(true);
554
+ });
555
+
556
+ it("rejects every non-npm protocol", () => {
557
+ for (const value of [
558
+ "workspace:*",
559
+ "workspace:^1.0.0",
560
+ "file:../l",
561
+ "link:../l",
562
+ "jsr:^1.0.0",
563
+ "catalog:default",
564
+ "github:user/repo",
565
+ "git+ssh://git@host/r.git",
566
+ "portal:../l",
567
+ "someproto:x",
568
+ ]) {
569
+ expect(comparableOf("name", value)).toBe(false);
570
+ }
571
+ });
572
+
573
+ // the cases a protocol check alone would let through
574
+ it("rejects shorthand declarations that carry no protocol", () => {
575
+ for (const value of [
576
+ "user/repo",
577
+ "user/repo#semver:^1",
578
+ "./local/path",
579
+ "git@github.com:moment/moment.git",
580
+ "https://host/x.tgz",
581
+ ]) {
582
+ expect(comparableOf("name", value)).toBe(false);
583
+ }
584
+ });
585
+
586
+ // a dist-tag accepts any version, so dropping it from the dependents is the
587
+ // same answer as counting it satisfied by everything — and far better than
588
+ // `semver.satisfies` reading it as unsatisfiable
589
+ it("rejects dist-tags", () => {
590
+ expect(comparableOf("name", "latest")).toBe(false);
591
+ expect(comparableOf("name", "next")).toBe(false);
592
+ });
593
+ });