@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,570 +1,616 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end orchestration: specs in, tarballs out.
|
|
3
|
-
*
|
|
4
|
-
* Two properties this module is responsible for, both of them fixes for how
|
|
5
|
-
* the original tool behaved:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Nothing is written outside the output directory.** Every intermediate
|
|
8
|
-
* file lives in a scoped temp directory obtained from
|
|
9
|
-
* `makeTempDirectoryScoped`, so the scope closing — on success, on failure,
|
|
10
|
-
* or on Ctrl-C — takes the whole staging tree with it. The working
|
|
11
|
-
* directory is never touched.
|
|
12
|
-
*
|
|
13
|
-
* 2. **Each package is downloaded exactly once**, no matter how many bundles
|
|
14
|
-
* it ends up in. Per-spec archives are assembled by hard-linking out of one
|
|
15
|
-
* shared download tree, so `per-spec` costs extra disk in the *output*, not
|
|
16
|
-
* extra network.
|
|
17
|
-
*/
|
|
18
|
-
import * as Effect from "effect/Effect"
|
|
19
|
-
import * as FileSystem from "effect/FileSystem"
|
|
20
|
-
import * as Path from "effect/Path"
|
|
21
|
-
import type { PlatformError } from "effect/PlatformError"
|
|
22
|
-
import type { Scope } from "effect/Scope"
|
|
23
|
-
import { createArchive } from "./
|
|
24
|
-
import { downloadAll } from "./
|
|
25
|
-
import
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
import
|
|
40
|
-
import {
|
|
41
|
-
import
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
readonly
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
readonly
|
|
58
|
-
readonly
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
readonly
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
readonly
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
readonly
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*/
|
|
122
|
-
export const
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
yield*
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
const
|
|
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
|
-
yield*
|
|
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
|
-
|
|
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
|
-
yield*
|
|
434
|
-
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end orchestration: specs in, tarballs out.
|
|
3
|
+
*
|
|
4
|
+
* Two properties this module is responsible for, both of them fixes for how
|
|
5
|
+
* the original tool behaved:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Nothing is written outside the output directory.** Every intermediate
|
|
8
|
+
* file lives in a scoped temp directory obtained from
|
|
9
|
+
* `makeTempDirectoryScoped`, so the scope closing — on success, on failure,
|
|
10
|
+
* or on Ctrl-C — takes the whole staging tree with it. The working
|
|
11
|
+
* directory is never touched.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Each package is downloaded exactly once**, no matter how many bundles
|
|
14
|
+
* it ends up in. Per-spec archives are assembled by hard-linking out of one
|
|
15
|
+
* shared download tree, so `per-spec` costs extra disk in the *output*, not
|
|
16
|
+
* extra network.
|
|
17
|
+
*/
|
|
18
|
+
import * as Effect from "effect/Effect"
|
|
19
|
+
import * as FileSystem from "effect/FileSystem"
|
|
20
|
+
import * as Path from "effect/Path"
|
|
21
|
+
import type { PlatformError } from "effect/PlatformError"
|
|
22
|
+
import type { Scope } from "effect/Scope"
|
|
23
|
+
import { createArchive } from "./archive.js"
|
|
24
|
+
import { downloadAll } from "./download.js"
|
|
25
|
+
import { ArtifactKind } from "./enums/artifact-kind.js"
|
|
26
|
+
import { Layout } from "./enums/layout.js"
|
|
27
|
+
import { Phase } from "./enums/phase.js"
|
|
28
|
+
import type { BundlerError } from "./errors.js"
|
|
29
|
+
import { OutputError } from "./errors.js"
|
|
30
|
+
import {
|
|
31
|
+
importGuide,
|
|
32
|
+
MANIFEST_FILE,
|
|
33
|
+
packagePath,
|
|
34
|
+
perSpecArchiveName,
|
|
35
|
+
README_FILE,
|
|
36
|
+
singleArchiveName
|
|
37
|
+
} from "./layout.js"
|
|
38
|
+
import type { LockedResolveOptions } from "./locked-resolve.js"
|
|
39
|
+
import { resolveLocked } from "./locked-resolve.js"
|
|
40
|
+
import type { LockedTree } from "./lockfile/types.js"
|
|
41
|
+
import { buildManifest, serializeManifest } from "./manifest.js"
|
|
42
|
+
import type { BundleOptions } from "./options.js"
|
|
43
|
+
import * as Progress from "./progress.js"
|
|
44
|
+
import { Registry } from "./registry.js"
|
|
45
|
+
import type { PackageKey, Resolution, ResolvedPackage } from "./resolve.js"
|
|
46
|
+
import { indexPackages, resolve } from "./resolve.js"
|
|
47
|
+
import type { PackageSpec } from "./spec.js"
|
|
48
|
+
import { formatSpec } from "./spec.js"
|
|
49
|
+
|
|
50
|
+
/** One thing written to the output directory. */
|
|
51
|
+
export type BundleArtifact = {
|
|
52
|
+
readonly kind: ArtifactKind
|
|
53
|
+
readonly path: string
|
|
54
|
+
readonly bytes: number
|
|
55
|
+
readonly packageCount: number
|
|
56
|
+
/** Which spec this artifact covers. Absent for `single` and `dir`. */
|
|
57
|
+
readonly spec?: string | undefined
|
|
58
|
+
readonly version?: string | undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The outcome of a bundle run. */
|
|
62
|
+
export type BundleResult = {
|
|
63
|
+
readonly resolution: Resolution
|
|
64
|
+
readonly artifacts: ReadonlyArray<BundleArtifact>
|
|
65
|
+
/** Total bytes downloaded from the registry. */
|
|
66
|
+
readonly downloadedBytes: number
|
|
67
|
+
/** Packages the registry advertised no usable checksum for. */
|
|
68
|
+
readonly unverified: ReadonlyArray<string>
|
|
69
|
+
readonly dryRun: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Everything `bundle` needs beyond the user-facing options. */
|
|
73
|
+
export type BundleContext = BundleOptions & {
|
|
74
|
+
/** Recorded in the manifest so a bundle can be traced to the tool that built it. */
|
|
75
|
+
readonly toolVersion: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A summary of what a resolution would produce, for deciding whether the
|
|
80
|
+
* chosen layout is still the right one.
|
|
81
|
+
*/
|
|
82
|
+
export type PlanSummary = {
|
|
83
|
+
/** Distinct packages in the deduplicated union. */
|
|
84
|
+
readonly uniquePackages: number
|
|
85
|
+
/** How many archives `per-spec` layout would emit. */
|
|
86
|
+
readonly perSpecArchives: number
|
|
87
|
+
/**
|
|
88
|
+
* Total package entries across all per-spec archives, counting shared
|
|
89
|
+
* dependencies once per archive they appear in. The gap between this and
|
|
90
|
+
* `uniquePackages` is exactly what `single` layout would save.
|
|
91
|
+
*/
|
|
92
|
+
readonly perSpecEntries: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The files a run will write, known before anything is downloaded.
|
|
97
|
+
*
|
|
98
|
+
* `dir` layout is the odd one: it merges a package tree into `outDir`, and
|
|
99
|
+
* package paths are `name/-/name-version.tgz` — the same package at the same
|
|
100
|
+
* version is the same bytes, so an overlap there is idempotent rather than
|
|
101
|
+
* destructive. Only the two summary files are genuinely replaced, and those are
|
|
102
|
+
* the ones worth guarding.
|
|
103
|
+
*/
|
|
104
|
+
export type PlannedOutput = {
|
|
105
|
+
readonly file: string
|
|
106
|
+
/** The root this file belongs to, when one file corresponds to one spec. */
|
|
107
|
+
readonly name?: string | undefined
|
|
108
|
+
readonly version?: string | undefined
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type BundleEnv = Registry | Progress.Progress | FileSystem.FileSystem | Path.Path
|
|
112
|
+
|
|
113
|
+
type EmitInput = {
|
|
114
|
+
readonly resolution: Resolution
|
|
115
|
+
readonly options: BundleContext
|
|
116
|
+
readonly packagesDir: string
|
|
117
|
+
readonly report: { readonly sizes: ReadonlyMap<string, number> }
|
|
118
|
+
readonly registryInfo: { readonly kind: string; readonly url: string }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Computes the plan summary for a resolution. */
|
|
122
|
+
export const summarize = (resolution: Resolution): PlanSummary => {
|
|
123
|
+
let perSpecArchives = 0
|
|
124
|
+
let perSpecEntries = 0
|
|
125
|
+
for (const root of resolution.roots) {
|
|
126
|
+
for (const version of root.versions) {
|
|
127
|
+
perSpecArchives += 1
|
|
128
|
+
perSpecEntries += (root.closures.get(version) ?? []).length
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
uniquePackages: resolution.packages.length,
|
|
133
|
+
perSpecArchives,
|
|
134
|
+
perSpecEntries
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Replaces the layout on a set of options. */
|
|
139
|
+
export const withLayout = <T extends BundleOptions>(options: T, layout: Layout): T => ({
|
|
140
|
+
...options,
|
|
141
|
+
layout
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Checks the registry is reachable, then resolves every spec.
|
|
146
|
+
*
|
|
147
|
+
* Split out from `bundle` so a caller can inspect the plan — how many packages,
|
|
148
|
+
* how many archives — and act on it before any bytes move. The CLI uses this to
|
|
149
|
+
* offer a different layout when a run turns out to be much larger than
|
|
150
|
+
* expected.
|
|
151
|
+
*/
|
|
152
|
+
export const plan = (
|
|
153
|
+
specs: ReadonlyArray<PackageSpec>,
|
|
154
|
+
options: BundleOptions
|
|
155
|
+
): Effect.Effect<Resolution, BundlerError, Registry | Progress.Progress> =>
|
|
156
|
+
Effect.gen(function*() {
|
|
157
|
+
yield* preflight
|
|
158
|
+
return yield* resolve(specs, options)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The lockfile counterpart of `plan`.
|
|
163
|
+
*
|
|
164
|
+
* Same preflight, same `Resolution` out; the difference is entirely in how the
|
|
165
|
+
* package set is arrived at. Kept as a separate entry point rather than an
|
|
166
|
+
* option on `plan` because the two take genuinely different inputs — a set of
|
|
167
|
+
* specs to satisfy versus a graph to reproduce — and blurring that is how a
|
|
168
|
+
* "pinned" run quietly starts resolving ranges again.
|
|
169
|
+
*/
|
|
170
|
+
export const planLocked = (
|
|
171
|
+
lockfile: LockedTree,
|
|
172
|
+
options: BundleOptions & LockedResolveOptions
|
|
173
|
+
): Effect.Effect<Resolution, BundlerError, Registry | Progress.Progress> =>
|
|
174
|
+
Effect.gen(function*() {
|
|
175
|
+
yield* preflight
|
|
176
|
+
return yield* resolveLocked(lockfile, options)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const preflight: Effect.Effect<void, BundlerError, Registry | Progress.Progress> = Effect.gen(
|
|
180
|
+
function*() {
|
|
181
|
+
const registry = yield* Registry
|
|
182
|
+
yield* Progress.emit({ _tag: "PhaseStarted", phase: Phase.Preflight })
|
|
183
|
+
yield* registry.preflight
|
|
184
|
+
yield* Progress.emit({ _tag: "PhaseCompleted", phase: Phase.Preflight })
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Resolves, downloads and packages a set of specs.
|
|
190
|
+
*
|
|
191
|
+
* Under `dryRun` this stops after resolution and reports the plan — useful for
|
|
192
|
+
* checking what a command *would* pull before committing to a multi-gigabyte
|
|
193
|
+
* download over a slow VPN.
|
|
194
|
+
*/
|
|
195
|
+
export const bundle = (
|
|
196
|
+
specs: ReadonlyArray<PackageSpec>,
|
|
197
|
+
options: BundleContext
|
|
198
|
+
): Effect.Effect<BundleResult, BundlerError | PlatformError, BundleEnv> =>
|
|
199
|
+
Effect.gen(function*() {
|
|
200
|
+
const resolution = yield* plan(specs, options)
|
|
201
|
+
return yield* bundleResolved(resolution, options)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
/** `bundle`, pinned to a lockfile. */
|
|
205
|
+
export const bundleLocked = (
|
|
206
|
+
lockfile: LockedTree,
|
|
207
|
+
options: BundleContext & LockedResolveOptions
|
|
208
|
+
): Effect.Effect<BundleResult, BundlerError | PlatformError, BundleEnv> =>
|
|
209
|
+
Effect.gen(function*() {
|
|
210
|
+
const resolution = yield* planLocked(lockfile, options)
|
|
211
|
+
return yield* bundleResolved(resolution, options)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Downloads and packages an already-computed resolution.
|
|
216
|
+
*
|
|
217
|
+
* Everything from here on touches the disk, and all of it happens inside
|
|
218
|
+
* `Effect.scoped` — so the staging tree's lifetime is exactly this call,
|
|
219
|
+
* including when it fails partway through or the user hits Ctrl-C.
|
|
220
|
+
*/
|
|
221
|
+
export const bundleResolved = (
|
|
222
|
+
resolution: Resolution,
|
|
223
|
+
options: BundleContext
|
|
224
|
+
): Effect.Effect<BundleResult, BundlerError | PlatformError, BundleEnv> =>
|
|
225
|
+
Effect.gen(function*() {
|
|
226
|
+
if (options.dryRun) {
|
|
227
|
+
return {
|
|
228
|
+
resolution,
|
|
229
|
+
artifacts: [],
|
|
230
|
+
downloadedBytes: 0,
|
|
231
|
+
unverified: [],
|
|
232
|
+
dryRun: true
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return yield* Effect.scoped(runBundle(resolution, options))
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The files a run will write.
|
|
240
|
+
*
|
|
241
|
+
* Computed before anything is downloaded, so a collision is reported in the
|
|
242
|
+
* first second rather than after a twenty-minute transfer.
|
|
243
|
+
*/
|
|
244
|
+
export const plannedOutputs = (
|
|
245
|
+
resolution: Resolution,
|
|
246
|
+
options: BundleContext
|
|
247
|
+
): ReadonlyArray<PlannedOutput> => {
|
|
248
|
+
if (options.layout === Layout.Dir) return [{ file: MANIFEST_FILE }, { file: README_FILE }]
|
|
249
|
+
if (options.layout === Layout.Single) {
|
|
250
|
+
return [{ file: singleArchiveName(options.archiveName ?? "bundle") }]
|
|
251
|
+
}
|
|
252
|
+
return resolution.roots.flatMap((root) =>
|
|
253
|
+
root.versions.map((version) => ({
|
|
254
|
+
file: perSpecArchiveName(root.spec.name, version),
|
|
255
|
+
name: root.spec.name,
|
|
256
|
+
version
|
|
257
|
+
}))
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const runBundle = (
|
|
262
|
+
resolution: Resolution,
|
|
263
|
+
options: BundleContext
|
|
264
|
+
): Effect.Effect<
|
|
265
|
+
BundleResult,
|
|
266
|
+
BundlerError | PlatformError,
|
|
267
|
+
BundleEnv | Scope
|
|
268
|
+
> =>
|
|
269
|
+
Effect.gen(function*() {
|
|
270
|
+
const fs = yield* FileSystem.FileSystem
|
|
271
|
+
const path = yield* Path.Path
|
|
272
|
+
const registry = yield* Registry
|
|
273
|
+
|
|
274
|
+
yield* prepareOutputDir(resolution, options)
|
|
275
|
+
|
|
276
|
+
const staging = yield* fs.makeTempDirectoryScoped({ prefix: "packall-" })
|
|
277
|
+
const packagesDir = path.join(staging, "packages")
|
|
278
|
+
yield* fs.makeDirectory(packagesDir, { recursive: true })
|
|
279
|
+
|
|
280
|
+
const report = yield* downloadAll(resolution.packages, packagesDir, {
|
|
281
|
+
concurrency: options.concurrency,
|
|
282
|
+
verifyIntegrity: options.verifyIntegrity
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
yield* Progress.emit({ _tag: "PhaseStarted", phase: Phase.Archive })
|
|
286
|
+
|
|
287
|
+
const input: EmitInput = {
|
|
288
|
+
resolution,
|
|
289
|
+
options,
|
|
290
|
+
packagesDir,
|
|
291
|
+
report,
|
|
292
|
+
registryInfo: {
|
|
293
|
+
kind: registry.kind,
|
|
294
|
+
url: registry.registryFor(resolution.roots[0]?.spec.name ?? "")
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const artifacts = yield* emit(input, staging)
|
|
299
|
+
|
|
300
|
+
yield* Progress.emit({ _tag: "PhaseCompleted", phase: Phase.Archive })
|
|
301
|
+
yield* Progress.emit({ _tag: "PhaseStarted", phase: Phase.Done })
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
resolution,
|
|
305
|
+
artifacts,
|
|
306
|
+
downloadedBytes: report.totalBytes,
|
|
307
|
+
unverified: report.unverified,
|
|
308
|
+
dryRun: false
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
/* -------------------------------------------------------------------------- */
|
|
313
|
+
/* Emitters */
|
|
314
|
+
/* -------------------------------------------------------------------------- */
|
|
315
|
+
|
|
316
|
+
const emit = (
|
|
317
|
+
input: EmitInput,
|
|
318
|
+
staging: string
|
|
319
|
+
): Effect.Effect<
|
|
320
|
+
ReadonlyArray<BundleArtifact>,
|
|
321
|
+
BundlerError | PlatformError,
|
|
322
|
+
FileSystem.FileSystem | Path.Path | Progress.Progress
|
|
323
|
+
> => {
|
|
324
|
+
switch (input.options.layout) {
|
|
325
|
+
case Layout.Single:
|
|
326
|
+
return emitSingle(input)
|
|
327
|
+
case Layout.Dir:
|
|
328
|
+
return emitDirectory(input)
|
|
329
|
+
case Layout.PerSpec:
|
|
330
|
+
return emitPerSpec(input, staging)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** One tarball containing the deduplicated union of every closure. */
|
|
335
|
+
const emitSingle = (
|
|
336
|
+
input: EmitInput
|
|
337
|
+
): Effect.Effect<
|
|
338
|
+
ReadonlyArray<BundleArtifact>,
|
|
339
|
+
BundlerError | PlatformError,
|
|
340
|
+
FileSystem.FileSystem | Path.Path | Progress.Progress
|
|
341
|
+
> =>
|
|
342
|
+
Effect.gen(function*() {
|
|
343
|
+
const path = yield* Path.Path
|
|
344
|
+
|
|
345
|
+
const archiveName = singleArchiveName(input.options.archiveName ?? "bundle")
|
|
346
|
+
// Declined at the prompt, and it is the run's only output.
|
|
347
|
+
if (input.options.skipExisting?.has(archiveName) === true) return []
|
|
348
|
+
|
|
349
|
+
yield* writeBundleMetadata({
|
|
350
|
+
dir: input.packagesDir,
|
|
351
|
+
resolution: input.resolution,
|
|
352
|
+
included: input.resolution.packages,
|
|
353
|
+
options: input.options,
|
|
354
|
+
registryInfo: input.registryInfo,
|
|
355
|
+
sizes: input.report.sizes
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
const outPath = path.join(input.options.outDir, archiveName)
|
|
359
|
+
|
|
360
|
+
const result = yield* createArchive({
|
|
361
|
+
cwd: input.packagesDir,
|
|
362
|
+
entries: yield* topLevelEntries(input.packagesDir),
|
|
363
|
+
outPath
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
return [
|
|
367
|
+
{
|
|
368
|
+
kind: ArtifactKind.Archive,
|
|
369
|
+
path: result.path,
|
|
370
|
+
bytes: result.bytes,
|
|
371
|
+
packageCount: input.resolution.packages.length
|
|
372
|
+
}
|
|
373
|
+
]
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
/** The raw npm-layout tree, no archive. */
|
|
377
|
+
const emitDirectory = (
|
|
378
|
+
input: EmitInput
|
|
379
|
+
): Effect.Effect<
|
|
380
|
+
ReadonlyArray<BundleArtifact>,
|
|
381
|
+
BundlerError | PlatformError,
|
|
382
|
+
FileSystem.FileSystem | Path.Path
|
|
383
|
+
> =>
|
|
384
|
+
Effect.gen(function*() {
|
|
385
|
+
const fs = yield* FileSystem.FileSystem
|
|
386
|
+
|
|
387
|
+
yield* writeBundleMetadata({
|
|
388
|
+
dir: input.packagesDir,
|
|
389
|
+
resolution: input.resolution,
|
|
390
|
+
included: input.resolution.packages,
|
|
391
|
+
options: input.options,
|
|
392
|
+
registryInfo: input.registryInfo,
|
|
393
|
+
sizes: input.report.sizes
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// The staging tree is about to be deleted with its scope, so this is a
|
|
397
|
+
// real copy rather than a move — `copy` also works across devices, which
|
|
398
|
+
// matters because the temp dir often lives on a different filesystem.
|
|
399
|
+
yield* fs.copy(input.packagesDir, input.options.outDir, { overwrite: true }).pipe(
|
|
400
|
+
Effect.mapError(
|
|
401
|
+
(cause) => new OutputError(input.options.outDir, "could not write output tree", { cause })
|
|
402
|
+
)
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
const bytes = [...input.report.sizes.values()].reduce((a, b) => a + b, 0)
|
|
406
|
+
|
|
407
|
+
return [
|
|
408
|
+
{
|
|
409
|
+
kind: ArtifactKind.Directory,
|
|
410
|
+
path: input.options.outDir,
|
|
411
|
+
bytes,
|
|
412
|
+
packageCount: input.resolution.packages.length
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* One tarball per resolved root *version*.
|
|
419
|
+
*
|
|
420
|
+
* Under `--all-versions react@^18` this produces `react-18.0.0.tgz`,
|
|
421
|
+
* `react-18.1.0.tgz` and so on, each independently importable — which is the
|
|
422
|
+
* whole reason to prefer this layout.
|
|
423
|
+
*/
|
|
424
|
+
const emitPerSpec = (
|
|
425
|
+
input: EmitInput,
|
|
426
|
+
staging: string
|
|
427
|
+
): Effect.Effect<
|
|
428
|
+
ReadonlyArray<BundleArtifact>,
|
|
429
|
+
BundlerError | PlatformError,
|
|
430
|
+
FileSystem.FileSystem | Path.Path | Progress.Progress
|
|
431
|
+
> =>
|
|
432
|
+
Effect.gen(function*() {
|
|
433
|
+
const fs = yield* FileSystem.FileSystem
|
|
434
|
+
const path = yield* Path.Path
|
|
435
|
+
const index = indexPackages(input.resolution)
|
|
436
|
+
const artifacts: Array<BundleArtifact> = []
|
|
437
|
+
|
|
438
|
+
let counter = 0
|
|
439
|
+
for (const root of input.resolution.roots) {
|
|
440
|
+
for (const version of root.versions) {
|
|
441
|
+
// Declined at the prompt: the existing file stays, and this archive is
|
|
442
|
+
// simply never built. Skipping the staging work too, not just the write.
|
|
443
|
+
if (input.options.skipExisting?.has(perSpecArchiveName(root.spec.name, version)) === true) {
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const included = includedPackages(index, root.closures.get(version) ?? [])
|
|
448
|
+
|
|
449
|
+
const rootDir = path.join(staging, "roots", String(counter++))
|
|
450
|
+
yield* fs.makeDirectory(rootDir, { recursive: true })
|
|
451
|
+
|
|
452
|
+
for (const pkg of included) {
|
|
453
|
+
const parts = packagePath(pkg.name, pkg.version).split("/")
|
|
454
|
+
const source = path.join(input.packagesDir, ...parts)
|
|
455
|
+
const target = path.join(rootDir, ...parts)
|
|
456
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true })
|
|
457
|
+
yield* linkOrCopy(source, target)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
yield* writeBundleMetadata({
|
|
461
|
+
dir: rootDir,
|
|
462
|
+
resolution: {
|
|
463
|
+
...input.resolution,
|
|
464
|
+
roots: [{ ...root, versions: [version] }]
|
|
465
|
+
},
|
|
466
|
+
included,
|
|
467
|
+
options: input.options,
|
|
468
|
+
registryInfo: input.registryInfo,
|
|
469
|
+
sizes: input.report.sizes
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
const outPath = path.join(
|
|
473
|
+
input.options.outDir,
|
|
474
|
+
perSpecArchiveName(root.spec.name, version)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
const result = yield* createArchive({
|
|
478
|
+
cwd: rootDir,
|
|
479
|
+
entries: yield* topLevelEntries(rootDir),
|
|
480
|
+
outPath
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
artifacts.push({
|
|
484
|
+
kind: ArtifactKind.Archive,
|
|
485
|
+
path: result.path,
|
|
486
|
+
bytes: result.bytes,
|
|
487
|
+
packageCount: included.length,
|
|
488
|
+
spec: formatSpec(root.spec),
|
|
489
|
+
version
|
|
490
|
+
})
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return artifacts
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
/* -------------------------------------------------------------------------- */
|
|
498
|
+
/* Helpers */
|
|
499
|
+
/* -------------------------------------------------------------------------- */
|
|
500
|
+
|
|
501
|
+
const includedPackages = (
|
|
502
|
+
index: ReadonlyMap<PackageKey, ResolvedPackage>,
|
|
503
|
+
closure: ReadonlyArray<PackageKey>
|
|
504
|
+
): ReadonlyArray<ResolvedPackage> => closure.flatMap((key) => index.get(key) ?? [])
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Hard-links a file, falling back to a copy.
|
|
508
|
+
*
|
|
509
|
+
* Hard links make `per-spec` layout nearly free in the staging tree. They fail
|
|
510
|
+
* across devices and on some Windows configurations, so the copy fallback is
|
|
511
|
+
* not optional.
|
|
512
|
+
*/
|
|
513
|
+
const linkOrCopy = (
|
|
514
|
+
source: string,
|
|
515
|
+
target: string
|
|
516
|
+
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
|
517
|
+
Effect.gen(function*() {
|
|
518
|
+
const fs = yield* FileSystem.FileSystem
|
|
519
|
+
yield* fs.link(source, target).pipe(
|
|
520
|
+
Effect.catch(() => fs.copyFile(source, target))
|
|
521
|
+
)
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
/** Writes the manifest and import guide into a staging directory. */
|
|
525
|
+
const writeBundleMetadata = (input: {
|
|
526
|
+
readonly dir: string
|
|
527
|
+
readonly resolution: Resolution
|
|
528
|
+
readonly included: ReadonlyArray<ResolvedPackage>
|
|
529
|
+
readonly options: BundleContext
|
|
530
|
+
readonly registryInfo: { readonly kind: string; readonly url: string }
|
|
531
|
+
readonly sizes: ReadonlyMap<string, number>
|
|
532
|
+
}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
|
|
533
|
+
Effect.gen(function*() {
|
|
534
|
+
const fs = yield* FileSystem.FileSystem
|
|
535
|
+
const path = yield* Path.Path
|
|
536
|
+
|
|
537
|
+
const createdAt = new Date()
|
|
538
|
+
const manifest = buildManifest({
|
|
539
|
+
resolution: input.resolution,
|
|
540
|
+
included: input.included,
|
|
541
|
+
options: input.options,
|
|
542
|
+
registry: input.registryInfo,
|
|
543
|
+
toolVersion: input.options.toolVersion,
|
|
544
|
+
sizes: input.sizes,
|
|
545
|
+
createdAt
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
yield* fs.writeFileString(path.join(input.dir, MANIFEST_FILE), serializeManifest(manifest))
|
|
549
|
+
yield* fs.writeFileString(
|
|
550
|
+
path.join(input.dir, README_FILE),
|
|
551
|
+
importGuide({
|
|
552
|
+
packageCount: input.included.length,
|
|
553
|
+
createdAt: createdAt.toISOString(),
|
|
554
|
+
toolVersion: input.options.toolVersion
|
|
555
|
+
})
|
|
556
|
+
)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
/** Sorted top-level entries of a directory, used as the tar entry list. */
|
|
560
|
+
const topLevelEntries = (
|
|
561
|
+
dir: string
|
|
562
|
+
): Effect.Effect<ReadonlyArray<string>, PlatformError, FileSystem.FileSystem> =>
|
|
563
|
+
Effect.gen(function*() {
|
|
564
|
+
const fs = yield* FileSystem.FileSystem
|
|
565
|
+
const entries = yield* fs.readDirectory(dir)
|
|
566
|
+
return entries.toSorted()
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Fails if the run would overwrite something, unless `--force`.
|
|
571
|
+
*
|
|
572
|
+
* The guard is on the *files this run writes*, not on whether the directory has
|
|
573
|
+
* anything in it. An output directory accumulating bundles is the normal way to
|
|
574
|
+
* use this — bundling `esbuild` into an `out/` that already holds `tsdown` is
|
|
575
|
+
* not a conflict, and refusing it made `--out` a single-use directory and the
|
|
576
|
+
* default `.` unusable.
|
|
577
|
+
*
|
|
578
|
+
* Checked before anything is downloaded. Discovering the collision *after* a
|
|
579
|
+
* twenty-minute download would be a uniquely irritating way to fail.
|
|
580
|
+
*/
|
|
581
|
+
const prepareOutputDir = (
|
|
582
|
+
resolution: Resolution,
|
|
583
|
+
options: BundleContext
|
|
584
|
+
): Effect.Effect<void, OutputError | PlatformError, FileSystem.FileSystem | Path.Path> =>
|
|
585
|
+
Effect.gen(function*() {
|
|
586
|
+
const fs = yield* FileSystem.FileSystem
|
|
587
|
+
const path = yield* Path.Path
|
|
588
|
+
const outDir = options.outDir
|
|
589
|
+
|
|
590
|
+
if (!options.force) {
|
|
591
|
+
const clashes: Array<string> = []
|
|
592
|
+
for (const planned of plannedOutputs(resolution, options)) {
|
|
593
|
+
if (options.skipExisting?.has(planned.file) === true) continue
|
|
594
|
+
if (options.overwrite?.has(planned.file) === true) continue
|
|
595
|
+
const exists = yield* fs
|
|
596
|
+
.exists(path.join(outDir, planned.file))
|
|
597
|
+
.pipe(Effect.orElseSucceed(() => false))
|
|
598
|
+
if (exists) clashes.push(planned.file)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (clashes.length > 0) {
|
|
602
|
+
return yield* Effect.fail(
|
|
603
|
+
new OutputError(
|
|
604
|
+
outDir,
|
|
605
|
+
`would overwrite ${clashes.length} existing file${clashes.length === 1 ? "" : "s"} ` +
|
|
606
|
+
`(${clashes.slice(0, 3).join(", ")}${clashes.length > 3 ? ", …" : ""}). ` +
|
|
607
|
+
`Pass --force true to replace ${clashes.length === 1 ? "it" : "them"}, or choose a different --out.`
|
|
608
|
+
)
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
yield* fs.makeDirectory(outDir, { recursive: true }).pipe(
|
|
614
|
+
Effect.mapError((cause) => new OutputError(outDir, "could not create output directory", { cause }))
|
|
615
|
+
)
|
|
616
|
+
})
|