@packall/core 0.0.1 → 0.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.
- package/dist/{Archive.d.ts → archive.d.ts} +5 -5
- package/dist/archive.d.ts.map +1 -0
- package/dist/{Bundle.d.ts → bundle.d.ts} +51 -36
- package/dist/bundle.d.ts.map +1 -0
- package/dist/{DependencyRange.d.ts → dependency-range.d.ts} +2 -2
- package/dist/dependency-range.d.ts.map +1 -0
- package/dist/{Download.d.ts → download.d.ts} +7 -7
- package/dist/download.d.ts.map +1 -0
- package/dist/enums/artifact-kind.d.ts +8 -0
- package/dist/enums/artifact-kind.d.ts.map +1 -0
- package/dist/enums/edge-kind.d.ts +29 -0
- package/dist/enums/edge-kind.d.ts.map +1 -0
- package/dist/enums/input-file-kind.d.ts +12 -0
- package/dist/enums/input-file-kind.d.ts.map +1 -0
- package/dist/enums/layout.d.ts +23 -0
- package/dist/enums/layout.d.ts.map +1 -0
- package/dist/enums/lockfile-format.d.ts +9 -0
- package/dist/enums/lockfile-format.d.ts.map +1 -0
- package/dist/enums/phase.d.ts +11 -0
- package/dist/enums/phase.d.ts.map +1 -0
- package/dist/{Errors.d.ts → errors.d.ts} +49 -7
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +44 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2416 -1180
- package/dist/index.js.map +1 -1
- package/dist/input-file.d.ts +77 -0
- package/dist/input-file.d.ts.map +1 -0
- package/dist/{Integrity.d.ts → integrity.d.ts} +3 -3
- package/dist/integrity.d.ts.map +1 -0
- package/dist/{Layout.d.ts → layout.d.ts} +1 -1
- package/dist/{Layout.d.ts.map → layout.d.ts.map} +1 -1
- package/dist/locked-resolve.d.ts +47 -0
- package/dist/locked-resolve.d.ts.map +1 -0
- package/dist/lockfile/bun.d.ts +3 -0
- package/dist/lockfile/bun.d.ts.map +1 -0
- package/dist/lockfile/detect.d.ts +31 -0
- package/dist/lockfile/detect.d.ts.map +1 -0
- package/dist/lockfile/json.d.ts +23 -0
- package/dist/lockfile/json.d.ts.map +1 -0
- package/dist/lockfile/names.d.ts +16 -0
- package/dist/lockfile/names.d.ts.map +1 -0
- package/dist/lockfile/npm.d.ts +3 -0
- package/dist/lockfile/npm.d.ts.map +1 -0
- package/dist/lockfile/parse.d.ts +23 -0
- package/dist/lockfile/parse.d.ts.map +1 -0
- package/dist/lockfile/pnpm.d.ts +20 -0
- package/dist/lockfile/pnpm.d.ts.map +1 -0
- package/dist/lockfile/tree-builder.d.ts +70 -0
- package/dist/lockfile/tree-builder.d.ts.map +1 -0
- package/dist/lockfile/types.d.ts +70 -0
- package/dist/lockfile/types.d.ts.map +1 -0
- package/dist/{Manifest.d.ts → manifest.d.ts} +7 -15
- package/dist/manifest.d.ts.map +1 -0
- package/dist/{Options.d.ts → options.d.ts} +9 -27
- package/dist/options.d.ts.map +1 -0
- package/dist/{Platform.d.ts → platform.d.ts} +9 -9
- package/dist/platform.d.ts.map +1 -0
- package/dist/{Progress.d.ts → progress.d.ts} +10 -9
- package/dist/progress.d.ts.map +1 -0
- package/dist/{Registry.d.ts → registry.d.ts} +42 -44
- package/dist/registry.d.ts.map +1 -0
- package/dist/{Resolve.d.ts → resolve.d.ts} +25 -26
- package/dist/resolve.d.ts.map +1 -0
- package/dist/schemas/lenient.d.ts +38 -0
- package/dist/schemas/lenient.d.ts.map +1 -0
- package/dist/schemas/package-json.d.ts +44 -0
- package/dist/schemas/package-json.d.ts.map +1 -0
- package/dist/{Spec.d.ts → spec.d.ts} +4 -4
- package/dist/spec.d.ts.map +1 -0
- package/dist/types/value-of.d.ts +9 -0
- package/dist/types/value-of.d.ts.map +1 -0
- package/dist/utils/is-record.d.ts +8 -0
- package/dist/utils/is-record.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/{Archive.ts → archive.ts} +92 -92
- package/src/{Bundle.ts → bundle.ts} +616 -570
- package/src/{DependencyRange.ts → dependency-range.ts} +115 -115
- package/src/{Download.ts → download.ts} +133 -132
- package/src/enums/artifact-kind.ts +9 -0
- package/src/enums/edge-kind.ts +32 -0
- package/src/enums/input-file-kind.ts +13 -0
- package/src/enums/layout.ts +25 -0
- package/src/enums/lockfile-format.ts +10 -0
- package/src/enums/phase.ts +12 -0
- package/src/{Errors.ts → errors.ts} +87 -5
- package/src/index.ts +133 -35
- package/src/{InputFile.ts → input-file.ts} +127 -44
- package/src/{Integrity.ts → integrity.ts} +121 -122
- package/src/locked-resolve.ts +461 -0
- package/src/lockfile/bun.ts +207 -0
- package/src/lockfile/detect.ts +80 -0
- package/src/lockfile/json.ts +132 -0
- package/src/lockfile/names.ts +20 -0
- package/src/lockfile/npm.ts +308 -0
- package/src/lockfile/parse.ts +56 -0
- package/src/lockfile/pnpm.ts +225 -0
- package/src/lockfile/tree-builder.ts +166 -0
- package/src/lockfile/types.ts +74 -0
- package/src/{Manifest.ts → manifest.ts} +133 -133
- package/src/{Options.ts → options.ts} +7 -27
- package/src/{Platform.ts → platform.ts} +23 -23
- package/src/{Progress.ts → progress.ts} +106 -106
- package/src/{Registry.ts → registry.ts} +115 -121
- package/src/{Resolve.ts → resolve.ts} +537 -528
- package/src/schemas/lenient.ts +123 -0
- package/src/schemas/package-json.ts +27 -0
- package/src/{Spec.ts → spec.ts} +11 -19
- package/src/types/value-of.ts +8 -0
- package/src/utils/is-record.ts +8 -0
- package/dist/Archive.d.ts.map +0 -1
- package/dist/Bundle.d.ts.map +0 -1
- package/dist/DependencyRange.d.ts.map +0 -1
- package/dist/Download.d.ts.map +0 -1
- package/dist/Errors.d.ts.map +0 -1
- package/dist/InputFile.d.ts +0 -46
- package/dist/InputFile.d.ts.map +0 -1
- package/dist/Integrity.d.ts.map +0 -1
- package/dist/Manifest.d.ts.map +0 -1
- package/dist/Options.d.ts.map +0 -1
- package/dist/Platform.d.ts.map +0 -1
- package/dist/Progress.d.ts.map +0 -1
- package/dist/Registry.d.ts.map +0 -1
- package/dist/Resolve.d.ts.map +0 -1
- package/dist/Spec.d.ts.map +0 -1
- /package/src/{Layout.ts → layout.ts} +0 -0
|
@@ -1,528 +1,537 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version selection and the dependency closure walk.
|
|
3
|
-
*
|
|
4
|
-
* This is the part that decides *what* ends up in a bundle. Its only side
|
|
5
|
-
* effect is asking the `Registry` service for metadata, which makes the whole
|
|
6
|
-
* of it testable against a fake registry with no network at all.
|
|
7
|
-
*
|
|
8
|
-
* The walk is breadth-first in waves: resolve the current frontier
|
|
9
|
-
* concurrently, collect the newly discovered edges, repeat. Wave-based BFS
|
|
10
|
-
* gives real parallelism without a dynamic worker pool, and because each wave
|
|
11
|
-
* merges its results serially there is no shared mutable state being written
|
|
12
|
-
* from more than one fiber.
|
|
13
|
-
*
|
|
14
|
-
* One subtlety worth calling out: metadata is cached across walks, but each
|
|
15
|
-
* walk still traverses the full graph. That is what lets `per-spec` layout ask
|
|
16
|
-
* "what does *this* root version depend on" many times over while the network
|
|
17
|
-
* is touched exactly once per package.
|
|
18
|
-
*/
|
|
19
|
-
import * as Effect from "effect/Effect"
|
|
20
|
-
import semver from "semver"
|
|
21
|
-
import { parseDependencyTarget } from "./
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
import type {
|
|
25
|
-
import {
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
28
|
-
import * as Progress from "./
|
|
29
|
-
import type {
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| { readonly _tag: "
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
readonly
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/** The
|
|
72
|
-
|
|
73
|
-
readonly
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
resolution
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
throw new
|
|
119
|
-
}
|
|
120
|
-
return [version]
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
case "
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* State shared across every walk in a run.
|
|
166
|
-
*
|
|
167
|
-
* Caching packuments and expanded edges here is what makes repeated walks
|
|
168
|
-
* cheap: the second walk over `react@18.2.0` costs no network at all.
|
|
169
|
-
*/
|
|
170
|
-
|
|
171
|
-
readonly resolved: Map<PackageKey, ResolvedPackage>
|
|
172
|
-
readonly edges: Map<PackageKey, ReadonlyArray<Task>>
|
|
173
|
-
readonly packuments: Map<string, Packument>
|
|
174
|
-
readonly warnings: Array<ResolutionWarning>
|
|
175
|
-
/** Warnings are deduplicated — the same skipped edge appears in many walks. */
|
|
176
|
-
readonly warningKeys: Set<string>
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const makeWalkState = (): WalkState => ({
|
|
180
|
-
resolved: new Map(),
|
|
181
|
-
edges: new Map(),
|
|
182
|
-
packuments: new Map(),
|
|
183
|
-
warnings: [],
|
|
184
|
-
warningKeys: new Set()
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
const addWarning = (state: WalkState, warning: ResolutionWarning): void => {
|
|
188
|
-
const key = `${warning.from ?? ""}|${warning.message}`
|
|
189
|
-
if (state.warningKeys.has(key)) return
|
|
190
|
-
state.warningKeys.add(key)
|
|
191
|
-
state.warnings.push(warning)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Fetches a packument, reusing anything already seen in this run.
|
|
196
|
-
*
|
|
197
|
-
* Two fibers in the same wave can race here and both fetch. That is one
|
|
198
|
-
* duplicated GET at worst, and de-racing it would cost more complexity than it
|
|
199
|
-
* saves.
|
|
200
|
-
*/
|
|
201
|
-
const getPackument = (
|
|
202
|
-
state: WalkState,
|
|
203
|
-
name: string
|
|
204
|
-
): Effect.Effect<Packument, BundlerError, Registry> =>
|
|
205
|
-
Effect.gen(function*() {
|
|
206
|
-
const cached = state.packuments.get(name)
|
|
207
|
-
if (cached !== undefined) return cached
|
|
208
|
-
const registry = yield* Registry
|
|
209
|
-
const packument = yield* registry.packument(name)
|
|
210
|
-
state.packuments.set(name, packument)
|
|
211
|
-
return packument
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
*/
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version selection and the dependency closure walk.
|
|
3
|
+
*
|
|
4
|
+
* This is the part that decides *what* ends up in a bundle. Its only side
|
|
5
|
+
* effect is asking the `Registry` service for metadata, which makes the whole
|
|
6
|
+
* of it testable against a fake registry with no network at all.
|
|
7
|
+
*
|
|
8
|
+
* The walk is breadth-first in waves: resolve the current frontier
|
|
9
|
+
* concurrently, collect the newly discovered edges, repeat. Wave-based BFS
|
|
10
|
+
* gives real parallelism without a dynamic worker pool, and because each wave
|
|
11
|
+
* merges its results serially there is no shared mutable state being written
|
|
12
|
+
* from more than one fiber.
|
|
13
|
+
*
|
|
14
|
+
* One subtlety worth calling out: metadata is cached across walks, but each
|
|
15
|
+
* walk still traverses the full graph. That is what lets `per-spec` layout ask
|
|
16
|
+
* "what does *this* root version depend on" many times over while the network
|
|
17
|
+
* is touched exactly once per package.
|
|
18
|
+
*/
|
|
19
|
+
import * as Effect from "effect/Effect"
|
|
20
|
+
import semver from "semver"
|
|
21
|
+
import { parseDependencyTarget } from "./dependency-range.js"
|
|
22
|
+
import { EdgeKind } from "./enums/edge-kind.js"
|
|
23
|
+
import { Phase } from "./enums/phase.js"
|
|
24
|
+
import type { BundlerError } from "./errors.js"
|
|
25
|
+
import { NoMatchingVersionsError, VersionNotFoundError } from "./errors.js"
|
|
26
|
+
import type { ResolveOptions } from "./options.js"
|
|
27
|
+
import { isIncluded } from "./platform.js"
|
|
28
|
+
import * as Progress from "./progress.js"
|
|
29
|
+
import type { PackageManifest, Packument } from "./registry.js"
|
|
30
|
+
import { Registry } from "./registry.js"
|
|
31
|
+
import type { PackageSpec, Selector } from "./spec.js"
|
|
32
|
+
import { formatSelector, formatSpec } from "./spec.js"
|
|
33
|
+
|
|
34
|
+
/** A single justification for including a package. */
|
|
35
|
+
export type Reason =
|
|
36
|
+
| { readonly _tag: "Root"; readonly spec: string }
|
|
37
|
+
| { readonly _tag: "Edge"; readonly from: string; readonly kind: EdgeKind }
|
|
38
|
+
|
|
39
|
+
/** `name@version` — the identity of a resolved package throughout the engine. */
|
|
40
|
+
export type PackageKey = string
|
|
41
|
+
|
|
42
|
+
/** One package, pinned to one version, with every reason it was pulled in. */
|
|
43
|
+
export type ResolvedPackage = {
|
|
44
|
+
readonly name: string
|
|
45
|
+
readonly version: string
|
|
46
|
+
readonly manifest: PackageManifest
|
|
47
|
+
readonly reasons: ReadonlyArray<Reason>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Something we could not include, reported rather than thrown. */
|
|
51
|
+
export type ResolutionWarning = {
|
|
52
|
+
readonly message: string
|
|
53
|
+
/** `name@version` of the package whose manifest contained the problem. */
|
|
54
|
+
readonly from?: string | undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What one requested spec expanded to. */
|
|
58
|
+
export type RootResolution = {
|
|
59
|
+
readonly spec: PackageSpec
|
|
60
|
+
/** The concrete versions the spec selected — more than one under `--all-versions`. */
|
|
61
|
+
readonly versions: ReadonlyArray<string>
|
|
62
|
+
/** Closure per selected version, keyed by version. Drives `per-spec` layout. */
|
|
63
|
+
readonly closures: ReadonlyMap<string, ReadonlyArray<PackageKey>>
|
|
64
|
+
/** Union of every per-version closure. */
|
|
65
|
+
readonly closure: ReadonlyArray<PackageKey>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The complete result of resolving every requested spec. */
|
|
69
|
+
export type Resolution = {
|
|
70
|
+
readonly roots: ReadonlyArray<RootResolution>
|
|
71
|
+
/** The deduplicated union of every closure, sorted by name then version. */
|
|
72
|
+
readonly packages: ReadonlyArray<ResolvedPackage>
|
|
73
|
+
readonly warnings: ReadonlyArray<ResolutionWarning>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One pending item on the BFS frontier. */
|
|
77
|
+
export type Task = {
|
|
78
|
+
readonly name: string
|
|
79
|
+
readonly selector: Selector
|
|
80
|
+
readonly reason: Reason
|
|
81
|
+
/** Optional edges tolerate resolution failure; required ones do not. */
|
|
82
|
+
readonly tolerateFailure: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const packageKey = (name: string, version: string): PackageKey => `${name}@${version}`
|
|
86
|
+
|
|
87
|
+
/** Index of a resolution by `name@version`, for lookups during bundling. */
|
|
88
|
+
export const indexPackages = (
|
|
89
|
+
resolution: Resolution
|
|
90
|
+
): ReadonlyMap<PackageKey, ResolvedPackage> =>
|
|
91
|
+
new Map(resolution.packages.map((pkg) => [packageKey(pkg.name, pkg.version), pkg]))
|
|
92
|
+
|
|
93
|
+
/* -------------------------------------------------------------------------- */
|
|
94
|
+
/* Version selection */
|
|
95
|
+
/* -------------------------------------------------------------------------- */
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Turns a selector into the concrete version(s) it names.
|
|
99
|
+
*
|
|
100
|
+
* `all` controls whether a range collapses to its best match — npm's behaviour,
|
|
101
|
+
* and what you always want for a transitive dependency — or expands to every
|
|
102
|
+
* satisfying published version, which is what `--all-versions` is for.
|
|
103
|
+
*/
|
|
104
|
+
export const selectVersions = (
|
|
105
|
+
packument: Packument,
|
|
106
|
+
selector: Selector,
|
|
107
|
+
options: {
|
|
108
|
+
readonly all: boolean
|
|
109
|
+
readonly maxVersions?: number | undefined
|
|
110
|
+
readonly includePrerelease: boolean
|
|
111
|
+
}
|
|
112
|
+
): ReadonlyArray<string> => {
|
|
113
|
+
const available = Object.keys(packument.versions)
|
|
114
|
+
|
|
115
|
+
switch (selector._tag) {
|
|
116
|
+
case "Exact": {
|
|
117
|
+
if (!Object.hasOwn(packument.versions, selector.version)) {
|
|
118
|
+
throw new VersionNotFoundError(packument.name, selector.version, sortVersions(available))
|
|
119
|
+
}
|
|
120
|
+
return [selector.version]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case "Tag": {
|
|
124
|
+
const version = packument.distTags[selector.tag]
|
|
125
|
+
if (version === undefined || !Object.hasOwn(packument.versions, version)) {
|
|
126
|
+
throw new NoMatchingVersionsError(packument.name, selector.tag, sortVersions(available))
|
|
127
|
+
}
|
|
128
|
+
return [version]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case "Range": {
|
|
132
|
+
const satisfying = available
|
|
133
|
+
.filter((version) =>
|
|
134
|
+
semver.satisfies(version, selector.range, {
|
|
135
|
+
loose: true,
|
|
136
|
+
includePrerelease: options.includePrerelease
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
.toSorted(semver.rcompare)
|
|
140
|
+
|
|
141
|
+
const [best] = satisfying
|
|
142
|
+
if (best === undefined) {
|
|
143
|
+
throw new NoMatchingVersionsError(packument.name, selector.range, sortVersions(available))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!options.all) return [best]
|
|
147
|
+
|
|
148
|
+
return options.maxVersions !== undefined && options.maxVersions > 0
|
|
149
|
+
? satisfying.slice(0, options.maxVersions)
|
|
150
|
+
: satisfying
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const sortVersions = (versions: ReadonlyArray<string>): ReadonlyArray<string> =>
|
|
156
|
+
versions.toSorted((a, b) =>
|
|
157
|
+
semver.valid(a) && semver.valid(b) ? semver.compare(a, b) : a.localeCompare(b)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
/* -------------------------------------------------------------------------- */
|
|
161
|
+
/* Walk state */
|
|
162
|
+
/* -------------------------------------------------------------------------- */
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* State shared across every walk in a run.
|
|
166
|
+
*
|
|
167
|
+
* Caching packuments and expanded edges here is what makes repeated walks
|
|
168
|
+
* cheap: the second walk over `react@18.2.0` costs no network at all.
|
|
169
|
+
*/
|
|
170
|
+
type WalkState = {
|
|
171
|
+
readonly resolved: Map<PackageKey, ResolvedPackage>
|
|
172
|
+
readonly edges: Map<PackageKey, ReadonlyArray<Task>>
|
|
173
|
+
readonly packuments: Map<string, Packument>
|
|
174
|
+
readonly warnings: Array<ResolutionWarning>
|
|
175
|
+
/** Warnings are deduplicated — the same skipped edge appears in many walks. */
|
|
176
|
+
readonly warningKeys: Set<string>
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const makeWalkState = (): WalkState => ({
|
|
180
|
+
resolved: new Map(),
|
|
181
|
+
edges: new Map(),
|
|
182
|
+
packuments: new Map(),
|
|
183
|
+
warnings: [],
|
|
184
|
+
warningKeys: new Set()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const addWarning = (state: WalkState, warning: ResolutionWarning): void => {
|
|
188
|
+
const key = `${warning.from ?? ""}|${warning.message}`
|
|
189
|
+
if (state.warningKeys.has(key)) return
|
|
190
|
+
state.warningKeys.add(key)
|
|
191
|
+
state.warnings.push(warning)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Fetches a packument, reusing anything already seen in this run.
|
|
196
|
+
*
|
|
197
|
+
* Two fibers in the same wave can race here and both fetch. That is one
|
|
198
|
+
* duplicated GET at worst, and de-racing it would cost more complexity than it
|
|
199
|
+
* saves.
|
|
200
|
+
*/
|
|
201
|
+
const getPackument = (
|
|
202
|
+
state: WalkState,
|
|
203
|
+
name: string
|
|
204
|
+
): Effect.Effect<Packument, BundlerError, Registry> =>
|
|
205
|
+
Effect.gen(function*() {
|
|
206
|
+
const cached = state.packuments.get(name)
|
|
207
|
+
if (cached !== undefined) return cached
|
|
208
|
+
const registry = yield* Registry
|
|
209
|
+
const packument = yield* registry.packument(name)
|
|
210
|
+
state.packuments.set(name, packument)
|
|
211
|
+
return packument
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Re-fails a thrown selection error into the Effect error channel.
|
|
216
|
+
*
|
|
217
|
+
* `selectVersions` throws `BundlerError` subclasses and nothing else, so this
|
|
218
|
+
* narrows rather than casting; anything unexpected is rethrown as a defect,
|
|
219
|
+
* which is what an unexpected exception actually is.
|
|
220
|
+
*/
|
|
221
|
+
const asBundlerError = (error: unknown): BundlerError => {
|
|
222
|
+
if (error instanceof VersionNotFoundError || error instanceof NoMatchingVersionsError) {
|
|
223
|
+
return error
|
|
224
|
+
}
|
|
225
|
+
throw error
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* -------------------------------------------------------------------------- */
|
|
229
|
+
/* Edge expansion */
|
|
230
|
+
/* -------------------------------------------------------------------------- */
|
|
231
|
+
|
|
232
|
+
/** Expands one resolved manifest into the edges that follow from it. */
|
|
233
|
+
export const edgesOf = (
|
|
234
|
+
manifest: PackageManifest,
|
|
235
|
+
options: ResolveOptions
|
|
236
|
+
): {
|
|
237
|
+
readonly tasks: ReadonlyArray<Task>
|
|
238
|
+
readonly warnings: ReadonlyArray<ResolutionWarning>
|
|
239
|
+
} => {
|
|
240
|
+
const from = packageKey(manifest.name, manifest.version)
|
|
241
|
+
const tasks: Array<Task> = []
|
|
242
|
+
const warnings: Array<ResolutionWarning> = []
|
|
243
|
+
|
|
244
|
+
const add = (
|
|
245
|
+
entries: Readonly<Record<string, string>> | undefined,
|
|
246
|
+
kind: EdgeKind,
|
|
247
|
+
tolerateFailure: boolean,
|
|
248
|
+
skip?: (name: string) => boolean
|
|
249
|
+
): void => {
|
|
250
|
+
if (entries === undefined) return
|
|
251
|
+
for (const [name, raw] of Object.entries(entries)) {
|
|
252
|
+
if (skip?.(name)) continue
|
|
253
|
+
const target = parseDependencyTarget(name, raw)
|
|
254
|
+
if (target._tag === "Unsupported") {
|
|
255
|
+
warnings.push({
|
|
256
|
+
from,
|
|
257
|
+
message:
|
|
258
|
+
`Skipped ${name}@${target.raw} (${target.reason}) — it cannot be fetched from a ` +
|
|
259
|
+
`registry. Vendor it separately if the offline install needs it.`
|
|
260
|
+
})
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
tasks.push({
|
|
264
|
+
name: target.name,
|
|
265
|
+
selector: target.selector,
|
|
266
|
+
reason: { _tag: "Edge", from, kind },
|
|
267
|
+
tolerateFailure
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Runtime dependencies are non-negotiable.
|
|
273
|
+
add(manifest.dependencies, EdgeKind.Prod, false)
|
|
274
|
+
|
|
275
|
+
// Optional dependencies are allowed to be missing — npm tolerates a 404 here
|
|
276
|
+
// and so must we, or one unpublished platform package fails the whole run.
|
|
277
|
+
if (options.scope.optional) {
|
|
278
|
+
add(manifest.optionalDependencies, EdgeKind.Optional, true)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (options.scope.peer) {
|
|
282
|
+
const meta = manifest.peerDependenciesMeta ?? {}
|
|
283
|
+
add(manifest.peerDependencies, EdgeKind.Peer, false, (name) => meta[name]?.optional === true)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { tasks, warnings }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Edges for a package, computed once and cached. */
|
|
290
|
+
const edgesFor = (
|
|
291
|
+
state: WalkState,
|
|
292
|
+
manifest: PackageManifest,
|
|
293
|
+
options: ResolveOptions
|
|
294
|
+
): ReadonlyArray<Task> => {
|
|
295
|
+
const key = packageKey(manifest.name, manifest.version)
|
|
296
|
+
const cached = state.edges.get(key)
|
|
297
|
+
if (cached !== undefined) return cached
|
|
298
|
+
|
|
299
|
+
const { tasks, warnings } = edgesOf(manifest, options)
|
|
300
|
+
for (const warning of warnings) addWarning(state, warning)
|
|
301
|
+
state.edges.set(key, tasks)
|
|
302
|
+
return tasks
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* -------------------------------------------------------------------------- */
|
|
306
|
+
/* The walk */
|
|
307
|
+
/* -------------------------------------------------------------------------- */
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Resolves everything reachable from `seeds`, returning the closure.
|
|
311
|
+
*
|
|
312
|
+
* `state` is mutated with newly resolved packages and warnings; the returned
|
|
313
|
+
* array is scoped to *this* walk, which is what makes per-root-version
|
|
314
|
+
* closures possible.
|
|
315
|
+
*/
|
|
316
|
+
const walk = (
|
|
317
|
+
state: WalkState,
|
|
318
|
+
seeds: ReadonlyArray<Task>,
|
|
319
|
+
options: ResolveOptions
|
|
320
|
+
): Effect.Effect<ReadonlyArray<PackageKey>, BundlerError, Registry | Progress.Progress> =>
|
|
321
|
+
Effect.gen(function*() {
|
|
322
|
+
const discovered = new Set<PackageKey>()
|
|
323
|
+
// Guards against re-resolving the same (name, selector) pair. Large trees
|
|
324
|
+
// ask for `tslib@^2` hundreds of times, and this is also what terminates
|
|
325
|
+
// dependency cycles.
|
|
326
|
+
const attempted = new Set<string>()
|
|
327
|
+
let frontier: ReadonlyArray<Task> = seeds
|
|
328
|
+
|
|
329
|
+
while (frontier.length > 0) {
|
|
330
|
+
const wave: Array<Task> = []
|
|
331
|
+
for (const task of frontier) {
|
|
332
|
+
const id = `${task.name} ${formatSelector(task.selector)}`
|
|
333
|
+
if (attempted.has(id)) continue
|
|
334
|
+
attempted.add(id)
|
|
335
|
+
wave.push(task)
|
|
336
|
+
}
|
|
337
|
+
if (wave.length === 0) break
|
|
338
|
+
|
|
339
|
+
const results = yield* Effect.forEach(
|
|
340
|
+
wave,
|
|
341
|
+
(task) => resolveTask(state, task, options),
|
|
342
|
+
{ concurrency: options.concurrency }
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
const next: Array<Task> = []
|
|
346
|
+
|
|
347
|
+
// Merged serially: every mutation of `state` happens on one fiber.
|
|
348
|
+
for (const result of results) {
|
|
349
|
+
if (result === null) continue
|
|
350
|
+
|
|
351
|
+
for (const { manifest, reason } of result) {
|
|
352
|
+
const key = packageKey(manifest.name, manifest.version)
|
|
353
|
+
const existing = state.resolved.get(key)
|
|
354
|
+
|
|
355
|
+
if (existing === undefined) {
|
|
356
|
+
state.resolved.set(key, {
|
|
357
|
+
name: manifest.name,
|
|
358
|
+
version: manifest.version,
|
|
359
|
+
manifest,
|
|
360
|
+
reasons: [reason]
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
yield* Progress.emit({
|
|
364
|
+
_tag: "PackageResolved",
|
|
365
|
+
name: manifest.name,
|
|
366
|
+
version: manifest.version,
|
|
367
|
+
resolvedCount: state.resolved.size,
|
|
368
|
+
pendingCount: next.length
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
if (manifest.deprecated !== undefined && manifest.deprecated !== "") {
|
|
372
|
+
addWarning(state, { from: key, message: `${key} is deprecated: ${manifest.deprecated}` })
|
|
373
|
+
}
|
|
374
|
+
} else if (!hasReason(existing.reasons, reason)) {
|
|
375
|
+
state.resolved.set(key, { ...existing, reasons: [...existing.reasons, reason] })
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Expand regardless of whether the package is new. A package resolved
|
|
379
|
+
// during an earlier walk still has to contribute its subtree to
|
|
380
|
+
// *this* walk's closure; `attempted` stops that from looping.
|
|
381
|
+
if (!discovered.has(key)) {
|
|
382
|
+
discovered.add(key)
|
|
383
|
+
next.push(...edgesFor(state, manifest, options))
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
frontier = next
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return [...discovered]
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const hasReason = (reasons: ReadonlyArray<Reason>, candidate: Reason): boolean =>
|
|
395
|
+
reasons.some((reason) => {
|
|
396
|
+
if (reason._tag === "Root" && candidate._tag === "Root") return reason.spec === candidate.spec
|
|
397
|
+
if (reason._tag === "Edge" && candidate._tag === "Edge") {
|
|
398
|
+
return reason.from === candidate.from && reason.kind === candidate.kind
|
|
399
|
+
}
|
|
400
|
+
return false
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Resolves one task to its manifest(s).
|
|
405
|
+
*
|
|
406
|
+
* Returns `null` when an optional edge could not be resolved — the caller
|
|
407
|
+
* treats that as "skip quietly", which is what npm does for
|
|
408
|
+
* `optionalDependencies`.
|
|
409
|
+
*/
|
|
410
|
+
const resolveTask = (
|
|
411
|
+
state: WalkState,
|
|
412
|
+
task: Task,
|
|
413
|
+
options: ResolveOptions
|
|
414
|
+
): Effect.Effect<
|
|
415
|
+
ReadonlyArray<{ readonly manifest: PackageManifest; readonly reason: Reason }> | null,
|
|
416
|
+
BundlerError,
|
|
417
|
+
Registry
|
|
418
|
+
> =>
|
|
419
|
+
Effect.gen(function*() {
|
|
420
|
+
const packument = yield* getPackument(state, task.name)
|
|
421
|
+
|
|
422
|
+
// Transitive edges always collapse to a single best match; only root specs
|
|
423
|
+
// ever fan out, and those arrive here already pinned to an exact version.
|
|
424
|
+
const versions = yield* Effect.try({
|
|
425
|
+
try: () =>
|
|
426
|
+
selectVersions(packument, task.selector, {
|
|
427
|
+
all: false,
|
|
428
|
+
includePrerelease: options.includePrerelease
|
|
429
|
+
}),
|
|
430
|
+
catch: asBundlerError
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
return versions.flatMap((version) => {
|
|
434
|
+
const manifest = packument.versions[version]
|
|
435
|
+
if (manifest === undefined) return []
|
|
436
|
+
|
|
437
|
+
// Platform narrowing only ever applies to optional edges. Filtering a
|
|
438
|
+
// required dependency by platform would produce a bundle that cannot
|
|
439
|
+
// install anywhere.
|
|
440
|
+
if (
|
|
441
|
+
task.reason._tag === "Edge" &&
|
|
442
|
+
task.reason.kind === EdgeKind.Optional &&
|
|
443
|
+
!isIncluded(manifest, options.scope.platforms)
|
|
444
|
+
) {
|
|
445
|
+
return []
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return { manifest, reason: task.reason }
|
|
449
|
+
})
|
|
450
|
+
}).pipe(
|
|
451
|
+
Effect.catch((error) => {
|
|
452
|
+
// A required edge that cannot be resolved is fatal. An optional one is
|
|
453
|
+
// recorded and skipped, matching how npm treats `optionalDependencies`.
|
|
454
|
+
if (!task.tolerateFailure) return Effect.fail(error)
|
|
455
|
+
return Effect.sync(() => {
|
|
456
|
+
addWarning(state, {
|
|
457
|
+
message:
|
|
458
|
+
`Optional dependency ${task.name}@${formatSelector(task.selector)} could not be ` +
|
|
459
|
+
`resolved and was skipped: ${error.message}`
|
|
460
|
+
})
|
|
461
|
+
return null
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
/* -------------------------------------------------------------------------- */
|
|
467
|
+
/* Entry point */
|
|
468
|
+
/* -------------------------------------------------------------------------- */
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Resolves every requested spec.
|
|
472
|
+
*
|
|
473
|
+
* Each selected root *version* is walked separately so `per-spec` layout has an
|
|
474
|
+
* exact closure per tarball, while all walks share one cache, so a package
|
|
475
|
+
* reached from twenty roots is fetched once.
|
|
476
|
+
*/
|
|
477
|
+
export const resolve = (
|
|
478
|
+
specs: ReadonlyArray<PackageSpec>,
|
|
479
|
+
options: ResolveOptions
|
|
480
|
+
): Effect.Effect<Resolution, BundlerError, Registry | Progress.Progress> =>
|
|
481
|
+
Effect.gen(function*() {
|
|
482
|
+
yield* Progress.emit({ _tag: "PhaseStarted", phase: Phase.Resolve, total: specs.length })
|
|
483
|
+
|
|
484
|
+
const state = makeWalkState()
|
|
485
|
+
const roots: Array<RootResolution> = []
|
|
486
|
+
|
|
487
|
+
for (const spec of specs) {
|
|
488
|
+
const packument = yield* getPackument(state, spec.name)
|
|
489
|
+
|
|
490
|
+
const versions = yield* Effect.try({
|
|
491
|
+
try: () =>
|
|
492
|
+
selectVersions(packument, spec.selector, {
|
|
493
|
+
all: options.allVersions,
|
|
494
|
+
maxVersions: options.maxVersions,
|
|
495
|
+
includePrerelease: options.includePrerelease
|
|
496
|
+
}),
|
|
497
|
+
catch: asBundlerError
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const closures = new Map<string, ReadonlyArray<PackageKey>>()
|
|
501
|
+
const union = new Set<PackageKey>()
|
|
502
|
+
|
|
503
|
+
for (const version of versions) {
|
|
504
|
+
const closure = yield* walk(
|
|
505
|
+
state,
|
|
506
|
+
[
|
|
507
|
+
{
|
|
508
|
+
name: spec.name,
|
|
509
|
+
selector: { _tag: "Exact", version },
|
|
510
|
+
reason: { _tag: "Root", spec: formatSpec(spec) },
|
|
511
|
+
tolerateFailure: false
|
|
512
|
+
}
|
|
513
|
+
],
|
|
514
|
+
options
|
|
515
|
+
)
|
|
516
|
+
closures.set(version, closure)
|
|
517
|
+
for (const key of closure) union.add(key)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
roots.push({ spec, versions, closures, closure: [...union] })
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
yield* Progress.emit({ _tag: "PhaseCompleted", phase: Phase.Resolve })
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
roots,
|
|
527
|
+
packages: [...state.resolved.values()].toSorted(compareResolved),
|
|
528
|
+
warnings: state.warnings
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
const compareResolved = (a: ResolvedPackage, b: ResolvedPackage): number => {
|
|
533
|
+
if (a.name !== b.name) return a.name < b.name ? -1 : 1
|
|
534
|
+
return semver.valid(a.version) && semver.valid(b.version)
|
|
535
|
+
? semver.compare(a.version, b.version)
|
|
536
|
+
: a.version.localeCompare(b.version)
|
|
537
|
+
}
|