gulp-mu-gulp-api 0.3.9 → 0.3.11
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 +24 -0
- package/package.json +1 -1
- package/src/index.mjs +843 -770
package/src/index.mjs
CHANGED
|
@@ -1,770 +1,843 @@
|
|
|
1
|
-
// ===========================================
|
|
2
|
-
// gulp-mu-gulp-api — µGulp task API
|
|
3
|
-
// © 2026 Meinolf Amekudzi
|
|
4
|
-
// (published under MIT license)
|
|
5
|
-
// ===========================================
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Public API for gulp tasks running under the µGulp orchestrator.
|
|
9
|
-
*
|
|
10
|
-
* Under µGulp every task runs inside a forked worker process with an IPC
|
|
11
|
-
* channel to the engine; this module talks to that channel directly, so it
|
|
12
|
-
* has zero coupling to µGulp internals and works from any npm package in
|
|
13
|
-
* the pipeline. Without µGulp (plain "gulp" CLI run) every function
|
|
14
|
-
* degrades gracefully: progress renders on the terminal, inputs fall back
|
|
15
|
-
* to readline prompts on a TTY or to the declared defaults otherwise.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* import { ReportProgress, RequestTextInput, RequestColorInput } from 'gulp-mu-gulp-api';
|
|
19
|
-
*
|
|
20
|
-
* export async function BUILD_THEME() {
|
|
21
|
-
* let accent = await RequestColorInput({ label: 'Accent color', default: '#00e5ff' });
|
|
22
|
-
* for (let step = 0; step < 10; step++) {
|
|
23
|
-
* // ... work ...
|
|
24
|
-
* ReportProgress((step + 1) / 10, 'compiling theme');
|
|
25
|
-
* }
|
|
26
|
-
* }
|
|
27
|
-
*
|
|
28
|
-
* @module gulp-mu-gulp-api
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
// Localized console output (i18x). Re-exported so tasks import everything
|
|
32
|
-
// from one place: `import { Log, Warn, ReportProgress } from 'gulp-mu-gulp-api'`.
|
|
33
|
-
export { Log, Warn, LogError, Translate, FormatValue, GetLid, SetLid, GetTimeZone, DateInTimeZone, TimeZoneOffset, InstallStringExtensions, InstallFormatPrototypes, InstallHyphenPrototypes } from './i18x.mjs';
|
|
34
|
-
export { WordHyphenation, Hyphenation, LoadHyphenData } from './i18x-hyphen.mjs';
|
|
35
|
-
import * as I18x from './i18x.mjs';
|
|
36
|
-
|
|
37
|
-
let uiRequestCounter = 0;
|
|
38
|
-
let ipcListenerAttached = false;
|
|
39
|
-
let lastLoggedPercent = -1;
|
|
40
|
-
const pendingUiRequests = new Map();
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @returns {boolean} true when the task runs inside a µGulp worker with an
|
|
44
|
-
* attached dashboard (progress and inputs are rendered in the webview)
|
|
45
|
-
*/
|
|
46
|
-
export function IsMicroGulp() {
|
|
47
|
-
// The worker pool sets MICROGULP=1 for every forked task process
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
* the
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* @param {
|
|
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
|
-
process.send
|
|
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
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* @param {object
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
_entry.
|
|
177
|
-
_entry.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* @param {object}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
if (payload
|
|
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
|
-
if (
|
|
265
|
-
console.log(payload.
|
|
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
|
-
* the
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* @param {
|
|
313
|
-
* @param {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* @param {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (
|
|
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
|
-
title:
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function
|
|
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
|
-
function
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
return {
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1
|
+
// ===========================================
|
|
2
|
+
// gulp-mu-gulp-api — µGulp task API
|
|
3
|
+
// © 2026 Meinolf Amekudzi
|
|
4
|
+
// (published under MIT license)
|
|
5
|
+
// ===========================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Public API for gulp tasks running under the µGulp orchestrator.
|
|
9
|
+
*
|
|
10
|
+
* Under µGulp every task runs inside a forked worker process with an IPC
|
|
11
|
+
* channel to the engine; this module talks to that channel directly, so it
|
|
12
|
+
* has zero coupling to µGulp internals and works from any npm package in
|
|
13
|
+
* the pipeline. Without µGulp (plain "gulp" CLI run) every function
|
|
14
|
+
* degrades gracefully: progress renders on the terminal, inputs fall back
|
|
15
|
+
* to readline prompts on a TTY or to the declared defaults otherwise.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { ReportProgress, RequestTextInput, RequestColorInput } from 'gulp-mu-gulp-api';
|
|
19
|
+
*
|
|
20
|
+
* export async function BUILD_THEME() {
|
|
21
|
+
* let accent = await RequestColorInput({ label: 'Accent color', default: '#00e5ff' });
|
|
22
|
+
* for (let step = 0; step < 10; step++) {
|
|
23
|
+
* // ... work ...
|
|
24
|
+
* ReportProgress((step + 1) / 10, 'compiling theme');
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* @module gulp-mu-gulp-api
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// Localized console output (i18x). Re-exported so tasks import everything
|
|
32
|
+
// from one place: `import { Log, Warn, ReportProgress } from 'gulp-mu-gulp-api'`.
|
|
33
|
+
export { Log, Warn, LogError, Translate, FormatValue, GetLid, SetLid, GetTimeZone, DateInTimeZone, TimeZoneOffset, InstallStringExtensions, InstallFormatPrototypes, InstallHyphenPrototypes } from './i18x.mjs';
|
|
34
|
+
export { WordHyphenation, Hyphenation, LoadHyphenData } from './i18x-hyphen.mjs';
|
|
35
|
+
import * as I18x from './i18x.mjs';
|
|
36
|
+
|
|
37
|
+
let uiRequestCounter = 0;
|
|
38
|
+
let ipcListenerAttached = false;
|
|
39
|
+
let lastLoggedPercent = -1;
|
|
40
|
+
const pendingUiRequests = new Map();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @returns {boolean} true when the task runs inside a µGulp worker with an
|
|
44
|
+
* attached dashboard (progress and inputs are rendered in the webview)
|
|
45
|
+
*/
|
|
46
|
+
export function IsMicroGulp() {
|
|
47
|
+
// The worker pool sets MICROGULP=1 for every forked task process and
|
|
48
|
+
// connects an IPC channel (process.send). Child processes spawned from
|
|
49
|
+
// within a task inherit the env flag but not IPC — they must not be
|
|
50
|
+
// treated as dashboard workers.
|
|
51
|
+
return process.env.MICROGULP === '1' && typeof process.send === 'function';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Brand-aligned alias for {@link IsMicroGulp}. */
|
|
55
|
+
export const IsµGulp = IsMicroGulp;
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* Task metadata (µDisplayName, µDescription, µTooltip, µGroup) is localized
|
|
59
|
+
* the same way as any other i18x phrase: with the context tag written *into*
|
|
60
|
+
* the source phrase and a String.prototype.i18xRegister() marker so the
|
|
61
|
+
* i18xe-sync tooling extracts it and the dashboard can translate it later.
|
|
62
|
+
*
|
|
63
|
+
* MAKE_BUILDS.µDisplayName =
|
|
64
|
+
* 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
|
|
65
|
+
*
|
|
66
|
+
* i18xRegister() returns the phrase unchanged (the i18x key), so the classic
|
|
67
|
+
* gulp CLI keeps seeing a plain string while the dashboard translates it via
|
|
68
|
+
* the merged project dictionary. The prototype is installed by
|
|
69
|
+
* InstallStringExtensions() (auto-run on import of this module).
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
// -------------------------------------------------
|
|
73
|
+
// progress
|
|
74
|
+
// -------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Reports task progress to the µGulp dashboard (determinate neon progress
|
|
78
|
+
* bar in the task's sector). CLI fallback: an updating line on a TTY,
|
|
79
|
+
* 10%-step log lines otherwise.
|
|
80
|
+
*
|
|
81
|
+
* @param {number} _value progress in the range 0..1
|
|
82
|
+
* @param {string} [_label] short status label shown next to the bar
|
|
83
|
+
*/
|
|
84
|
+
export function ReportProgress(_value, _label) {
|
|
85
|
+
let value = Math.max(0, Math.min(1, Number(_value) || 0));
|
|
86
|
+
if (IsMicroGulp()) {
|
|
87
|
+
process.send({ type: 'progress', value, label: _label });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let percent = Math.round(value * 100);
|
|
91
|
+
if (process.stdout.isTTY) {
|
|
92
|
+
process.stdout.write('\r' + (_label ?? 'progress') + ' ' + percent + '%' + (value >= 1 ? '\n' : ''));
|
|
93
|
+
} else if (percent >= 100 || percent - lastLoggedPercent >= 10) {
|
|
94
|
+
lastLoggedPercent = percent >= 100 ? -1 : percent;
|
|
95
|
+
console.log((_label ?? 'progress') + ' ' + percent + '%');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convenience wrapper that carries a fixed label.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} [_label]
|
|
103
|
+
* @returns {{Update: function(number, string=): void, Done: function(): void}}
|
|
104
|
+
*/
|
|
105
|
+
export function CreateProgress(_label) {
|
|
106
|
+
return {
|
|
107
|
+
Update(_value, _stepLabel) {
|
|
108
|
+
ReportProgress(_value, _stepLabel ?? _label);
|
|
109
|
+
},
|
|
110
|
+
Done() {
|
|
111
|
+
ReportProgress(1, _label);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function _SendStructuredLog(_format, _payload) {
|
|
117
|
+
if (!IsMicroGulp()) return false;
|
|
118
|
+
if (typeof process.send !== 'function') return false;
|
|
119
|
+
process.send({ type: 'structured-log', format: _format, payload: _payload });
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sends a table to the dashboard log pane. The payload is JSON:
|
|
125
|
+
* `{ columns: string[] | {id, label}[], rows: (string|number)[][] | object[], hyphenate?: boolean }`.
|
|
126
|
+
* CLI fallback: a simple ASCII table on stdout.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} _spec table descriptor
|
|
129
|
+
*/
|
|
130
|
+
export function LogTable(_spec) {
|
|
131
|
+
let payload = _NormalizeTableSpec(_spec);
|
|
132
|
+
if (_SendStructuredLog('table', payload)) return;
|
|
133
|
+
console.log(_AsciiTable(payload));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sends a tree structure to the dashboard log pane. The payload is JSON:
|
|
138
|
+
* `{ label?: string, children?: object[], roots?: object[] }` where each node
|
|
139
|
+
* has `{ label, children? }`. CLI fallback: indented text lines.
|
|
140
|
+
*
|
|
141
|
+
* @param {object} _spec tree descriptor (single root or `{ roots: [...] }`)
|
|
142
|
+
*/
|
|
143
|
+
export function LogTree(_spec) {
|
|
144
|
+
let payload = _NormalizeTreeSpec(_spec);
|
|
145
|
+
if (_SendStructuredLog('tree', payload)) return;
|
|
146
|
+
for (let line of _AsciiTreeLines(payload)) console.log(line);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Sends an image gallery to the dashboard log pane. Each item needs a `src`
|
|
151
|
+
* (data URI or https URL) and a `label`; optional `caption` and per-item `title`.
|
|
152
|
+
* CLI fallback: lists labels and src lengths on stdout.
|
|
153
|
+
*
|
|
154
|
+
* @param {object} _spec `{ title?, columns?, items: { label, src, caption?, title? }[] }`
|
|
155
|
+
*/
|
|
156
|
+
export function LogGallery(_spec) {
|
|
157
|
+
let payload = _NormalizeGallerySpec(_spec);
|
|
158
|
+
if (_SendStructuredLog('gallery', payload)) return;
|
|
159
|
+
console.log(_AsciiGallery(payload));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Renders µCSS BuildSkin `report.debug.previews` as a thumbnail gallery.
|
|
164
|
+
* Items without `thumbDataUri` are omitted (paths-only assets stay in the text log).
|
|
165
|
+
*
|
|
166
|
+
* @param {object[]} _previews preview entries from `report.debug.previews`
|
|
167
|
+
* @param {object} [_options] `{ title?, columns? }`
|
|
168
|
+
*/
|
|
169
|
+
export function LogAssetPreviews(_previews, _options = {}) {
|
|
170
|
+
let items = (_previews ?? [])
|
|
171
|
+
.filter((_entry) => _entry?.thumbDataUri)
|
|
172
|
+
.map((_entry) => ({
|
|
173
|
+
label: String(_entry.relPath ?? _entry.path ?? ''),
|
|
174
|
+
src: _entry.thumbDataUri,
|
|
175
|
+
caption: [
|
|
176
|
+
_entry.step,
|
|
177
|
+
_entry.skipped ? 'cached' : null,
|
|
178
|
+
_entry.width && _entry.height ? `${_entry.width}\u00d7${_entry.height}` : null,
|
|
179
|
+
].filter(Boolean).join(' \u00b7 '),
|
|
180
|
+
}));
|
|
181
|
+
if (!items.length) return;
|
|
182
|
+
LogGallery({
|
|
183
|
+
title: _options.title,
|
|
184
|
+
columns: _options.columns ?? 4,
|
|
185
|
+
items,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Convenience wrapper for a full µCSS `BuildSkin` debug block: summary table
|
|
191
|
+
* plus thumbnail gallery when `report.debug` is present.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} _report BuildSkin return value
|
|
194
|
+
* @param {object} [_options] `{ title?, columns?, table?: boolean }`
|
|
195
|
+
*/
|
|
196
|
+
export function LogBuildDebugReport(_report, _options = {}) {
|
|
197
|
+
let debug = _report?.debug;
|
|
198
|
+
if (!debug) return;
|
|
199
|
+
if (_options.table !== false && debug.summary) {
|
|
200
|
+
LogTable({
|
|
201
|
+
columns: [{ id: 'metric', label: 'Metric' }, { id: 'value', label: 'Value' }],
|
|
202
|
+
rows: Object.entries(debug.summary).map(([metric, value]) => ({ metric, value: String(value) })),
|
|
203
|
+
hyphenate: false,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
LogAssetPreviews(debug.previews, {
|
|
207
|
+
title: _options.title ?? 'Generated assets',
|
|
208
|
+
columns: _options.columns,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Sends a highlighted callout box (info/success/warning/error) to the dashboard
|
|
214
|
+
* log pane. The payload is JSON: `{ variant, title?, message }`.
|
|
215
|
+
* CLI fallback: a bracketed line on stdout.
|
|
216
|
+
*
|
|
217
|
+
* @param {object} _spec `{ variant?: 'info'|'success'|'warning'|'error', title?, message }`
|
|
218
|
+
*/
|
|
219
|
+
export function LogCallout(_spec) {
|
|
220
|
+
let payload = _NormalizeCalloutSpec(_spec);
|
|
221
|
+
if (_SendStructuredLog('callout', payload)) return;
|
|
222
|
+
let head = payload.variant.toUpperCase() + (payload.title ? ': ' + payload.title : '');
|
|
223
|
+
console.log('[' + head + '] ' + payload.message);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Sends a key/value list (metrics, build summary, environment) to the dashboard
|
|
228
|
+
* log pane. The payload is JSON: `{ title?, items: {key, value}[] }`.
|
|
229
|
+
* CLI fallback: aligned `key: value` lines on stdout.
|
|
230
|
+
*
|
|
231
|
+
* @param {object} _spec `{ title?, items: Array<{key, value}> | Record<string, any> }`
|
|
232
|
+
*/
|
|
233
|
+
export function LogKeyValue(_spec) {
|
|
234
|
+
let payload = _NormalizeKeyValueSpec(_spec);
|
|
235
|
+
if (_SendStructuredLog('key-value', payload)) return;
|
|
236
|
+
if (payload.title) console.log(payload.title);
|
|
237
|
+
let width = payload.items.reduce((_max, _item) => Math.max(_max, _item.key.length), 0);
|
|
238
|
+
for (let item of payload.items) console.log(' ' + item.key.padEnd(width) + ' ' + item.value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Sends a row of status badges/chips to the dashboard log pane. The payload is
|
|
243
|
+
* JSON: `{ title?, items: {label, variant?}[] }`.
|
|
244
|
+
* CLI fallback: bracketed labels on one stdout line.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} _spec `{ title?, items: Array<string|{label, variant?}> }`
|
|
247
|
+
*/
|
|
248
|
+
export function LogBadges(_spec) {
|
|
249
|
+
let payload = _NormalizeBadgesSpec(_spec);
|
|
250
|
+
if (_SendStructuredLog('badges', payload)) return;
|
|
251
|
+
let chips = payload.items.map((_item) => '[' + _item.label + ']').join(' ');
|
|
252
|
+
console.log((payload.title ? payload.title + ' ' : '') + chips);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Sends a code block (monospace, preserved whitespace) to the dashboard log
|
|
257
|
+
* pane. The payload is JSON: `{ title?, language?, code }`.
|
|
258
|
+
* CLI fallback: the code printed verbatim on stdout.
|
|
259
|
+
*
|
|
260
|
+
* @param {object} _spec `{ title?, language?, code }`
|
|
261
|
+
*/
|
|
262
|
+
export function LogCode(_spec) {
|
|
263
|
+
let payload = _NormalizeCodeSpec(_spec);
|
|
264
|
+
if (_SendStructuredLog('code', payload)) return;
|
|
265
|
+
if (payload.title) console.log(payload.title + (payload.language ? ' (' + payload.language + ')' : ''));
|
|
266
|
+
console.log(payload.code);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Sends a bar chart to the dashboard log pane, rendered as inline SVG (no
|
|
271
|
+
* scripts — Content-Security-Policy safe). The payload is JSON:
|
|
272
|
+
* `{ title?, type?: 'bar', series: {label, value, color?}[], max?, unit? }`.
|
|
273
|
+
* CLI fallback: an ASCII bar chart on stdout.
|
|
274
|
+
*
|
|
275
|
+
* @param {object} _spec `{ title?, type?, series: Array<{label, value, color?}>, max?, unit? }`
|
|
276
|
+
*/
|
|
277
|
+
export function LogChart(_spec) {
|
|
278
|
+
let payload = _NormalizeChartSpec(_spec);
|
|
279
|
+
if (_SendStructuredLog('chart', payload)) return;
|
|
280
|
+
for (let line of _AsciiChart(payload)) console.log(line);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// -------------------------------------------------
|
|
284
|
+
// sound & speech
|
|
285
|
+
// -------------------------------------------------
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Plays an acoustic signal in the µGulp dashboard (WebAudio, respects the
|
|
289
|
+
* dashboard audio settings: mute/volume). CLI fallback: terminal bell for
|
|
290
|
+
* 'attention' and 'error' on a TTY, silent otherwise.
|
|
291
|
+
*
|
|
292
|
+
* @param {'success'|'error'|'attention'} [_signal]
|
|
293
|
+
*/
|
|
294
|
+
export function PlaySignal(_signal = 'attention') {
|
|
295
|
+
if (IsMicroGulp()) {
|
|
296
|
+
process.send({ type: 'sound', signal: _signal });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (process.stdout.isTTY && (_signal === 'attention' || _signal === 'error')) {
|
|
300
|
+
process.stdout.write('\u0007');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Plays a named sample from the dashboard sound atlas (µAU). Sample names match
|
|
306
|
+
* the files bundled with µGulp (`success`, `error`, `attention`, or custom
|
|
307
|
+
* atlas entries in the consumer skin). When `loop` is true the sample repeats
|
|
308
|
+
* until `StopSound(sample)` is called.
|
|
309
|
+
*
|
|
310
|
+
* CLI fallback: logs `[sound] <name>` on a TTY, silent otherwise.
|
|
311
|
+
*
|
|
312
|
+
* @param {string} _sample atlas sample name (e.g. `'success'`)
|
|
313
|
+
* @param {object} [_options]
|
|
314
|
+
* @param {boolean} [_options.loop] repeat until stopped (default `false`)
|
|
315
|
+
*/
|
|
316
|
+
export function PlaySound(_sample, _options = {}) {
|
|
317
|
+
let sample = String(_sample ?? '').trim();
|
|
318
|
+
if (!sample) return;
|
|
319
|
+
if (IsMicroGulp()) {
|
|
320
|
+
process.send({ type: 'sound', sample, loop: !!_options.loop });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (process.stdout.isTTY) {
|
|
324
|
+
console.log(`[sound] ${sample}${_options.loop ? ' (loop)' : ''}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Stops a looping sample started with `PlaySound(name, { loop: true })`.
|
|
330
|
+
* No-op for one-shot samples and outside µGulp (CLI logs `[sound-stop]` on TTY).
|
|
331
|
+
*
|
|
332
|
+
* @param {string} _sample atlas sample name passed to `PlaySound`
|
|
333
|
+
*/
|
|
334
|
+
export function StopSound(_sample) {
|
|
335
|
+
let sample = String(_sample ?? '').trim();
|
|
336
|
+
if (!sample) return;
|
|
337
|
+
if (IsMicroGulp()) {
|
|
338
|
+
process.send({ type: 'sound-stop', sample });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (process.stdout.isTTY) {
|
|
342
|
+
console.log(`[sound-stop] ${sample}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Speaks a text through the µGulp dashboard speech output (Web Speech API,
|
|
348
|
+
* respects the dashboard speech settings: enabled/volume/rate/pitch).
|
|
349
|
+
* CLI fallback: the text is printed as a log line.
|
|
350
|
+
*
|
|
351
|
+
* @param {string} _text
|
|
352
|
+
* @param {object} [_options] { rate?, pitch?, volume? } — per-call overrides (0..2 / 0..2 / 0..1)
|
|
353
|
+
*/
|
|
354
|
+
export function Speak(_text, _options) {
|
|
355
|
+
let text = String(_text ?? '').trim();
|
|
356
|
+
if (!text) return;
|
|
357
|
+
if (IsMicroGulp()) {
|
|
358
|
+
process.send({ type: 'speech', text, options: _options ?? null });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
console.log('[speech] ' + text);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// -------------------------------------------------
|
|
365
|
+
// interactive inputs
|
|
366
|
+
// -------------------------------------------------
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Requests a complete form from the µGulp dashboard.
|
|
370
|
+
*
|
|
371
|
+
* Field descriptor: { id, type, label, default?, options?, validate?,
|
|
372
|
+
* min?, max?, step?, rows?, placeholder? }
|
|
373
|
+
*
|
|
374
|
+
* Supported types: 'text' | 'password' | 'number' | 'textarea' | 'color' |
|
|
375
|
+
* 'font' | 'select' | 'radio' | 'checkbox' (multi-select, resolves to a
|
|
376
|
+
* string array) | 'range' (slider with synced number box) | 'date' |
|
|
377
|
+
* 'time' | 'datetime' | 'file'.
|
|
378
|
+
*
|
|
379
|
+
* Options (select/radio/checkbox) accept plain strings or objects
|
|
380
|
+
* { value, label?, checked?, disabled? }.
|
|
381
|
+
*
|
|
382
|
+
* Validation rules: { required?, minLength?, pattern?, minSelected? } —
|
|
383
|
+
* checks run asynchronously in the webview before submission.
|
|
384
|
+
*
|
|
385
|
+
* @param {object} _form { title, fields: [...] }
|
|
386
|
+
* @returns {Promise<object>} map fieldId -> value
|
|
387
|
+
*/
|
|
388
|
+
export async function RequestForm(_form) {
|
|
389
|
+
if (IsMicroGulp()) {
|
|
390
|
+
_EnsureIpcListener();
|
|
391
|
+
let requestId = 'api-req-' + process.pid + '-' + (++uiRequestCounter);
|
|
392
|
+
return new Promise((_resolve) => {
|
|
393
|
+
pendingUiRequests.set(requestId, _resolve);
|
|
394
|
+
process.send({ type: 'ui-request', requestId, form: _form });
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return _FallbackForm(_form);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Shows a blocking modal message inside the µGulp webview only (info / warning / error).
|
|
402
|
+
* Opt-in — use when a task needs explicit acknowledgment, not for every log line.
|
|
403
|
+
* CLI fallback: bracketed console output and `{ button: 'ok' }`.
|
|
404
|
+
*
|
|
405
|
+
* @param {object} _spec `{ variant?, title?, message?, buttons?, presentation? }`
|
|
406
|
+
* @param {'webview'|'ide'|'auto'} [_spec.presentation] `ide` = native host dialog when
|
|
407
|
+
* available (VS Code/Cursor); `webview` = in-dashboard glass modal; `auto` = follow host setting
|
|
408
|
+
* @returns {Promise<{ button: string }>}
|
|
409
|
+
*/
|
|
410
|
+
export async function ShowModalMessage(_spec) {
|
|
411
|
+
if (IsMicroGulp()) {
|
|
412
|
+
_EnsureIpcListener();
|
|
413
|
+
let requestId = 'api-modal-' + process.pid + '-' + (++uiRequestCounter);
|
|
414
|
+
return new Promise((_resolve) => {
|
|
415
|
+
pendingUiRequests.set(requestId, _resolve);
|
|
416
|
+
process.send({ type: 'ui-modal', requestId, modal: _NormalizeModalSpec(_spec) });
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return _FallbackModalMessage(_spec);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @param {object|undefined|null} _spec
|
|
424
|
+
* @returns {object}
|
|
425
|
+
*/
|
|
426
|
+
function _NormalizeModalSpec(_spec) {
|
|
427
|
+
let variant = ['info', 'warning', 'error'].includes(_spec?.variant) ? _spec.variant : 'info';
|
|
428
|
+
let presentation = ['webview', 'ide', 'auto'].includes(_spec?.presentation) ? _spec.presentation : undefined;
|
|
429
|
+
return {
|
|
430
|
+
variant,
|
|
431
|
+
title: _spec?.title != null ? String(_spec.title) : '',
|
|
432
|
+
message: _spec?.message != null ? String(_spec.message) : '',
|
|
433
|
+
buttons: Array.isArray(_spec?.buttons) ? _spec.buttons : undefined,
|
|
434
|
+
presentation,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* @param {object|undefined|null} _spec
|
|
440
|
+
* @returns {Promise<{ button: string }>}
|
|
441
|
+
*/
|
|
442
|
+
function _FallbackModalMessage(_spec) {
|
|
443
|
+
let normalized = _NormalizeModalSpec(_spec);
|
|
444
|
+
console.log(`[modal:${normalized.variant}] ${normalized.title}`.trim());
|
|
445
|
+
if (normalized.message) console.log(normalized.message);
|
|
446
|
+
return { button: 'ok' };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Confirmation dialog (Yes/No or OK/Cancel). Wrapper around {@link ShowModalMessage}.
|
|
451
|
+
*
|
|
452
|
+
* @param {object} _spec `{ title?, message?, variant?, style?, presentation? }`
|
|
453
|
+
* @param {'yes-no'|'ok-cancel'} [_spec.style] default `yes-no`
|
|
454
|
+
* @returns {Promise<{ button: string }>} `yes`, `no`, `ok`, or `cancel`
|
|
455
|
+
*/
|
|
456
|
+
export async function ShowConfirmMessage(_spec) {
|
|
457
|
+
let style = _spec?.style === 'ok-cancel' ? 'ok-cancel' : 'yes-no';
|
|
458
|
+
let buttons = style === 'ok-cancel'
|
|
459
|
+
? [{ id: 'cancel' }, { id: 'ok', primary: true }]
|
|
460
|
+
: [{ id: 'no' }, { id: 'yes', primary: true }];
|
|
461
|
+
return ShowModalMessage({
|
|
462
|
+
variant: _spec?.variant ?? 'warning',
|
|
463
|
+
title: _spec?.title,
|
|
464
|
+
message: _spec?.message,
|
|
465
|
+
presentation: _spec?.presentation,
|
|
466
|
+
buttons,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Requests a single validated text value.
|
|
472
|
+
* @param {object} _options { label, title?, default?, validate? }
|
|
473
|
+
* @returns {Promise<string|null>}
|
|
474
|
+
*/
|
|
475
|
+
export async function RequestTextInput(_options) {
|
|
476
|
+
return _SingleField({ ..._options, type: 'text' });
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Requests a color (native color picker in the dashboard).
|
|
481
|
+
* @param {object} _options { label, title?, default? } — default as #rrggbb
|
|
482
|
+
* @returns {Promise<string|null>}
|
|
483
|
+
*/
|
|
484
|
+
export async function RequestColorInput(_options) {
|
|
485
|
+
return _SingleField({ ..._options, type: 'color' });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Requests a font selection.
|
|
490
|
+
* @param {object} _options { label, title?, default?, options? } — options: font family names
|
|
491
|
+
* @returns {Promise<string|null>}
|
|
492
|
+
*/
|
|
493
|
+
export async function RequestFontInput(_options) {
|
|
494
|
+
return _SingleField({ ..._options, type: 'font' });
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Requests a selection from a fixed option list.
|
|
499
|
+
* @param {object} _options { label, title?, default?, options: string[] }
|
|
500
|
+
* @returns {Promise<string|null>}
|
|
501
|
+
*/
|
|
502
|
+
export async function RequestSelectInput(_options) {
|
|
503
|
+
return _SingleField({ ..._options, type: 'select' });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Requests a multi-selection (checkbox group in the dashboard).
|
|
508
|
+
* @param {object} _options { label, title?, options: Array<string|{value, label?, checked?, disabled?}>, validate? }
|
|
509
|
+
* @returns {Promise<string[]>} selected values (empty array when nothing was picked)
|
|
510
|
+
*/
|
|
511
|
+
export async function RequestMultiSelectInput(_options) {
|
|
512
|
+
let value = await _SingleField({ ..._options, type: 'checkbox' });
|
|
513
|
+
return Array.isArray(value) ? value : (value != null ? [value] : []);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// -------------------------------------------------
|
|
517
|
+
// internal
|
|
518
|
+
// -------------------------------------------------
|
|
519
|
+
|
|
520
|
+
function _EnsureIpcListener() {
|
|
521
|
+
if (ipcListenerAttached) return;
|
|
522
|
+
ipcListenerAttached = true;
|
|
523
|
+
process.on('message', (_message) => {
|
|
524
|
+
if (_message && _message.type === 'ui-response' && pendingUiRequests.has(_message.requestId)) {
|
|
525
|
+
let resolver = pendingUiRequests.get(_message.requestId);
|
|
526
|
+
pendingUiRequests.delete(_message.requestId);
|
|
527
|
+
resolver(_message.payload);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function _SingleField(_options) {
|
|
533
|
+
let values = await RequestForm({
|
|
534
|
+
title: _options.title ?? _options.label,
|
|
535
|
+
fields: [{
|
|
536
|
+
id: 'value',
|
|
537
|
+
type: _options.type,
|
|
538
|
+
label: _options.label,
|
|
539
|
+
default: _options.default,
|
|
540
|
+
options: _options.options,
|
|
541
|
+
validate: _options.validate,
|
|
542
|
+
}],
|
|
543
|
+
});
|
|
544
|
+
return values.value ?? null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function _FallbackForm(_form) {
|
|
548
|
+
let values = {};
|
|
549
|
+
for (let field of _form.fields ?? []) {
|
|
550
|
+
values[field.id] = await _FallbackField(field);
|
|
551
|
+
}
|
|
552
|
+
return values;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function _NormalizeOptions(_options) {
|
|
556
|
+
let result = [];
|
|
557
|
+
for (let option of _options ?? []) {
|
|
558
|
+
if (option == null) continue;
|
|
559
|
+
if (typeof option === 'object') {
|
|
560
|
+
if (option.separator) continue;
|
|
561
|
+
let value = option.value ?? option.label ?? option.name ?? '';
|
|
562
|
+
result.push({
|
|
563
|
+
value: String(value),
|
|
564
|
+
label: String(option.label ?? option.name ?? value),
|
|
565
|
+
checked: !!option.checked,
|
|
566
|
+
});
|
|
567
|
+
} else {
|
|
568
|
+
result.push({ value: String(option), label: String(option), checked: false });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function _FallbackDefault(_field) {
|
|
575
|
+
if (_field.type === 'checkbox') {
|
|
576
|
+
return _NormalizeOptions(_field.options).filter((_option) => _option.checked).map((_option) => _option.value);
|
|
577
|
+
}
|
|
578
|
+
return _field.default ?? null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function _FallbackField(_field) {
|
|
582
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
583
|
+
return _FallbackDefault(_field);
|
|
584
|
+
}
|
|
585
|
+
const readline = await import('node:readline/promises');
|
|
586
|
+
let prompt = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
587
|
+
try {
|
|
588
|
+
let label = _field.label ?? _field.id;
|
|
589
|
+
let options = _NormalizeOptions(_field.options);
|
|
590
|
+
let hasOptions = options.length > 0;
|
|
591
|
+
if (_field.type === 'checkbox' && hasOptions) {
|
|
592
|
+
options.forEach((_option, _index) => console.log(' [' + (_index + 1) + '] ' + _option.label + (_option.checked ? ' *' : '')));
|
|
593
|
+
let answer = await prompt.question(label + ' (comma-separated numbers, Enter = defaults marked *): ');
|
|
594
|
+
let picked = answer.split(',')
|
|
595
|
+
.map((_part) => parseInt(_part.trim(), 10))
|
|
596
|
+
.filter((_index) => _index >= 1 && _index <= options.length)
|
|
597
|
+
.map((_index) => options[_index - 1].value);
|
|
598
|
+
return picked.length > 0 ? picked : _FallbackDefault(_field);
|
|
599
|
+
}
|
|
600
|
+
if ((_field.type === 'select' || _field.type === 'font' || _field.type === 'radio') && hasOptions) {
|
|
601
|
+
options.forEach((_option, _index) => console.log(' [' + (_index + 1) + '] ' + _option.label));
|
|
602
|
+
let answer = await prompt.question(label + ' (1-' + options.length + (_field.default ? ', default "' + _field.default + '"' : '') + '): ');
|
|
603
|
+
let index = parseInt(answer, 10);
|
|
604
|
+
if (index >= 1 && index <= options.length) return options[index - 1].value;
|
|
605
|
+
return _field.default ?? null;
|
|
606
|
+
}
|
|
607
|
+
let hint = _field.type === 'range' || _field.type === 'number'
|
|
608
|
+
? ' (' + (_field.min ?? 0) + '-' + (_field.max ?? 100) + (_field.default != null ? ', default ' + _field.default : '') + ')'
|
|
609
|
+
: (_field.default ? ' [' + _field.default + ']' : '');
|
|
610
|
+
let answer = await prompt.question(label + hint + ': ');
|
|
611
|
+
return answer || (_field.default ?? null);
|
|
612
|
+
} finally {
|
|
613
|
+
prompt.close();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function _NormalizeColumn(_column, _index) {
|
|
618
|
+
if (typeof _column === 'string') return { id: 'c' + _index, label: _column };
|
|
619
|
+
return { id: String(_column.id ?? 'c' + _index), label: String(_column.label ?? _column.id ?? 'c' + _index) };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function _NormalizeTableSpec(_spec) {
|
|
623
|
+
let columns = (_spec?.columns ?? []).map(_NormalizeColumn);
|
|
624
|
+
let rows = _spec?.rows ?? [];
|
|
625
|
+
let normalizedRows = rows.map((_row) => {
|
|
626
|
+
if (Array.isArray(_row)) {
|
|
627
|
+
let obj = {};
|
|
628
|
+
for (let c = 0; c < columns.length; c++) obj[columns[c].id] = _row[c] ?? '';
|
|
629
|
+
return obj;
|
|
630
|
+
}
|
|
631
|
+
return _row;
|
|
632
|
+
});
|
|
633
|
+
return { columns, rows: normalizedRows, hyphenate: _spec?.hyphenate !== false };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function _NormalizeTreeSpec(_spec) {
|
|
637
|
+
if (_spec?.roots) return { roots: _spec.roots };
|
|
638
|
+
if (_spec?.label != null || _spec?.children) return { roots: [_spec] };
|
|
639
|
+
return { roots: [] };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function _AsciiTable(_payload) {
|
|
643
|
+
let headers = _payload.columns.map((_c) => _c.label);
|
|
644
|
+
let widths = headers.map((_h) => _h.length);
|
|
645
|
+
for (let row of _payload.rows) {
|
|
646
|
+
for (let c = 0; c < _payload.columns.length; c++) {
|
|
647
|
+
let cell = String(row[_payload.columns[c].id] ?? '');
|
|
648
|
+
widths[c] = Math.max(widths[c], cell.length);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
let sep = widths.map((_w) => '-'.repeat(_w)).join('-+-');
|
|
652
|
+
let lines = [headers.map((_h, _i) => _h.padEnd(widths[_i])).join(' | ')];
|
|
653
|
+
lines.push(sep);
|
|
654
|
+
for (let row of _payload.rows) {
|
|
655
|
+
lines.push(_payload.columns.map((_col, _i) => String(row[_col.id] ?? '').padEnd(widths[_i])).join(' | '));
|
|
656
|
+
}
|
|
657
|
+
return lines.join('\n');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function _AsciiTreeLines(_payload, _indent = '', _roots = null) {
|
|
661
|
+
let roots = _roots ?? _payload.roots ?? [];
|
|
662
|
+
let lines = [];
|
|
663
|
+
for (let i = 0; i < roots.length; i++) {
|
|
664
|
+
let node = roots[i];
|
|
665
|
+
let branch = i < roots.length - 1 ? '├─ ' : '└─ ';
|
|
666
|
+
lines.push(_indent + branch + String(node.label ?? ''));
|
|
667
|
+
let childIndent = _indent + (i < roots.length - 1 ? '│ ' : ' ');
|
|
668
|
+
if (node.children?.length) lines.push(..._AsciiTreeLines(_payload, childIndent, node.children));
|
|
669
|
+
}
|
|
670
|
+
return lines;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function _NormalizeGallerySpec(_spec) {
|
|
674
|
+
let columns = Math.max(1, Math.min(8, Number(_spec?.columns) || 4));
|
|
675
|
+
let items = (_spec?.items ?? []).map((_item) => ({
|
|
676
|
+
label: String(_item?.label ?? ''),
|
|
677
|
+
src: String(_item?.src ?? ''),
|
|
678
|
+
caption: _item?.caption != null ? String(_item.caption) : '',
|
|
679
|
+
title: _item?.title != null ? String(_item.title) : '',
|
|
680
|
+
})).filter((_item) => _item.src);
|
|
681
|
+
return {
|
|
682
|
+
title: _spec?.title != null ? String(_spec.title) : '',
|
|
683
|
+
columns,
|
|
684
|
+
items,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function _AsciiGallery(_payload) {
|
|
689
|
+
let lines = [];
|
|
690
|
+
if (_payload.title) lines.push(_payload.title);
|
|
691
|
+
for (let item of _payload.items) {
|
|
692
|
+
let meta = item.caption ? ` (${item.caption})` : '';
|
|
693
|
+
let srcHint = item.src.startsWith('data:') ? `[data URI, ${item.src.length} chars]` : item.src;
|
|
694
|
+
lines.push(` ${item.label}${meta}: ${srcHint}`);
|
|
695
|
+
}
|
|
696
|
+
return lines.join('\n');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const CALLOUT_VARIANTS = new Set(['info', 'success', 'warning', 'error']);
|
|
700
|
+
|
|
701
|
+
function _NormalizeCalloutSpec(_spec) {
|
|
702
|
+
let variant = String(_spec?.variant ?? 'info').toLowerCase();
|
|
703
|
+
if (!CALLOUT_VARIANTS.has(variant)) variant = 'info';
|
|
704
|
+
return {
|
|
705
|
+
variant,
|
|
706
|
+
title: _spec?.title != null ? String(_spec.title) : '',
|
|
707
|
+
message: String(_spec?.message ?? _spec?.text ?? ''),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function _NormalizeKeyValueSpec(_spec) {
|
|
712
|
+
let rawItems = _spec?.items ?? _spec ?? [];
|
|
713
|
+
let items = [];
|
|
714
|
+
if (Array.isArray(rawItems)) {
|
|
715
|
+
for (let item of rawItems) {
|
|
716
|
+
if (item == null) continue;
|
|
717
|
+
if (typeof item === 'object' && !Array.isArray(item)) {
|
|
718
|
+
items.push({ key: String(item.key ?? ''), value: String(item.value ?? '') });
|
|
719
|
+
} else if (Array.isArray(item)) {
|
|
720
|
+
items.push({ key: String(item[0] ?? ''), value: String(item[1] ?? '') });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} else if (rawItems && typeof rawItems === 'object') {
|
|
724
|
+
for (let [key, value] of Object.entries(rawItems)) {
|
|
725
|
+
if (key === 'title') continue;
|
|
726
|
+
items.push({ key: String(key), value: String(value) });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return { title: _spec?.title != null ? String(_spec.title) : '', items };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const BADGE_VARIANTS = new Set(['neutral', 'info', 'success', 'warning', 'error']);
|
|
733
|
+
|
|
734
|
+
function _NormalizeBadgesSpec(_spec) {
|
|
735
|
+
let items = (_spec?.items ?? []).map((_item) => {
|
|
736
|
+
if (_item == null) return null;
|
|
737
|
+
if (typeof _item === 'object') {
|
|
738
|
+
let variant = String(_item.variant ?? 'neutral').toLowerCase();
|
|
739
|
+
return { label: String(_item.label ?? _item.value ?? ''), variant: BADGE_VARIANTS.has(variant) ? variant : 'neutral' };
|
|
740
|
+
}
|
|
741
|
+
return { label: String(_item), variant: 'neutral' };
|
|
742
|
+
}).filter((_item) => _item && _item.label);
|
|
743
|
+
return { title: _spec?.title != null ? String(_spec.title) : '', items };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function _NormalizeCodeSpec(_spec) {
|
|
747
|
+
return {
|
|
748
|
+
title: _spec?.title != null ? String(_spec.title) : '',
|
|
749
|
+
language: _spec?.language != null ? String(_spec.language) : '',
|
|
750
|
+
code: String(_spec?.code ?? _spec?.text ?? ''),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function _NormalizeChartSpec(_spec) {
|
|
755
|
+
let series = (_spec?.series ?? _spec?.data ?? []).map((_entry) => {
|
|
756
|
+
if (_entry == null) return null;
|
|
757
|
+
if (typeof _entry === 'object' && !Array.isArray(_entry)) {
|
|
758
|
+
return {
|
|
759
|
+
label: String(_entry.label ?? ''),
|
|
760
|
+
value: Number(_entry.value) || 0,
|
|
761
|
+
color: _entry.color != null ? String(_entry.color) : '',
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
if (Array.isArray(_entry)) return { label: String(_entry[0] ?? ''), value: Number(_entry[1]) || 0, color: '' };
|
|
765
|
+
return { label: '', value: Number(_entry) || 0, color: '' };
|
|
766
|
+
}).filter(Boolean);
|
|
767
|
+
let explicitMax = Number(_spec?.max);
|
|
768
|
+
let dataMax = series.reduce((_max, _entry) => Math.max(_max, _entry.value), 0);
|
|
769
|
+
return {
|
|
770
|
+
title: _spec?.title != null ? String(_spec.title) : '',
|
|
771
|
+
type: 'bar',
|
|
772
|
+
unit: _spec?.unit != null ? String(_spec.unit) : '',
|
|
773
|
+
max: Number.isFinite(explicitMax) && explicitMax > 0 ? explicitMax : (dataMax || 1),
|
|
774
|
+
series,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function _AsciiChart(_payload) {
|
|
779
|
+
let lines = [];
|
|
780
|
+
if (_payload.title) lines.push(_payload.title);
|
|
781
|
+
let labelWidth = _payload.series.reduce((_max, _entry) => Math.max(_max, _entry.label.length), 0);
|
|
782
|
+
const BAR_WIDTH = 24;
|
|
783
|
+
for (let entry of _payload.series) {
|
|
784
|
+
let filled = _payload.max > 0 ? Math.round((entry.value / _payload.max) * BAR_WIDTH) : 0;
|
|
785
|
+
let bar = '\u2588'.repeat(filled) + '\u00b7'.repeat(Math.max(0, BAR_WIDTH - filled));
|
|
786
|
+
lines.push(' ' + entry.label.padEnd(labelWidth) + ' ' + bar + ' ' + entry.value + (_payload.unit ? ' ' + _payload.unit : ''));
|
|
787
|
+
}
|
|
788
|
+
return lines;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Asks µGulp to re-evaluate dynamic task metadata (µEnabled predicates,
|
|
793
|
+
* disabled tooltips) and refresh the dashboard task list. Call after
|
|
794
|
+
* changing module-level state that drives enable/disable rules.
|
|
795
|
+
* No-op outside a µGulp worker.
|
|
796
|
+
*/
|
|
797
|
+
export function NotifyTasksChanged() {
|
|
798
|
+
if (typeof process.send === 'function') {
|
|
799
|
+
process.send({ type: 'refresh-tasks' });
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export default {
|
|
804
|
+
IsMicroGulp,
|
|
805
|
+
IsµGulp,
|
|
806
|
+
NotifyTasksChanged,
|
|
807
|
+
ReportProgress,
|
|
808
|
+
CreateProgress,
|
|
809
|
+
PlaySignal,
|
|
810
|
+
PlaySound,
|
|
811
|
+
StopSound,
|
|
812
|
+
Speak,
|
|
813
|
+
RequestForm,
|
|
814
|
+
RequestTextInput,
|
|
815
|
+
RequestColorInput,
|
|
816
|
+
RequestFontInput,
|
|
817
|
+
RequestSelectInput,
|
|
818
|
+
RequestMultiSelectInput,
|
|
819
|
+
Log: I18x.Log,
|
|
820
|
+
Warn: I18x.Warn,
|
|
821
|
+
LogError: I18x.LogError,
|
|
822
|
+
Translate: I18x.Translate,
|
|
823
|
+
GetLid: I18x.GetLid,
|
|
824
|
+
SetLid: I18x.SetLid,
|
|
825
|
+
GetTimeZone: I18x.GetTimeZone,
|
|
826
|
+
DateInTimeZone: I18x.DateInTimeZone,
|
|
827
|
+
TimeZoneOffset: I18x.TimeZoneOffset,
|
|
828
|
+
InstallStringExtensions: I18x.InstallStringExtensions,
|
|
829
|
+
InstallFormatPrototypes: I18x.InstallFormatPrototypes,
|
|
830
|
+
InstallHyphenPrototypes: I18x.InstallHyphenPrototypes,
|
|
831
|
+
LogTable,
|
|
832
|
+
LogTree,
|
|
833
|
+
LogGallery,
|
|
834
|
+
LogAssetPreviews,
|
|
835
|
+
LogBuildDebugReport,
|
|
836
|
+
LogCallout,
|
|
837
|
+
ShowModalMessage,
|
|
838
|
+
ShowConfirmMessage,
|
|
839
|
+
LogKeyValue,
|
|
840
|
+
LogBadges,
|
|
841
|
+
LogCode,
|
|
842
|
+
LogChart,
|
|
843
|
+
};
|