wyvrnpm 2.10.2 → 2.12.2
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/README.md +1914 -1860
- package/bin/{wyvrn.js → wyvrnpm.js} +66 -0
- package/cmake/cpp.cmake +9 -9
- package/cmake/functions.cmake +224 -224
- package/cmake/macros.cmake +284 -284
- package/package.json +3 -2
- package/src/auth.js +66 -66
- package/src/binary-dir.js +95 -0
- package/src/bootstrap/cookbook.js +196 -196
- package/src/bootstrap/detect.js +150 -150
- package/src/bootstrap/index.js +220 -220
- package/src/bootstrap/version.js +72 -72
- package/src/build/cache.js +344 -344
- package/src/build/clone.js +170 -170
- package/src/build/cmake.js +342 -342
- package/src/build/index.js +299 -297
- package/src/build/msvc-env.js +260 -260
- package/src/build/recipe.js +188 -188
- package/src/commands/add.js +141 -141
- package/src/commands/bootstrap.js +96 -96
- package/src/commands/build.js +482 -452
- package/src/commands/cache.js +189 -189
- package/src/commands/clean.js +80 -80
- package/src/commands/configure.js +92 -92
- package/src/commands/init.js +70 -70
- package/src/commands/install-skill.js +115 -115
- package/src/commands/install.js +730 -674
- package/src/commands/link.js +320 -320
- package/src/commands/profile.js +237 -237
- package/src/commands/publish.js +584 -555
- package/src/commands/show.js +252 -252
- package/src/commands/version.js +187 -0
- package/src/compat.js +273 -273
- package/src/conf/index.js +415 -391
- package/src/conf/namespaces.js +94 -94
- package/src/context.js +230 -230
- package/src/http-fetch.js +53 -53
- package/src/ignore.js +118 -118
- package/src/logger.js +122 -122
- package/src/options.js +303 -303
- package/src/settings-overrides.js +152 -152
- package/src/toolchain/deps.js +164 -164
- package/src/toolchain/index.js +212 -212
- package/src/toolchain/presets.js +356 -340
- package/src/toolchain/template.cmake +77 -77
- package/src/upload-built.js +265 -265
- package/src/version-range.js +301 -301
- package/src/zip-safe.js +52 -52
- package/src/zip-stream.js +126 -0
package/src/commands/install.js
CHANGED
|
@@ -1,674 +1,730 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
const { normalizeDependencies } = require('../manifest');
|
|
7
|
-
const { resolveDependencies } = require('../resolve');
|
|
8
|
-
const { downloadDependencies } = require('../download');
|
|
9
|
-
const { wyvrnFetch } = require('../http-fetch');
|
|
10
|
-
const { resolveEffectiveConf, cmakeCacheVariables, unflattenConf } = require('../conf');
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
13
|
-
const {
|
|
14
|
-
const {
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
'
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
'
|
|
81
|
-
|
|
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
|
-
console.log(`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
if (
|
|
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
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
// dep
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
// - effective
|
|
226
|
-
// -
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
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
|
-
const
|
|
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
|
-
|
|
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
|
-
downloadOptions
|
|
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
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
profileName,
|
|
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
|
-
// configure args
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const { normalizeDependencies } = require('../manifest');
|
|
7
|
+
const { resolveDependencies } = require('../resolve');
|
|
8
|
+
const { downloadDependencies } = require('../download');
|
|
9
|
+
const { wyvrnFetch } = require('../http-fetch');
|
|
10
|
+
const { resolveEffectiveConf, cmakeCacheVariables, unflattenConf } = require('../conf');
|
|
11
|
+
const { resolveBinaryDirRoot } = require('../binary-dir');
|
|
12
|
+
const { hashProfile, formatProfile, mergeProfile } = require('../profile');
|
|
13
|
+
const { generateToolchain, generateCMakePresets } = require('../toolchain');
|
|
14
|
+
const { normalizeRecipe, hasBuildRecipe } = require('../build/recipe');
|
|
15
|
+
const { buildContext } = require('../context');
|
|
16
|
+
const {
|
|
17
|
+
normalizeOptionsDeclaration,
|
|
18
|
+
resolveEffectiveOptions,
|
|
19
|
+
} = require('../options');
|
|
20
|
+
const {
|
|
21
|
+
normalizeSettingsOverrides,
|
|
22
|
+
resolveOverrideSettings,
|
|
23
|
+
} = require('../settings-overrides');
|
|
24
|
+
const {
|
|
25
|
+
resolveUploadDestination,
|
|
26
|
+
createUploadStats,
|
|
27
|
+
formatUploadSummary,
|
|
28
|
+
} = require('../upload-built');
|
|
29
|
+
const log = require('../logger');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolves and downloads all dependencies declared in the manifest, then writes
|
|
33
|
+
* a wyvrn.lock file recording exactly what was installed.
|
|
34
|
+
*
|
|
35
|
+
* v2 behaviour:
|
|
36
|
+
* - Loads the named build profile (--profile flag → config.defaultProfile → "default")
|
|
37
|
+
* - Each dependency can override individual profile settings via its `settings` field
|
|
38
|
+
* - Lock file records the full profile + per-package SHA256, profileHash, and git metadata
|
|
39
|
+
*
|
|
40
|
+
* @param {object} argv
|
|
41
|
+
*/
|
|
42
|
+
async function install(argv) {
|
|
43
|
+
// Shared per-invocation state (config, profile, manifest lazy, auth,
|
|
44
|
+
// CLI options/conf parsing, JSON-mode toggle). See src/context.js +
|
|
45
|
+
// claude/PLAN-COMMAND-CONTEXT.md. buildContext throws on input errors
|
|
46
|
+
// so they surface here with a single log+exit rather than deep in the
|
|
47
|
+
// command.
|
|
48
|
+
let ctx;
|
|
49
|
+
try {
|
|
50
|
+
ctx = buildContext(argv);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
log.error(err.message);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
rootDir,
|
|
58
|
+
manifestPath,
|
|
59
|
+
config,
|
|
60
|
+
cliOptions: cliOptionsByPkg,
|
|
61
|
+
flags,
|
|
62
|
+
} = ctx;
|
|
63
|
+
const { name: profileName, profile: baseProfile, fromFile } = ctx.profiles.host;
|
|
64
|
+
const { build: buildMode, uploadBuilt } = flags;
|
|
65
|
+
const jsonOut = flags.format === 'json';
|
|
66
|
+
|
|
67
|
+
// --upload-built implies a build step happened. `--build=never` +
|
|
68
|
+
// `--upload-built` is a usage error — fail fast rather than silently
|
|
69
|
+
// accept something that cannot upload anything.
|
|
70
|
+
if (uploadBuilt && buildMode === 'never') {
|
|
71
|
+
log.error(
|
|
72
|
+
'--upload-built requires --build=missing (or --build=always).\n' +
|
|
73
|
+
' Nothing will be built, so there is nothing to upload.',
|
|
74
|
+
);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (buildMode === 'always') {
|
|
79
|
+
log.error(
|
|
80
|
+
'--build=always is not implemented (requires PLAN-BUILD-MISSING phase 3).\n' +
|
|
81
|
+
' Use --build=never (default) or --build=missing.',
|
|
82
|
+
);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Auth / sources ────────────────────────────────────────────────────────
|
|
87
|
+
// Source filtering stays per-command (install takes URL array + pin
|
|
88
|
+
// resolution; publish takes name-or-URL; show takes URL array). The
|
|
89
|
+
// ctx-level config read above means we do NOT call readConfig() again.
|
|
90
|
+
let packageSources;
|
|
91
|
+
if (argv.source && argv.source.length > 0) {
|
|
92
|
+
packageSources = argv.source;
|
|
93
|
+
} else {
|
|
94
|
+
packageSources = config.installSources.map((s) => s.url);
|
|
95
|
+
if (packageSources.length > 0) {
|
|
96
|
+
log.info(`Using ${packageSources.length} source(s) from config`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (packageSources.length === 0) {
|
|
101
|
+
log.warn(
|
|
102
|
+
'no sources configured. ' +
|
|
103
|
+
'Pass --source <url> or run: wyvrnpm configure add-source --kind install ...',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Active build profile ──────────────────────────────────────────────────
|
|
108
|
+
// Resolved once during buildContext(); destructured at the top of this
|
|
109
|
+
// function. `profileName`, `baseProfile`, `fromFile` are already in scope.
|
|
110
|
+
log.info(`Profile: "${profileName}"${fromFile ? '' : ' (auto-detected)'}`);
|
|
111
|
+
console.log(formatProfile(baseProfile).split('\n').map((l) => ` ${l}`).join('\n'));
|
|
112
|
+
console.log(` Profile hash : ${hashProfile(baseProfile)}`);
|
|
113
|
+
|
|
114
|
+
// ── Lock file ─────────────────────────────────────────────────────────────
|
|
115
|
+
const lockPath = path.join(path.dirname(manifestPath), 'wyvrn.lock');
|
|
116
|
+
const lockedVersions = new Map();
|
|
117
|
+
|
|
118
|
+
if (fs.existsSync(lockPath)) {
|
|
119
|
+
try {
|
|
120
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
121
|
+
for (const [name, entry] of Object.entries(lock.packages ?? {})) {
|
|
122
|
+
const ver = typeof entry === 'string' ? entry : entry.version;
|
|
123
|
+
if (ver) lockedVersions.set(name, ver);
|
|
124
|
+
}
|
|
125
|
+
log.info(`Lock file found — ${lockedVersions.size} pinned package(s)`);
|
|
126
|
+
} catch {
|
|
127
|
+
log.warn('could not read wyvrn.lock, resolving from scratch');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Read manifest ─────────────────────────────────────────────────────────
|
|
132
|
+
// ctx.manifest is a lazy getter: first access triggers readManifest()
|
|
133
|
+
// and caches the result for the rest of this invocation.
|
|
134
|
+
const manifest = ctx.manifest;
|
|
135
|
+
|
|
136
|
+
// Normalize to { name: { version, settings } }
|
|
137
|
+
const normalizedDeps = normalizeDependencies(manifest.dependencies ?? manifest.Dependencies);
|
|
138
|
+
|
|
139
|
+
// For resolution, only pass name → version (settings don't affect resolution)
|
|
140
|
+
const depsForResolution = Object.fromEntries(
|
|
141
|
+
Object.entries(normalizedDeps).map(([name, d]) => [name, d.version]),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Per-package source pinning (EVALUATION.md S4). Deps declaring
|
|
145
|
+
// `"source": "primary-s3"` restrict to that named source's URL from
|
|
146
|
+
// config — typosquat / mirror-shadow defense. Unknown name → fail
|
|
147
|
+
// fast (silent fallback to fan-out would defeat the point).
|
|
148
|
+
//
|
|
149
|
+
// `argv.source` overrides config entirely; if the user passes
|
|
150
|
+
// `--source <url>` but a dep pins a name, we can't resolve the name
|
|
151
|
+
// → warn and fall back to fan-out across the CLI-supplied URLs.
|
|
152
|
+
//
|
|
153
|
+
// Transitive deps continue to fan out — a pinned top-level dep's
|
|
154
|
+
// recipe can reference whatever it wants.
|
|
155
|
+
const sourcesByName = new Map();
|
|
156
|
+
if (!argv.source || argv.source.length === 0) {
|
|
157
|
+
for (const s of config.installSources ?? []) {
|
|
158
|
+
if (s.name && s.url) sourcesByName.set(s.name, s.url);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const pinnedSources = new Map();
|
|
162
|
+
for (const [name, d] of Object.entries(normalizedDeps)) {
|
|
163
|
+
if (!d.source) continue;
|
|
164
|
+
if (argv.source && argv.source.length > 0) {
|
|
165
|
+
log.warn(
|
|
166
|
+
`"${name}" pins source "${d.source}" but --source was passed on CLI — ` +
|
|
167
|
+
`pin ignored for this run`,
|
|
168
|
+
);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const url = sourcesByName.get(d.source);
|
|
172
|
+
if (!url) {
|
|
173
|
+
log.error(
|
|
174
|
+
`"${name}" pins source "${d.source}" but no install source by that name is configured. ` +
|
|
175
|
+
`Run "wyvrnpm configure list" to see configured sources.`,
|
|
176
|
+
);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
pinnedSources.set(name, url);
|
|
180
|
+
log.info(`${name}: pinned to source "${d.source}"`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
log.info(
|
|
184
|
+
`Installing dependencies for "${manifest.name}" (platform: ${argv.platform})`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Manifests collected during resolution — reused below to read each dep's
|
|
188
|
+
// recipe-declared `options` block without a second HTTP round trip.
|
|
189
|
+
const depManifests = new Map();
|
|
190
|
+
|
|
191
|
+
const resolvedDeps = await resolveDependencies(
|
|
192
|
+
depsForResolution,
|
|
193
|
+
packageSources,
|
|
194
|
+
argv.platform,
|
|
195
|
+
wyvrnFetch,
|
|
196
|
+
lockedVersions,
|
|
197
|
+
depManifests,
|
|
198
|
+
// F13: prefer the consumer's own profileHash when picking which build
|
|
199
|
+
// entry to read wyvrn.json from. Today every published build for a
|
|
200
|
+
// given (name, version) ships the same manifest, so this is harmless;
|
|
201
|
+
// it future-proofs against per-profile manifest variance.
|
|
202
|
+
hashProfile(baseProfile),
|
|
203
|
+
pinnedSources,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// CLI `-o <pkg>:<name>=<value>` overrides come pre-parsed from
|
|
207
|
+
// buildContext as `cliOptionsByPkg` (shape: { pkg: { name: value } }).
|
|
208
|
+
|
|
209
|
+
// F11: pattern-based settings overrides. `manifest.settingsOverrides`
|
|
210
|
+
// is { "<glob>": { <setting>: <value> }, ... }. Validated + sorted by
|
|
211
|
+
// specificity once here; `resolveOverrideSettings(depName, ...)` then
|
|
212
|
+
// returns the merged-from-patterns settings for each dep. Literal
|
|
213
|
+
// per-dep `settings` still override these (authors pinning a specific
|
|
214
|
+
// dep win over a team-wide pattern).
|
|
215
|
+
let settingsOverrides;
|
|
216
|
+
try {
|
|
217
|
+
settingsOverrides = normalizeSettingsOverrides(manifest.settingsOverrides);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
log.error(err.message);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Build per-dependency enriched map ─────────────────────────────────────
|
|
224
|
+
// Each dep gets:
|
|
225
|
+
// - an effective profile (base + per-dep settings overrides)
|
|
226
|
+
// - effective options (recipe defaults + consumer overrides + CLI overrides)
|
|
227
|
+
// - profileHash computed from both (options only hashed in when declared)
|
|
228
|
+
const enrichedDeps = new Map();
|
|
229
|
+
for (const [name, version] of resolvedDeps) {
|
|
230
|
+
// F11: layer pattern overrides first (less-specific applied first
|
|
231
|
+
// inside resolveOverrideSettings), then the literal per-dep
|
|
232
|
+
// settings on top. Literal wins over any pattern.
|
|
233
|
+
const patternOverrides = resolveOverrideSettings(name, settingsOverrides);
|
|
234
|
+
const literalOverrides = normalizedDeps[name]?.settings ?? {};
|
|
235
|
+
const depOverrides = { ...patternOverrides, ...literalOverrides };
|
|
236
|
+
const effectiveProfile = mergeProfile(baseProfile, depOverrides);
|
|
237
|
+
|
|
238
|
+
const depManifest = depManifests.get(name);
|
|
239
|
+
const declaration = depManifest?.options
|
|
240
|
+
? normalizeOptionsDeclaration(depManifest.options, `${name}@${version}`)
|
|
241
|
+
: null;
|
|
242
|
+
const effectiveOptions = resolveEffectiveOptions(
|
|
243
|
+
declaration,
|
|
244
|
+
normalizedDeps[name]?.options ?? {},
|
|
245
|
+
cliOptionsByPkg[name] ?? {},
|
|
246
|
+
`${name}@${version}`,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const profileHash = hashProfile(effectiveProfile, effectiveOptions);
|
|
250
|
+
|
|
251
|
+
if (Object.keys(depOverrides).length > 0) {
|
|
252
|
+
log.info(
|
|
253
|
+
`${name}: settings override → profileHash ${profileHash}` +
|
|
254
|
+
` (${JSON.stringify(depOverrides)})`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (effectiveOptions) {
|
|
258
|
+
log.info(`${name}: options → ${JSON.stringify(effectiveOptions)}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
enrichedDeps.set(name, {
|
|
262
|
+
version,
|
|
263
|
+
profileHash,
|
|
264
|
+
profile: effectiveProfile,
|
|
265
|
+
options: effectiveOptions,
|
|
266
|
+
// S4: pinned install source (name + URL). Only set for direct
|
|
267
|
+
// deps that declared `"source": "..."`; transitive deps stay
|
|
268
|
+
// undefined so they fan out across configured sources.
|
|
269
|
+
pinnedSource: pinnedSources.has(name)
|
|
270
|
+
? { name: normalizedDeps[name].source, url: pinnedSources.get(name) }
|
|
271
|
+
: undefined,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Resolve effective conf (PLAN-CONF.md §4) ──────────────────────────────
|
|
276
|
+
// Four layers (phase 2): CLI > wyvrn.local.json > profile > recipe.
|
|
277
|
+
// Non-ABI; does not fold into profileHash. Feeds into CMakePresets.json
|
|
278
|
+
// cacheVariables below. `baseProfile` may carry its own `conf` block —
|
|
279
|
+
// an auto-detected profile (no on-disk file) does not.
|
|
280
|
+
let effectiveConfResult;
|
|
281
|
+
try {
|
|
282
|
+
effectiveConfResult = resolveEffectiveConf({
|
|
283
|
+
manifest,
|
|
284
|
+
rootDir,
|
|
285
|
+
cliConf: argv.conf, // yargs array: string[]
|
|
286
|
+
profile: baseProfile, // named profile may carry a `conf` block (phase 2)
|
|
287
|
+
});
|
|
288
|
+
} catch (err) {
|
|
289
|
+
log.error(`conf resolution failed: ${err.message}`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
const effectiveConf = effectiveConfResult.flat;
|
|
293
|
+
if (effectiveConfResult.localOverlayPath) {
|
|
294
|
+
log.info(`Local overlay: ${effectiveConfResult.localOverlayPath}`);
|
|
295
|
+
// One-line nudge when wyvrn.local.json exists but isn't gitignored —
|
|
296
|
+
// catching the existing-project case init.js's scaffold can't cover.
|
|
297
|
+
// Non-fatal; we never rewrite the user's .gitignore at install time.
|
|
298
|
+
const gitignorePath = path.join(rootDir, '.gitignore');
|
|
299
|
+
if (fs.existsSync(gitignorePath)) {
|
|
300
|
+
const gi = fs.readFileSync(gitignorePath, 'utf8');
|
|
301
|
+
const ignored = gi.split(/\r?\n/).some((l) => l.trim() === 'wyvrn.local.json');
|
|
302
|
+
if (!ignored) {
|
|
303
|
+
log.warn(
|
|
304
|
+
`wyvrn.local.json is present but not in ${gitignorePath} — ` +
|
|
305
|
+
`add a line "wyvrn.local.json" to keep dev-local overrides out of version control.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (Object.keys(effectiveConf).length > 0) {
|
|
311
|
+
log.info(`Effective conf: ${JSON.stringify(effectiveConf)}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── Resolve binaryDirRoot (CLI > wyvrn.local.json > "build") ─────────────
|
|
315
|
+
// Sibling channel to `conf` — non-ABI, dev-local. Folds into the
|
|
316
|
+
// generated preset's `binaryDir` so projects with a `BUILD` Bazel/Buck
|
|
317
|
+
// file at the root (gRPC, …) can sidestep the case-insensitive-FS
|
|
318
|
+
// collision against `build/`. See src/binary-dir.js.
|
|
319
|
+
let binaryDirRoot;
|
|
320
|
+
try {
|
|
321
|
+
binaryDirRoot = resolveBinaryDirRoot({
|
|
322
|
+
cliBuildDir: argv.buildDir,
|
|
323
|
+
localOverlayBinaryDirRoot: effectiveConfResult.localBinaryDirRoot,
|
|
324
|
+
});
|
|
325
|
+
} catch (err) {
|
|
326
|
+
log.error(err.message);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
if (binaryDirRoot !== 'build') {
|
|
330
|
+
log.info(`binaryDirRoot: ${binaryDirRoot} (preset binaryDir → \${sourceDir}/${binaryDirRoot}/wyvrn-${profileName})`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── --dry-run: print the resolved plan and exit (EVALUATION.md F6) ────────
|
|
334
|
+
// Runs the resolver + profile/options math end-to-end, then reports what
|
|
335
|
+
// WOULD be installed — no download, no wyvrn.lock, no wyvrn_internal/, no
|
|
336
|
+
// toolchain. Preserves --format=json for CI ingestion.
|
|
337
|
+
if (argv.dryRun) {
|
|
338
|
+
emitDryRunPlan({
|
|
339
|
+
enrichedDeps,
|
|
340
|
+
resolvedDeps,
|
|
341
|
+
baseProfile,
|
|
342
|
+
profileName,
|
|
343
|
+
buildMode,
|
|
344
|
+
packageSources,
|
|
345
|
+
effectiveConf,
|
|
346
|
+
jsonOut,
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const razerDir = path.join(rootDir, 'wyvrn_internal');
|
|
352
|
+
|
|
353
|
+
// Extract auth from the first install source (if any). Prefer
|
|
354
|
+
// `tokenEnv` over a literal `token` stored in config — closes
|
|
355
|
+
// EVALUATION.md S8 (no plaintext secret persisted, value is looked up
|
|
356
|
+
// fresh each run).
|
|
357
|
+
const firstInstallSrc = config.installSources?.[0];
|
|
358
|
+
const downloadOptions = {
|
|
359
|
+
awsProfile: firstInstallSrc?.profile ?? undefined,
|
|
360
|
+
token: ctx.auth.for(firstInstallSrc).token,
|
|
361
|
+
buildMode,
|
|
362
|
+
profileName,
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// ── Resolve upload destination up-front when --upload-built is set ──────
|
|
366
|
+
// Failing here (no publish source configured) avoids spending minutes on
|
|
367
|
+
// a source-build whose output we can't deliver.
|
|
368
|
+
if (uploadBuilt) {
|
|
369
|
+
const resolved = resolveUploadDestination(
|
|
370
|
+
{
|
|
371
|
+
uploadSource: argv.uploadSource,
|
|
372
|
+
awsProfile: argv.awsProfile ?? firstInstallSrc?.profile,
|
|
373
|
+
token: ctx.auth.for(firstInstallSrc).token,
|
|
374
|
+
},
|
|
375
|
+
config,
|
|
376
|
+
);
|
|
377
|
+
if (!resolved) {
|
|
378
|
+
log.error(
|
|
379
|
+
'--upload-built was passed but no upload destination could be resolved.\n' +
|
|
380
|
+
' Pass --upload-source <url-or-name> or configure a publish source:\n' +
|
|
381
|
+
' wyvrnpm configure add-source --kind publish --name ... --url ...',
|
|
382
|
+
);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
downloadOptions.uploadBuilt = true;
|
|
386
|
+
downloadOptions.uploadSource = resolved.uploadSource;
|
|
387
|
+
downloadOptions.uploadAuth = resolved.uploadAuth;
|
|
388
|
+
downloadOptions.uploadStats = createUploadStats();
|
|
389
|
+
|
|
390
|
+
if (resolved.uploadSource !== (packageSources[0] ?? '')) {
|
|
391
|
+
log.info(
|
|
392
|
+
`upload-built: install source(s) and upload source differ —\n` +
|
|
393
|
+
` install: ${packageSources.join(', ') || '(none)'}\n` +
|
|
394
|
+
` upload : ${resolved.uploadSource}`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const lockEntries = await downloadDependencies(
|
|
400
|
+
enrichedDeps,
|
|
401
|
+
packageSources,
|
|
402
|
+
argv.platform,
|
|
403
|
+
razerDir,
|
|
404
|
+
wyvrnFetch,
|
|
405
|
+
argv.timeout,
|
|
406
|
+
downloadOptions,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// ── Write v2 lock file ────────────────────────────────────────────────────
|
|
410
|
+
const sortedPackages = {};
|
|
411
|
+
for (const key of [...resolvedDeps.keys()].sort()) {
|
|
412
|
+
sortedPackages[key] = lockEntries?.get(key) ?? { version: resolvedDeps.get(key) };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const lockData = {
|
|
416
|
+
wyvrnVersion: 2,
|
|
417
|
+
generatedAt: new Date().toISOString(),
|
|
418
|
+
platform: argv.platform,
|
|
419
|
+
profileName,
|
|
420
|
+
profile: baseProfile,
|
|
421
|
+
profileHash: hashProfile(baseProfile),
|
|
422
|
+
packages: sortedPackages,
|
|
423
|
+
// PLAN-CONF.md §4.8.1: informational only, not consulted during
|
|
424
|
+
// resolution. Omitted entirely when empty so pre-conf locks
|
|
425
|
+
// remain byte-identical to today's output.
|
|
426
|
+
...(Object.keys(effectiveConf).length > 0
|
|
427
|
+
? { effectiveConf: unflattenConf(effectiveConf) }
|
|
428
|
+
: {}),
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2) + '\n', 'utf8');
|
|
432
|
+
log.info(`Lock file written to ${lockPath}`);
|
|
433
|
+
|
|
434
|
+
// ── CMake toolchain generation ────────────────────────────────────────────
|
|
435
|
+
const packageNames = [...resolvedDeps.keys()].sort();
|
|
436
|
+
const { toolchain } = generateToolchain({
|
|
437
|
+
destDir: razerDir,
|
|
438
|
+
profile: baseProfile,
|
|
439
|
+
profileName,
|
|
440
|
+
profileHash: hashProfile(baseProfile),
|
|
441
|
+
packageNames,
|
|
442
|
+
});
|
|
443
|
+
log.info(`CMake toolchain written to ${toolchain}`);
|
|
444
|
+
|
|
445
|
+
// ── CMakePresets.json emission ────────────────────────────────────────────
|
|
446
|
+
const toolchainRelPath = path
|
|
447
|
+
.relative(rootDir, toolchain)
|
|
448
|
+
.replace(/\\/g, '/');
|
|
449
|
+
|
|
450
|
+
// Derive generator + cacheVariables from the project's own `build` recipe
|
|
451
|
+
// so `wyvrnpm build` locally honours the same knobs the source-build /
|
|
452
|
+
// publish paths use. Without this, `build.configure` is advisory metadata
|
|
453
|
+
// and CMake falls back to its default generator (VS on Windows) — which
|
|
454
|
+
// breaks publishers whose recipe disables example/test subprojects (F15).
|
|
455
|
+
let presetGenerator = null;
|
|
456
|
+
let presetCacheVariables = null;
|
|
457
|
+
let presetInstallDir = null;
|
|
458
|
+
let presetConfigs = null;
|
|
459
|
+
if (hasBuildRecipe(manifest)) {
|
|
460
|
+
try {
|
|
461
|
+
// Resolve the project's own options to expand ${options.*} in its
|
|
462
|
+
// configure args. When the project declares no options, this is null
|
|
463
|
+
// and the recipe's configure / buildArgs must not contain templates
|
|
464
|
+
// (normalizeRecipe enforces that).
|
|
465
|
+
const projectDeclaration = manifest.options
|
|
466
|
+
? normalizeOptionsDeclaration(manifest.options, manifest.name)
|
|
467
|
+
: null;
|
|
468
|
+
const projectCliOverrides = cliOptionsByPkg[manifest.name] ?? {};
|
|
469
|
+
const projectEffectiveOptions = resolveEffectiveOptions(
|
|
470
|
+
projectDeclaration, {}, projectCliOverrides, manifest.name,
|
|
471
|
+
);
|
|
472
|
+
const recipe = normalizeRecipe(manifest.build, projectEffectiveOptions, manifest);
|
|
473
|
+
|
|
474
|
+
if (recipe.generators.length > 0) {
|
|
475
|
+
// First entry in the generator fallback chain. Users whose system
|
|
476
|
+
// lacks the first pick can override via CMakeUserPresets.json
|
|
477
|
+
// (which CMake merges in automatically).
|
|
478
|
+
[presetGenerator] = recipe.generators;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Fold -DKEY=VALUE from recipe.configure into cacheVariables. Other
|
|
482
|
+
// configure args (anything not matching `-DKEY=VALUE`) can't be
|
|
483
|
+
// expressed in a CMake preset and are silently skipped here — the
|
|
484
|
+
// source-build path still sees them verbatim.
|
|
485
|
+
const cacheVars = {};
|
|
486
|
+
for (const arg of recipe.configure) {
|
|
487
|
+
const match = arg.match(/^-D([^=]+)=(.*)$/);
|
|
488
|
+
if (match) cacheVars[match[1]] = match[2];
|
|
489
|
+
}
|
|
490
|
+
if (Object.keys(cacheVars).length > 0) presetCacheVariables = cacheVars;
|
|
491
|
+
|
|
492
|
+
// Propagate `build.installDir` so that `wyvrnpm build --install` lands
|
|
493
|
+
// under the project's build tree instead of CMake's system default
|
|
494
|
+
// (C:/Program Files on Windows, /usr/local on Unix). Same semantics as
|
|
495
|
+
// the source-build path which passes -DCMAKE_INSTALL_PREFIX explicitly.
|
|
496
|
+
presetInstallDir = recipe.installDir;
|
|
497
|
+
|
|
498
|
+
// Propagate `build.configs` so multi-config generators (Ninja
|
|
499
|
+
// Multi-Config, Visual Studio) generate build files for every
|
|
500
|
+
// config the recipe declares. Pre-2.8.1 the preset only carried
|
|
501
|
+
// Debug/Release, so a recipe listing MinSizeRel would fail with
|
|
502
|
+
// `ninja: error: loading 'build-MinSizeRel.ninja'`. CLAUDE.md §8
|
|
503
|
+
// documented that sharp edge; this closes it.
|
|
504
|
+
presetConfigs = recipe.configs;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
log.warn(
|
|
507
|
+
`could not derive CMake preset config from the project's build recipe: ${err.message}\n` +
|
|
508
|
+
` the preset will be emitted without a generator or recipe cacheVariables. ` +
|
|
509
|
+
`Fix the recipe or override via CMakeUserPresets.json.`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// PLAN-CONF.md §4.3 / §5 presets sink: fold effectiveConf's
|
|
515
|
+
// cmake.cache namespace into the preset's cacheVariables. Runs
|
|
516
|
+
// regardless of whether the project has its own build recipe —
|
|
517
|
+
// a consumer with no recipe but a wyvrn.local.json conf still gets
|
|
518
|
+
// their overrides into the generated preset.
|
|
519
|
+
//
|
|
520
|
+
// Conf wins on collisions with build.configure-derived vars. The
|
|
521
|
+
// recipe-normaliser blocks author-side collisions (§4.7); a
|
|
522
|
+
// collision here can only come from a consumer's CLI --conf or
|
|
523
|
+
// wyvrn.local.json overriding a value the author also sets via
|
|
524
|
+
// build.configure. Warn so the override is visible.
|
|
525
|
+
const confCacheVars = cmakeCacheVariables(effectiveConf);
|
|
526
|
+
if (Object.keys(confCacheVars).length > 0) {
|
|
527
|
+
presetCacheVariables = presetCacheVariables ?? {};
|
|
528
|
+
for (const [k, v] of Object.entries(confCacheVars)) {
|
|
529
|
+
if (k in presetCacheVariables && presetCacheVariables[k] !== v) {
|
|
530
|
+
log.warn(
|
|
531
|
+
`conf override: cmake.cache.${k} = ${JSON.stringify(v)} ` +
|
|
532
|
+
`supersedes build.configure's ${JSON.stringify(presetCacheVariables[k])}`,
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
presetCacheVariables[k] = v;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const presetResult = generateCMakePresets({
|
|
540
|
+
projectRoot: rootDir,
|
|
541
|
+
profileName,
|
|
542
|
+
profileHash: hashProfile(baseProfile),
|
|
543
|
+
toolchainRelPath,
|
|
544
|
+
generator: presetGenerator,
|
|
545
|
+
extraCacheVariables: presetCacheVariables,
|
|
546
|
+
installDir: presetInstallDir,
|
|
547
|
+
profileArch: baseProfile.arch ?? null,
|
|
548
|
+
configs: presetConfigs,
|
|
549
|
+
binaryDirRoot,
|
|
550
|
+
});
|
|
551
|
+
if (presetResult.action === 'skipped') {
|
|
552
|
+
log.warn(
|
|
553
|
+
'could not write presets — both CMakePresets.json and ' +
|
|
554
|
+
'CMakeUserPresets.json exist and are user-owned. Add this to your presets:\n' +
|
|
555
|
+
` { "name": "wyvrn-${profileName}", "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": "\${sourceDir}/${toolchainRelPath}" } }`,
|
|
556
|
+
);
|
|
557
|
+
} else {
|
|
558
|
+
log.info(
|
|
559
|
+
`CMake presets ${presetResult.action}: ${presetResult.path}` +
|
|
560
|
+
(presetResult.isUser ? ' (user presets — CMakePresets.json is user-owned)' : ''),
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ── Classify install outcomes ──────────────────────────────────────────
|
|
565
|
+
// CLAUDE.md principles 5 + 8: a sha256 mismatch / extract failure /
|
|
566
|
+
// not-found / no-exact-match / source-build-failed is a hard failure, not
|
|
567
|
+
// a silent-success-with-warning. The lockfile + toolchain were already
|
|
568
|
+
// written above so the partial state stays inspectable; we exit non-zero
|
|
569
|
+
// here so CI doesn't take "Done — N installed" at face value when one of
|
|
570
|
+
// the deps actually failed.
|
|
571
|
+
const SUCCESS_RESOLVED = new Set([
|
|
572
|
+
'v1', 'v2', 'v2-compat', 'v2-source-build', 'cached', 'link',
|
|
573
|
+
]);
|
|
574
|
+
const failed = [];
|
|
575
|
+
for (const [name, entry] of lockEntries ?? []) {
|
|
576
|
+
if (!SUCCESS_RESOLVED.has(entry.resolvedFrom)) {
|
|
577
|
+
failed.push({ name, version: entry.version, resolvedFrom: entry.resolvedFrom });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const count = resolvedDeps.size;
|
|
582
|
+
const installedCount = count - failed.length;
|
|
583
|
+
|
|
584
|
+
if (failed.length === 0) {
|
|
585
|
+
log.success(`Done — ${count} package${count !== 1 ? 's' : ''} installed.`);
|
|
586
|
+
} else {
|
|
587
|
+
log.error(
|
|
588
|
+
`Install incomplete — ${installedCount}/${count} package${count !== 1 ? 's' : ''} installed, ` +
|
|
589
|
+
`${failed.length} failed:`,
|
|
590
|
+
);
|
|
591
|
+
for (const f of failed) {
|
|
592
|
+
log.error(` ${f.name}@${f.version} — ${f.resolvedFrom}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ── upload-built summary ────────────────────────────────────────────────
|
|
597
|
+
const uploadSummary = formatUploadSummary(downloadOptions.uploadStats);
|
|
598
|
+
if (uploadSummary) log.info(uploadSummary);
|
|
599
|
+
|
|
600
|
+
// ── JSON payload (--format=json) ────────────────────────────────────────
|
|
601
|
+
// All log.info/success above have gone to stderr thanks to setJsonMode.
|
|
602
|
+
// stdout gets exactly one JSON object, no trailing text.
|
|
603
|
+
if (jsonOut) {
|
|
604
|
+
const packages = [...resolvedDeps.keys()].sort().map((name) => {
|
|
605
|
+
const entry = lockEntries?.get(name) ?? {};
|
|
606
|
+
const consumerSpec = normalizedDeps[name]?.version ?? null;
|
|
607
|
+
const resolvedVer = entry.version ?? resolvedDeps.get(name);
|
|
608
|
+
const versionRange = (consumerSpec && consumerSpec !== resolvedVer) ? consumerSpec : null;
|
|
609
|
+
return {
|
|
610
|
+
name,
|
|
611
|
+
version: resolvedVer,
|
|
612
|
+
versionRange,
|
|
613
|
+
profileHash: entry.profileHash ?? null,
|
|
614
|
+
options: entry.options ?? null,
|
|
615
|
+
contentSha256: entry.contentSha256 ?? null,
|
|
616
|
+
gitSha: entry.gitSha ?? null,
|
|
617
|
+
resolvedFrom: entry.resolvedFrom ?? null,
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
const stats = downloadOptions.uploadStats;
|
|
622
|
+
const uploadSummaryJson = stats ? {
|
|
623
|
+
uploaded: stats.uploaded.length,
|
|
624
|
+
skipped: stats.skipped.length,
|
|
625
|
+
failed: stats.failed.length,
|
|
626
|
+
} : null;
|
|
627
|
+
|
|
628
|
+
const payload = {
|
|
629
|
+
command: 'install',
|
|
630
|
+
wyvrnpmVersion: require('../../package.json').version,
|
|
631
|
+
profile: baseProfile,
|
|
632
|
+
profileName,
|
|
633
|
+
profileHash: hashProfile(baseProfile),
|
|
634
|
+
packages,
|
|
635
|
+
lockFile: lockPath,
|
|
636
|
+
uploadSummary: uploadSummaryJson,
|
|
637
|
+
failed,
|
|
638
|
+
};
|
|
639
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ── Exit non-zero on any unsuccessful resolution ────────────────────────
|
|
643
|
+
if (failed.length > 0) {
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
// --dry-run plan emitter (EVALUATION.md F6)
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Render the resolved install plan WITHOUT touching disk. Called from
|
|
654
|
+
* `install()` when `--dry-run` is passed; exits the install flow before
|
|
655
|
+
* download, lockfile write, or toolchain generation.
|
|
656
|
+
*
|
|
657
|
+
* Text mode: human-readable per-dep summary.
|
|
658
|
+
* JSON mode (`--format=json`): structured payload on stdout with
|
|
659
|
+
* `command: "install-dry-run"` so log consumers can disambiguate it
|
|
660
|
+
* from a real install.
|
|
661
|
+
*/
|
|
662
|
+
function emitDryRunPlan({
|
|
663
|
+
enrichedDeps, resolvedDeps, baseProfile, profileName, buildMode,
|
|
664
|
+
packageSources, effectiveConf = {}, jsonOut,
|
|
665
|
+
}) {
|
|
666
|
+
const baseHash = hashProfile(baseProfile);
|
|
667
|
+
const packages = [...resolvedDeps.keys()].sort().map((name) => {
|
|
668
|
+
const entry = enrichedDeps.get(name) ?? {};
|
|
669
|
+
return {
|
|
670
|
+
name,
|
|
671
|
+
version: resolvedDeps.get(name),
|
|
672
|
+
profileHash: entry.profileHash ?? null,
|
|
673
|
+
options: entry.options ?? null,
|
|
674
|
+
// Whether the dep's effective profile differs from the consumer's
|
|
675
|
+
// base (i.e. per-dep settings override produced a distinct hash).
|
|
676
|
+
overridesBaseProfile: entry.profileHash && entry.profileHash !== baseHash,
|
|
677
|
+
};
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
if (jsonOut) {
|
|
681
|
+
const payload = {
|
|
682
|
+
command: 'install-dry-run',
|
|
683
|
+
wyvrnpmVersion: require('../../package.json').version,
|
|
684
|
+
profile: baseProfile,
|
|
685
|
+
profileName,
|
|
686
|
+
profileHash: baseHash,
|
|
687
|
+
buildMode,
|
|
688
|
+
sources: packageSources,
|
|
689
|
+
effectiveConf: Object.keys(effectiveConf).length > 0
|
|
690
|
+
? unflattenConf(effectiveConf)
|
|
691
|
+
: {},
|
|
692
|
+
packages,
|
|
693
|
+
};
|
|
694
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
log.info('── install plan (--dry-run) ─────────────────────────────────────');
|
|
699
|
+
log.info(`Profile : "${profileName}" [${baseHash}]`);
|
|
700
|
+
log.info(`Build : ${buildMode}`);
|
|
701
|
+
log.info(`Sources : ${packageSources.length > 0 ? packageSources.join(', ') : '(none)'}`);
|
|
702
|
+
const confKeys = Object.keys(effectiveConf).sort();
|
|
703
|
+
if (confKeys.length > 0) {
|
|
704
|
+
log.info(`Conf : ${confKeys.map((k) => `${k}=${effectiveConf[k]}`).join(', ')}`);
|
|
705
|
+
}
|
|
706
|
+
if (packages.length === 0) {
|
|
707
|
+
log.info('(no dependencies)');
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const nameW = Math.max(4, ...packages.map((p) => p.name.length));
|
|
712
|
+
const versionW = Math.max(7, ...packages.map((p) => p.version.length));
|
|
713
|
+
const header = ` ${'name'.padEnd(nameW)} ${'version'.padEnd(versionW)} profileHash options`;
|
|
714
|
+
log.info(header);
|
|
715
|
+
log.info(` ${'-'.repeat(nameW)} ${'-'.repeat(versionW)} ---------------- --------------------`);
|
|
716
|
+
for (const p of packages) {
|
|
717
|
+
const override = p.overridesBaseProfile ? ' *' : ' ';
|
|
718
|
+
const optStr = p.options ? JSON.stringify(p.options) : '-';
|
|
719
|
+
log.info(
|
|
720
|
+
` ${p.name.padEnd(nameW)} ${p.version.padEnd(versionW)} ${p.profileHash ?? '(none) '}${override} ${optStr}`,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
log.info('');
|
|
724
|
+
log.info('(nothing was downloaded; wyvrn.lock / wyvrn_internal / toolchain untouched)');
|
|
725
|
+
if (packages.some((p) => p.overridesBaseProfile)) {
|
|
726
|
+
log.info(' * = per-dep settings override produces a profileHash distinct from the base.');
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
module.exports = install;
|