@crustjs/crust-linux-x64-musl 0.5.6 → 0.6.1

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/bin/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
- import { accessSync, constants, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
2
- import { chmod, copyFile, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
1
+ import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { chmod, copyFile, mkdtemp, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
- import { delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
6
5
  import { randomBytes } from "node:crypto";
7
- import { spawn, spawnSync } from "node:child_process";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { spawn } from "node:child_process";
8
8
  import { once } from "node:events";
9
9
  import { text } from "node:stream/consumers";
10
- //#region \0rolldown/runtime.js
10
+ // \0rolldown/runtime.js
11
11
  var __create = Object.create;
12
12
  var __defProp = Object.defineProperty;
13
13
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
14
14
  var __getOwnPropNames = Object.getOwnPropertyNames;
15
15
  var __getProtoOf = Object.getPrototypeOf;
16
16
  var __hasOwnProp = Object.prototype.hasOwnProperty;
17
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
17
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
18
18
  var __copyProps = (to, from, except, desc) => {
19
19
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
20
  key = keys[i];
@@ -29,26 +29,40 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  value: mod,
30
30
  enumerable: true
31
31
  }) : target, mod));
32
- //#endregion
33
- //#region ../utils/dist/artifacts.js
34
- /**
35
- * Set by `crust build` while it prepares the Command Snapshot: the absolute
36
- * entry-isolated directory Extension build hooks write into. Core reads it
37
- * to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
38
- * that run see the artifacts being built instead of the wiped `.crust/root`.
39
- */
40
- const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
32
+ // ../utils/dist/signals.js
33
+ /** SigIgn bits from the first read, taken before Crust installs any listener. */
34
+ let ignoredMask;
41
35
  /**
42
- * `Symbol.for` registry key of the packaged-build marker, for bundlers without a
43
- * `define` option (native `deno bundle`). `crust build` prepends
44
- * `globalThis[Symbol.for(PACKAGED_BUILD_KEY)] = true` to the command bundle,
45
- * before the inlined application graph, so every bundled copy sees it.
46
- * The registry key is shared across module copies; the value is never read from
47
- * the environment, so detection needs no Deno env permission.
36
+ * Whether `signal` is inherited as ignored, such as SIGHUP under `nohup` or SIGINT for a
37
+ * background job in a script. Adding a listener replaces that disposition and removing the
38
+ * last one restores the default action, so code that listens temporarily skips ignored
39
+ * signals to keep the process immune to them. Only Bun keeps an inherited ignore; Node
40
+ * resets it at startup, so this is always false there and skips the read.
41
+ *
42
+ * shortcut: Linux-only (`/proc/self/status`); macOS Bun still loses the ignore. Remove once
43
+ * Bun restores the previous disposition on listener removal (oven-sh/bun#47574).
48
44
  */
49
- const PACKAGED_BUILD_KEY = "crustjs.packagedBuild";
50
- //#endregion
51
- //#region ../core/dist/invocation-BstcKEMM.js
45
+ function isSignalIgnored(signal) {
46
+ if (process.platform !== "linux" || process.versions.bun === void 0) return false;
47
+ ignoredMask ??= readIgnoredMask();
48
+ const number = process.getBuiltinModule("node:os").constants.signals[signal];
49
+ return number !== void 0 && number <= 32 && (ignoredMask >>> number - 1 & 1) === 1;
50
+ }
51
+ function readIgnoredMask() {
52
+ try {
53
+ const status = process.getBuiltinModule("node:fs").readFileSync("/proc/self/status", "utf8");
54
+ const hex = /^SigIgn:\s*([0-9a-f]+)$/m.exec(status)?.[1];
55
+ return hex ? Number.parseInt(hex.slice(-8), 16) : 0;
56
+ } catch {
57
+ return 0;
58
+ }
59
+ }
60
+ // ../utils/dist/error.js
61
+ /** Narrows a caught Node.js system error to its stable errno contract. */
62
+ function isErrnoException(error) {
63
+ return error instanceof Error && "code" in error && typeof error.code === "string";
64
+ }
65
+ // ../core/dist/invocation-DKSTuFCO.js
52
66
  /**
53
67
  * A typed error for runtime recipe, Extension, Context, documentation, argv, and validation failures.
54
68
  *
@@ -81,6 +95,9 @@ var CrustError = class extends Error {
81
95
  this.code = code;
82
96
  this.details = details[0];
83
97
  }
98
+ static [Symbol.hasInstance](value) {
99
+ return Function.prototype[Symbol.hasInstance].call(this, value);
100
+ }
84
101
  is(code) {
85
102
  return Object.is(this.code, code);
86
103
  }
@@ -96,7 +113,6 @@ var CrustError = class extends Error {
96
113
  };
97
114
  }
98
115
  };
99
- Object.freeze({ [Symbol("crust.handled")]: true });
100
116
  /**
101
117
  * Snapshot subprocess protocol used by first-party build tooling.
102
118
  *
@@ -106,13 +122,23 @@ Object.freeze({ [Symbol("crust.handled")]: true });
106
122
  * without dispatching a Command Action. In-process callers use `Crust.snapshot()`.
107
123
  *
108
124
  * Only source entries run by `crust build` honor it: finished Bun/Node bundles carry
109
- * `process.env.CRUST_INTERNAL_BUILD === "1"` as a literal and compile the protocol out;
110
- * packaged Deno bundles set the `isPackagedBuild()` marker and skip it without
111
- * reading the environment.
125
+ * `process.env.CRUST_INTERNAL_BUILD === "1"` as a literal and compile the protocol out.
112
126
  */
113
127
  const SNAPSHOT_PATH_ENV = "CRUST_INTERNAL_SNAPSHOT_PATH";
114
- //#endregion
115
- //#region ../core/dist/tooling.js
128
+ // ../utils/dist/artifacts.js
129
+ /**
130
+ * Set by `crust build` while it prepares the Command Snapshot: the absolute
131
+ * entry-isolated directory Extension build hooks write into. Core reads it
132
+ * to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
133
+ * that run see the artifacts being built instead of the wiped `.crust/root`.
134
+ */
135
+ const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
136
+ // ../utils/dist/json.js
137
+ /** Narrow a JSON document root to an object (not array/scalar/null). */
138
+ function isJsonObject(value) {
139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
140
+ }
141
+ // ../core/dist/tooling.js
116
142
  const INSTALLED_COMMAND_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
117
143
  /** {@link isInstalledCommandName}'s rule as prose, for error messages: "Use <rule>." */
118
144
  const INSTALLED_COMMAND_NAME_RULE = "letters, digits, \".\", \"_\", and \"-\", starting with a letter or digit";
@@ -124,83 +150,18 @@ const INSTALLED_COMMAND_NAME_RULE = "letters, digits, \".\", \"_\", and \"-\", s
124
150
  function isInstalledCommandName(name) {
125
151
  return INSTALLED_COMMAND_NAME_PATTERN.test(name);
126
152
  }
127
- new Intl.Segmenter(void 0, { granularity: "grapheme" });
128
- new RegExp([
153
+ [
129
154
  String.raw`(?:\x1b\[|\x9b)[^\x1b\x18\x1a\x9c\x40-\x7e]*[\x40-\x7e\x18\x1a\x9c]?`,
130
155
  String.raw`(?:\x1b\]|\x9d)[^\x07\x1b\x18\x1a\x9c]*(?:\x07|\x1b\\|[\x18\x1a\x9c])?`,
131
156
  String.raw`(?:\x1b[PX^_]|[\x90\x98\x9e\x9f])[^\x1b\x18\x1a\x9c]*(?:\x1b\\|[\x18\x1a\x9c])?`,
132
157
  String.raw`\x1b[\x20-\x2f][^\x1b]?`,
133
158
  String.raw`\x1b[\x18\x1a\x9c\x30-\x7e]?`
134
- ].join("|"), "g");
135
- const modifierCodes = {
136
- /** Bold / increased intensity. */
137
- bold: [1, 22],
138
- /** Dim / decreased intensity. */
139
- dim: [2, 22],
140
- italic: [3, 23],
141
- underline: [4, 24],
142
- /** Inverse / reverse video. */
143
- inverse: [7, 27],
144
- /** Hidden / conceal. */
145
- hidden: [8, 28],
146
- /** Strikethrough / crossed out. */
147
- strikethrough: [9, 29]
148
- };
149
- const colorCodes = {
150
- black: [30, 39],
151
- red: [31, 39],
152
- green: [32, 39],
153
- yellow: [33, 39],
154
- blue: [34, 39],
155
- magenta: [35, 39],
156
- cyan: [36, 39],
157
- white: [37, 39],
158
- /** Bright black (gray). */
159
- gray: [90, 39],
160
- brightRed: [91, 39],
161
- brightGreen: [92, 39],
162
- brightYellow: [93, 39],
163
- brightBlue: [94, 39],
164
- brightMagenta: [95, 39],
165
- brightCyan: [96, 39],
166
- brightWhite: [97, 39],
167
- bgBlack: [40, 49],
168
- bgRed: [41, 49],
169
- bgGreen: [42, 49],
170
- bgYellow: [43, 49],
171
- bgBlue: [44, 49],
172
- bgMagenta: [45, 49],
173
- bgCyan: [46, 49],
174
- bgWhite: [47, 49],
175
- bgBrightBlack: [100, 49],
176
- bgBrightRed: [101, 49],
177
- bgBrightGreen: [102, 49],
178
- bgBrightYellow: [103, 49],
179
- bgBrightBlue: [104, 49],
180
- bgBrightMagenta: [105, 49],
181
- bgBrightCyan: [106, 49],
182
- bgBrightWhite: [107, 49]
183
- };
184
- const styleMethodCodes = {
185
- ...modifierCodes,
186
- ...colorCodes
187
- };
188
- const styleMethodNames = Object.freeze(Object.keys(styleMethodCodes));
189
- const styleMethodPairs = Object.freeze(Object.fromEntries(styleMethodNames.map((name) => {
190
- const [open, close] = styleMethodCodes[name];
191
- return [name, {
192
- open: `\x1b[${open}m`,
193
- close: `\x1b[${close}m`
194
- }];
195
- })));
196
- function isModifierName(name) {
197
- return Object.hasOwn(modifierCodes, name);
198
- }
159
+ ].join("|");
199
160
  function read(overrides, key, environment) {
200
161
  return overrides !== void 0 && key in overrides ? overrides[key] : environment;
201
162
  }
202
163
  function readTTY(overrides) {
203
- return read(overrides, "isTTY", process.stdout?.isTTY) ?? false;
164
+ return (overrides !== void 0 && "isTTY" in overrides ? overrides.isTTY : process.stdout?.isTTY) ?? false;
204
165
  }
205
166
  function readForceColor(overrides) {
206
167
  return read(overrides, "forceColor", process.env.FORCE_COLOR);
@@ -209,6 +170,9 @@ function readForceColor(overrides) {
209
170
  function forceColorDisables(forceColor) {
210
171
  return forceColor === "0" || forceColor === "false";
211
172
  }
173
+ function isDumbTerm(term) {
174
+ return term?.toLowerCase() === "dumb";
175
+ }
212
176
  function isTrueColorTerm(term) {
213
177
  const lower = term.toLowerCase();
214
178
  return lower.includes("24bit") || lower.includes("truecolor") || lower.endsWith("-direct");
@@ -232,25 +196,26 @@ function detectDepth(colorTerm, term) {
232
196
  * - `"never"` → `"none"`.
233
197
  * - `"always"` → `"truecolor"`.
234
198
  * - `"auto"`:
235
- * 1. `FORCE_COLOR` set → decides unconditionally (overrides TTY and
236
- * `NO_COLOR`, chalk convention): `0` / `false` → `"none"`; `1` →
199
+ * 1. `FORCE_COLOR` set → decides unconditionally (overrides TTY,
200
+ * `NO_COLOR`, and `TERM=dumb`, as in Node's `tty` color detection):
201
+ * `0` / `false` → `"none"`; `1` →
237
202
  * `"16"`; `2` → `"256"`; `3` → `"truecolor"`; any other value
238
203
  * (including empty) → forced on at the `COLORTERM` / `TERM`
239
204
  * detected depth.
240
- * 2. Not a TTY OR `NO_COLOR` set non-empty → `"none"`.
205
+ * 2. Not a TTY OR `NO_COLOR` set non-empty OR `TERM=dumb`
206
+ * (case-insensitive) → `"none"`.
241
207
  * 3. `COLORTERM` is `"truecolor"` or `"24bit"` (case-insensitive) →
242
208
  * `"truecolor"`.
243
209
  * 4. `TERM` ends with `-direct` OR contains `truecolor` / `24bit` →
244
210
  * `"truecolor"`.
245
- * 5. `TERM === "dumb"` → `"none"`.
246
- * 6. `TERM` contains `256color` → `"256"`.
247
- * 7. Any other TTY value → `"16"`.
211
+ * 5. `TERM` contains `256color` → `"256"`.
212
+ * 6. Any other TTY value → `"16"`.
248
213
  *
249
214
  * Detection follows the ecosystem `FORCE_COLOR` / `NO_COLOR` / `COLORTERM`
250
215
  * / `TERM` conventions; no bespoke environment variables are introduced.
251
216
  *
252
217
  * @param mode - The color emission mode.
253
- * @param overrides - Optional overrides for deterministic testing.
218
+ * @param overrides - Optional capability inputs, such as another stream's TTY state or fixed test values.
254
219
  * @returns The resolved {@link ColorDepth} tier.
255
220
  *
256
221
  * @example
@@ -286,14 +251,15 @@ function resolveColorDepth(mode, overrides) {
286
251
  if (!readTTY(overrides)) return "none";
287
252
  const noColor = read(overrides, "noColor", process.env.NO_COLOR);
288
253
  if (noColor !== void 0 && noColor !== "") return "none";
289
- if (term !== void 0 && term.toLowerCase() === "dumb" && !detectsTruecolor(colorTerm, term)) return "none";
254
+ if (isDumbTerm(term)) return "none";
290
255
  return detectDepth(colorTerm, term);
291
256
  }
292
257
  /**
293
258
  * Resolve whether non-color ANSI modifiers should be emitted.
294
259
  *
295
- * In `"auto"` mode, modifiers are enabled when stdout is a TTY, but are
296
- * **not** affected by `NO_COLOR` (which only controls color output).
260
+ * In `"auto"` mode, modifiers are enabled when stdout is a TTY and `TERM`
261
+ * is not `dumb`, but are **not** affected by `NO_COLOR` (which only
262
+ * controls color output).
297
263
  * `FORCE_COLOR`, when set, decides unconditionally — it is the all-ANSI
298
264
  * switch, while `NO_COLOR` is the colors-only switch.
299
265
  *
@@ -305,750 +271,8 @@ function resolveModifierCapability(mode, overrides) {
305
271
  if (mode === "never") return false;
306
272
  const forceColor = readForceColor(overrides);
307
273
  if (forceColor !== void 0) return !forceColorDisables(forceColor);
308
- return readTTY(overrides);
274
+ return readTTY(overrides) && !isDumbTerm(read(overrides, "term", process.env.TERM));
309
275
  }
310
- const namedColorValues = {
311
- aliceblue: [
312
- 240,
313
- 248,
314
- 255
315
- ],
316
- antiquewhite: [
317
- 250,
318
- 235,
319
- 215
320
- ],
321
- aqua: [
322
- 0,
323
- 255,
324
- 255
325
- ],
326
- aquamarine: [
327
- 127,
328
- 255,
329
- 212
330
- ],
331
- azure: [
332
- 240,
333
- 255,
334
- 255
335
- ],
336
- beige: [
337
- 245,
338
- 245,
339
- 220
340
- ],
341
- bisque: [
342
- 255,
343
- 228,
344
- 196
345
- ],
346
- black: [
347
- 0,
348
- 0,
349
- 0
350
- ],
351
- blanchedalmond: [
352
- 255,
353
- 235,
354
- 205
355
- ],
356
- blue: [
357
- 0,
358
- 0,
359
- 255
360
- ],
361
- blueviolet: [
362
- 138,
363
- 43,
364
- 226
365
- ],
366
- brown: [
367
- 165,
368
- 42,
369
- 42
370
- ],
371
- burlywood: [
372
- 222,
373
- 184,
374
- 135
375
- ],
376
- cadetblue: [
377
- 95,
378
- 158,
379
- 160
380
- ],
381
- chartreuse: [
382
- 127,
383
- 255,
384
- 0
385
- ],
386
- chocolate: [
387
- 210,
388
- 105,
389
- 30
390
- ],
391
- coral: [
392
- 255,
393
- 127,
394
- 80
395
- ],
396
- cornflowerblue: [
397
- 100,
398
- 149,
399
- 237
400
- ],
401
- cornsilk: [
402
- 255,
403
- 248,
404
- 220
405
- ],
406
- crimson: [
407
- 220,
408
- 20,
409
- 60
410
- ],
411
- cyan: [
412
- 0,
413
- 255,
414
- 255
415
- ],
416
- darkblue: [
417
- 0,
418
- 0,
419
- 139
420
- ],
421
- darkcyan: [
422
- 0,
423
- 139,
424
- 139
425
- ],
426
- darkgoldenrod: [
427
- 184,
428
- 134,
429
- 11
430
- ],
431
- darkgray: [
432
- 169,
433
- 169,
434
- 169
435
- ],
436
- darkgreen: [
437
- 0,
438
- 100,
439
- 0
440
- ],
441
- darkgrey: [
442
- 169,
443
- 169,
444
- 169
445
- ],
446
- darkkhaki: [
447
- 189,
448
- 183,
449
- 107
450
- ],
451
- darkmagenta: [
452
- 139,
453
- 0,
454
- 139
455
- ],
456
- darkolivegreen: [
457
- 85,
458
- 107,
459
- 47
460
- ],
461
- darkorange: [
462
- 255,
463
- 140,
464
- 0
465
- ],
466
- darkorchid: [
467
- 153,
468
- 50,
469
- 204
470
- ],
471
- darkred: [
472
- 139,
473
- 0,
474
- 0
475
- ],
476
- darksalmon: [
477
- 233,
478
- 150,
479
- 122
480
- ],
481
- darkseagreen: [
482
- 143,
483
- 188,
484
- 143
485
- ],
486
- darkslateblue: [
487
- 72,
488
- 61,
489
- 139
490
- ],
491
- darkslategray: [
492
- 47,
493
- 79,
494
- 79
495
- ],
496
- darkslategrey: [
497
- 47,
498
- 79,
499
- 79
500
- ],
501
- darkturquoise: [
502
- 0,
503
- 206,
504
- 209
505
- ],
506
- darkviolet: [
507
- 148,
508
- 0,
509
- 211
510
- ],
511
- deeppink: [
512
- 255,
513
- 20,
514
- 147
515
- ],
516
- deepskyblue: [
517
- 0,
518
- 191,
519
- 255
520
- ],
521
- dimgray: [
522
- 105,
523
- 105,
524
- 105
525
- ],
526
- dimgrey: [
527
- 105,
528
- 105,
529
- 105
530
- ],
531
- dodgerblue: [
532
- 30,
533
- 144,
534
- 255
535
- ],
536
- firebrick: [
537
- 178,
538
- 34,
539
- 34
540
- ],
541
- floralwhite: [
542
- 255,
543
- 250,
544
- 240
545
- ],
546
- forestgreen: [
547
- 34,
548
- 139,
549
- 34
550
- ],
551
- fuchsia: [
552
- 255,
553
- 0,
554
- 255
555
- ],
556
- gainsboro: [
557
- 220,
558
- 220,
559
- 220
560
- ],
561
- ghostwhite: [
562
- 248,
563
- 248,
564
- 255
565
- ],
566
- gold: [
567
- 255,
568
- 215,
569
- 0
570
- ],
571
- goldenrod: [
572
- 218,
573
- 165,
574
- 32
575
- ],
576
- gray: [
577
- 128,
578
- 128,
579
- 128
580
- ],
581
- green: [
582
- 0,
583
- 128,
584
- 0
585
- ],
586
- greenyellow: [
587
- 173,
588
- 255,
589
- 47
590
- ],
591
- grey: [
592
- 128,
593
- 128,
594
- 128
595
- ],
596
- honeydew: [
597
- 240,
598
- 255,
599
- 240
600
- ],
601
- hotpink: [
602
- 255,
603
- 105,
604
- 180
605
- ],
606
- indianred: [
607
- 205,
608
- 92,
609
- 92
610
- ],
611
- indigo: [
612
- 75,
613
- 0,
614
- 130
615
- ],
616
- ivory: [
617
- 255,
618
- 255,
619
- 240
620
- ],
621
- khaki: [
622
- 240,
623
- 230,
624
- 140
625
- ],
626
- lavender: [
627
- 230,
628
- 230,
629
- 250
630
- ],
631
- lavenderblush: [
632
- 255,
633
- 240,
634
- 245
635
- ],
636
- lawngreen: [
637
- 124,
638
- 252,
639
- 0
640
- ],
641
- lemonchiffon: [
642
- 255,
643
- 250,
644
- 205
645
- ],
646
- lightblue: [
647
- 173,
648
- 216,
649
- 230
650
- ],
651
- lightcoral: [
652
- 240,
653
- 128,
654
- 128
655
- ],
656
- lightcyan: [
657
- 224,
658
- 255,
659
- 255
660
- ],
661
- lightgoldenrodyellow: [
662
- 250,
663
- 250,
664
- 210
665
- ],
666
- lightgray: [
667
- 211,
668
- 211,
669
- 211
670
- ],
671
- lightgreen: [
672
- 144,
673
- 238,
674
- 144
675
- ],
676
- lightgrey: [
677
- 211,
678
- 211,
679
- 211
680
- ],
681
- lightpink: [
682
- 255,
683
- 182,
684
- 193
685
- ],
686
- lightsalmon: [
687
- 255,
688
- 160,
689
- 122
690
- ],
691
- lightseagreen: [
692
- 32,
693
- 178,
694
- 170
695
- ],
696
- lightskyblue: [
697
- 135,
698
- 206,
699
- 250
700
- ],
701
- lightslategray: [
702
- 119,
703
- 136,
704
- 153
705
- ],
706
- lightslategrey: [
707
- 119,
708
- 136,
709
- 153
710
- ],
711
- lightsteelblue: [
712
- 176,
713
- 196,
714
- 222
715
- ],
716
- lightyellow: [
717
- 255,
718
- 255,
719
- 224
720
- ],
721
- lime: [
722
- 0,
723
- 255,
724
- 0
725
- ],
726
- limegreen: [
727
- 50,
728
- 205,
729
- 50
730
- ],
731
- linen: [
732
- 250,
733
- 240,
734
- 230
735
- ],
736
- magenta: [
737
- 255,
738
- 0,
739
- 255
740
- ],
741
- maroon: [
742
- 128,
743
- 0,
744
- 0
745
- ],
746
- mediumaquamarine: [
747
- 102,
748
- 205,
749
- 170
750
- ],
751
- mediumblue: [
752
- 0,
753
- 0,
754
- 205
755
- ],
756
- mediumorchid: [
757
- 186,
758
- 85,
759
- 211
760
- ],
761
- mediumpurple: [
762
- 147,
763
- 112,
764
- 219
765
- ],
766
- mediumseagreen: [
767
- 60,
768
- 179,
769
- 113
770
- ],
771
- mediumslateblue: [
772
- 123,
773
- 104,
774
- 238
775
- ],
776
- mediumspringgreen: [
777
- 0,
778
- 250,
779
- 154
780
- ],
781
- mediumturquoise: [
782
- 72,
783
- 209,
784
- 204
785
- ],
786
- mediumvioletred: [
787
- 199,
788
- 21,
789
- 133
790
- ],
791
- midnightblue: [
792
- 25,
793
- 25,
794
- 112
795
- ],
796
- mintcream: [
797
- 245,
798
- 255,
799
- 250
800
- ],
801
- mistyrose: [
802
- 255,
803
- 228,
804
- 225
805
- ],
806
- moccasin: [
807
- 255,
808
- 228,
809
- 181
810
- ],
811
- navajowhite: [
812
- 255,
813
- 222,
814
- 173
815
- ],
816
- navy: [
817
- 0,
818
- 0,
819
- 128
820
- ],
821
- oldlace: [
822
- 253,
823
- 245,
824
- 230
825
- ],
826
- olive: [
827
- 128,
828
- 128,
829
- 0
830
- ],
831
- olivedrab: [
832
- 107,
833
- 142,
834
- 35
835
- ],
836
- orange: [
837
- 255,
838
- 165,
839
- 0
840
- ],
841
- orangered: [
842
- 255,
843
- 69,
844
- 0
845
- ],
846
- orchid: [
847
- 218,
848
- 112,
849
- 214
850
- ],
851
- palegoldenrod: [
852
- 238,
853
- 232,
854
- 170
855
- ],
856
- palegreen: [
857
- 152,
858
- 251,
859
- 152
860
- ],
861
- paleturquoise: [
862
- 175,
863
- 238,
864
- 238
865
- ],
866
- palevioletred: [
867
- 219,
868
- 112,
869
- 147
870
- ],
871
- papayawhip: [
872
- 255,
873
- 239,
874
- 213
875
- ],
876
- peachpuff: [
877
- 255,
878
- 218,
879
- 185
880
- ],
881
- peru: [
882
- 205,
883
- 133,
884
- 63
885
- ],
886
- pink: [
887
- 255,
888
- 192,
889
- 203
890
- ],
891
- plum: [
892
- 221,
893
- 160,
894
- 221
895
- ],
896
- powderblue: [
897
- 176,
898
- 224,
899
- 230
900
- ],
901
- purple: [
902
- 128,
903
- 0,
904
- 128
905
- ],
906
- rebeccapurple: [
907
- 102,
908
- 51,
909
- 153
910
- ],
911
- red: [
912
- 255,
913
- 0,
914
- 0
915
- ],
916
- rosybrown: [
917
- 188,
918
- 143,
919
- 143
920
- ],
921
- royalblue: [
922
- 65,
923
- 105,
924
- 225
925
- ],
926
- saddlebrown: [
927
- 139,
928
- 69,
929
- 19
930
- ],
931
- salmon: [
932
- 250,
933
- 128,
934
- 114
935
- ],
936
- sandybrown: [
937
- 244,
938
- 164,
939
- 96
940
- ],
941
- seagreen: [
942
- 46,
943
- 139,
944
- 87
945
- ],
946
- seashell: [
947
- 255,
948
- 245,
949
- 238
950
- ],
951
- sienna: [
952
- 160,
953
- 82,
954
- 45
955
- ],
956
- silver: [
957
- 192,
958
- 192,
959
- 192
960
- ],
961
- skyblue: [
962
- 135,
963
- 206,
964
- 235
965
- ],
966
- slateblue: [
967
- 106,
968
- 90,
969
- 205
970
- ],
971
- slategray: [
972
- 112,
973
- 128,
974
- 144
975
- ],
976
- slategrey: [
977
- 112,
978
- 128,
979
- 144
980
- ],
981
- snow: [
982
- 255,
983
- 250,
984
- 250
985
- ],
986
- springgreen: [
987
- 0,
988
- 255,
989
- 127
990
- ],
991
- steelblue: [
992
- 70,
993
- 130,
994
- 180
995
- ],
996
- tan: [
997
- 210,
998
- 180,
999
- 140
1000
- ],
1001
- teal: [
1002
- 0,
1003
- 128,
1004
- 128
1005
- ],
1006
- thistle: [
1007
- 216,
1008
- 191,
1009
- 216
1010
- ],
1011
- tomato: [
1012
- 255,
1013
- 99,
1014
- 71
1015
- ],
1016
- turquoise: [
1017
- 64,
1018
- 224,
1019
- 208
1020
- ],
1021
- violet: [
1022
- 238,
1023
- 130,
1024
- 238
1025
- ],
1026
- wheat: [
1027
- 245,
1028
- 222,
1029
- 179
1030
- ],
1031
- white: [
1032
- 255,
1033
- 255,
1034
- 255
1035
- ],
1036
- whitesmoke: [
1037
- 245,
1038
- 245,
1039
- 245
1040
- ],
1041
- yellow: [
1042
- 255,
1043
- 255,
1044
- 0
1045
- ],
1046
- yellowgreen: [
1047
- 154,
1048
- 205,
1049
- 50
1050
- ]
1051
- };
1052
276
  /**
1053
277
  * Apply an ANSI style pair to a string with nesting-safe composition.
1054
278
  *
@@ -1081,422 +305,57 @@ function applyStyle(text, style) {
1081
305
  if (result.includes(close)) result = result.replaceAll(close, close + open);
1082
306
  return open + result + close;
1083
307
  }
1084
- /** Foreground close: matches the close of every static fg color (`\x1b[39m`). */
1085
- const FG_CLOSE = "\x1B[39m";
1086
- /** Background close: matches the close of every static bg color (`\x1b[49m`). */
1087
- const BG_CLOSE = "\x1B[49m";
1088
- /** Foreground/background extended-color SGR introducers. */
1089
- const FG_INTRODUCER = "\x1B[38;";
1090
- const BG_INTRODUCER = "\x1B[48;";
1091
- /** Quote supported inputs, falling back to `String()` for hostile runtime values. */
1092
- function describeInput(input) {
1093
- try {
1094
- return JSON.stringify(input) ?? String(input);
1095
- } catch {
1096
- return String(input);
1097
- }
1098
- }
1099
- function isNamedColor(value) {
1100
- return Object.hasOwn(namedColorValues, value);
1101
- }
1102
- /** Parse a supported color into an RGB triple. */
1103
- function parseRgb(input) {
1104
- if (Array.isArray(input)) {
1105
- if (input.length === 3 && input.every((channel) => Number.isInteger(channel) && channel >= 0 && channel <= 255)) return [
1106
- input[0],
1107
- input[1],
1108
- input[2]
1109
- ];
1110
- } else if (typeof input === "string") {
1111
- const value = input.toLowerCase();
1112
- if (isNamedColor(value)) return namedColorValues[value];
1113
- const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value)?.[1];
1114
- if (hex) {
1115
- const expanded = hex.length === 3 ? hex.split("").map((digit) => digit + digit).join("") : hex;
1116
- return [
1117
- Number.parseInt(expanded.slice(0, 2), 16),
1118
- Number.parseInt(expanded.slice(2, 4), 16),
1119
- Number.parseInt(expanded.slice(4, 6), 16)
1120
- ];
1121
- }
1122
- const rgb = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/.exec(value) ?? /^rgb\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*\)$/.exec(value);
1123
- if (rgb) {
1124
- const channels = [
1125
- Number(rgb[1]),
1126
- Number(rgb[2]),
1127
- Number(rgb[3])
1128
- ];
1129
- if (channels.every((channel) => channel <= 255)) return channels;
1130
- }
1131
- }
1132
- throw new TypeError(`Invalid color input: ${describeInput(input)}`);
1133
- }
1134
- function rgbToAnsi256(r, g, b) {
1135
- const levels = [
1136
- 0,
1137
- 95,
1138
- 135,
1139
- 175,
1140
- 215,
1141
- 255
1142
- ];
1143
- const nearest = (channel) => levels.reduce((best, value, index) => Math.abs(value - channel) < Math.abs(levels[best] - channel) ? index : best, 0);
1144
- const [ir, ig, ib] = [
1145
- nearest(r),
1146
- nearest(g),
1147
- nearest(b)
1148
- ];
1149
- const grayIndex = Math.min(23, Math.max(0, Math.round(((r + g + b) / 3 - 8) / 10)));
1150
- const gray = 8 + grayIndex * 10;
1151
- const distance = (cr, cg, cb) => (r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2;
1152
- return distance(gray, gray, gray) < distance(levels[ir], levels[ig], levels[ib]) ? 232 + grayIndex : 16 + 36 * ir + 6 * ig + ib;
1153
- }
1154
- /**
1155
- * Quantize `[r, g, b]` to a foreground SGR parameter (`30`–`37`, `90`–`97`).
1156
- * Same algorithm as `ansi-styles` / `chalk`: bucket each channel at 50%,
1157
- * pack into a 3-bit base color, then add 60 for bright when the max
1158
- * channel rounds up. Call sites add `+10` for backgrounds.
1159
- */
1160
- function rgbToAnsi16Param(r, g, b) {
1161
- const brightnessBucket = Math.round(Math.max(r, g, b) / 127.5);
1162
- if (brightnessBucket === 0) return 30;
1163
- let ansi = 30 + ((Math.round(b / 255) << 2 | Math.round(g / 255) << 1 | Math.round(r / 255)) & 7);
1164
- if (brightnessBucket === 2) ansi += 60;
1165
- return ansi;
1166
- }
1167
- /**
1168
- * Foreground SGR open sequence at `depth`.
1169
- */
1170
- function fgOpen(input, depth) {
1171
- const [r, g, b] = parseRgb(input);
1172
- if (depth === "16") return `\x1b[${rgbToAnsi16Param(r, g, b)}m`;
1173
- if (depth === "256") return `\x1b[38;5;${rgbToAnsi256(r, g, b)}m`;
1174
- return `\x1b[38;2;${r};${g};${b}m`;
1175
- }
1176
- /**
1177
- * Background SGR open sequence at `depth`. For `truecolor` / `256`,
1178
- * derived from {@link fgOpen} by swapping the `\x1b[38;` introducer for
1179
- * `\x1b[48;` (both the truecolor and 256-color forms use it). For `16`,
1180
- * quantized directly to a real background SGR.
1181
- */
1182
- function bgOpen(input, depth) {
1183
- if (depth === "16") {
1184
- const [r, g, b] = parseRgb(input);
1185
- return `\x1b[${rgbToAnsi16Param(r, g, b) + 10}m`;
1186
- }
1187
- return fgOpen(input, depth).replace(FG_INTRODUCER, BG_INTRODUCER);
1188
- }
1189
- function colorPair(kind, input, depth) {
1190
- if (depth === "none") {
1191
- parseRgb(input);
1192
- return {
1193
- open: "",
1194
- close: ""
1195
- };
1196
- }
1197
- return {
1198
- open: kind === "fg" ? fgOpen(input, depth) : bgOpen(input, depth),
1199
- close: kind === "fg" ? FG_CLOSE : BG_CLOSE
1200
- };
1201
- }
1202
- function paint(kind, text, input, depth) {
1203
- const pair = colorPair(kind, input, depth);
1204
- return depth === "none" ? text : applyStyle(text, pair);
1205
- }
1206
- const OSC = "\x1B]";
1207
- const ST = "\x1B\\";
1208
- const HYPERLINK_CLOSE = `${OSC}8;;${ST}`;
1209
- function assertMatches(value, label, pattern, suffix = "") {
1210
- if (!pattern.test(value)) throw new TypeError(`Invalid ${label}: ${JSON.stringify(value)} must contain only printable ASCII characters${suffix}.`);
1211
- }
1212
- function serializeParams(options) {
1213
- const id = options?.id;
1214
- if (id === void 0 || id === "") return "";
1215
- assertMatches(id, "hyperlink id", /^[\x20-\x7e]*$/);
1216
- if (id.includes(":") || id.includes(";")) throw new TypeError("Invalid hyperlink id: \":\" and \";\" are reserved by the OSC 8 format.");
1217
- return `id=${id}`;
1218
- }
1219
- /**
1220
- * Create an OSC 8 hyperlink escape pair for `url`. Backs {@link link} and
1221
- * the `link` method on style instances; also used to validate URLs when
1222
- * hyperlink emission is disabled.
1223
- *
1224
- * @param url - Target URL. Must contain only printable ASCII characters
1225
- * and no spaces (per the OSC 8 spec). URL-encode characters outside
1226
- * that range before passing.
1227
- * @param options - Optional {@link HyperlinkOptions} — currently just
1228
- * `id` for grouping multi-segment links.
1229
- * @returns An {@link AnsiPair} whose `open` carries the URL and `close`
1230
- * terminates the hyperlink.
1231
- * @throws {TypeError} If `url` contains spaces or non-printable ASCII,
1232
- * or if `options.id` contains `":"` / `";"` (reserved by OSC 8) or
1233
- * non-printable characters.
1234
- *
1235
- * @internal
1236
- */
1237
- function linkCode(url, options) {
1238
- assertMatches(url, "hyperlink URL", /^[\x21-\x7e]*$/, " without spaces");
1239
- const params = serializeParams(options);
1240
- return {
1241
- open: `${OSC}8;${params};${url}${ST}`,
1242
- close: HYPERLINK_CLOSE
1243
- };
1244
- }
1245
- /**
1246
- * Wrap `text` in OSC 8 hyperlink escape sequences.
1247
- *
1248
- * @param text - The visible label.
1249
- * @param url - Target URL. Must contain only printable ASCII characters
1250
- * and no spaces (per the OSC 8 spec); URL-encode anything outside that
1251
- * range before passing.
1252
- * @param options - Optional {@link HyperlinkOptions}.
1253
- * @returns The styled string.
1254
- * @throws {TypeError} If `url` contains spaces or non-printable ASCII,
1255
- * or if `options.id` contains `":"` / `";"` (reserved by OSC 8) or
1256
- * non-printable characters.
1257
- *
1258
- * @example
1259
- * ```ts
1260
- * import { link } from "@crustjs/style";
1261
- *
1262
- * console.log(link("docs", "https://crustjs.com"));
1263
- * console.log(link("page 1", "https://example.com/p1", { id: "intro" }));
1264
- * ```
1265
- */
1266
- function link$1(text, url, options) {
1267
- return applyStyle(text, linkCode(url, options));
1268
- }
1269
- const dynamicColorKinds = ["fg", "bg"];
1270
- function stepIsModifier(step) {
1271
- return step.kind === "named" && isModifierName(step.name);
1272
- }
1273
- function stepPair(step, colorDepth) {
1274
- return step.kind === "named" ? styleMethodPairs[step.name] : colorPair(step.kind, step.input, colorDepth);
1275
- }
1276
- function colorStep(kind, input) {
1277
- const step = {
1278
- kind,
1279
- input
1280
- };
1281
- stepPair(step, "truecolor");
1282
- return Array.isArray(input) ? {
1283
- kind,
1284
- input: [
1285
- input[0],
1286
- input[1],
1287
- input[2]
1288
- ]
1289
- } : step;
1290
- }
1291
- function applyChain(text, steps, resolveCapabilities) {
1292
- if (text == null) return "";
1293
- let result = String(text);
1294
- if (result === "") return result;
1295
- const capabilities = resolveCapabilities();
1296
- const { modifiersEnabled, colorsEnabled } = capabilities;
1297
- for (let i = steps.length - 1; i >= 0; i--) {
1298
- const step = steps[i];
1299
- if (step === void 0) continue;
1300
- if (stepIsModifier(step) ? !modifiersEnabled : !colorsEnabled) continue;
1301
- result = applyStyle(result, stepPair(step, capabilities.colorDepth));
1302
- }
1303
- return result;
1304
- }
1305
308
  function isTemplateStringsArray(value) {
1306
309
  return Array.isArray(value) && "raw" in value && Array.isArray(value.raw);
1307
310
  }
1308
- function buildChainableStyleFactory(resolveCapabilities, runtime) {
1309
- const cache = /* @__PURE__ */ new Map();
1310
- const pairDepth = runtime ? "truecolor" : resolveCapabilities().colorDepth;
1311
- function createChainableStyle(steps) {
1312
- const key = steps.map((step) => step.kind === "named" ? step.name : `~${stepPair(step, "truecolor").open}`).join("|");
1313
- const cached = cache.get(key);
1314
- if (cached) return cached;
1315
- const styleFn = ((first, ...rest) => {
1316
- if (isTemplateStringsArray(first)) {
1317
- let text = "";
1318
- for (let i = 0; i < first.length; i++) {
1319
- text += first[i] ?? "";
1320
- if (i < rest.length) text += String(rest[i]);
1321
- }
1322
- return applyChain(text, steps, resolveCapabilities);
1323
- }
1324
- return applyChain(first, steps, resolveCapabilities);
1325
- });
1326
- if (cache.size >= 1024) cache.delete(cache.keys().next().value);
1327
- cache.set(key, styleFn);
1328
- for (const name of styleMethodNames) Object.defineProperty(styleFn, name, {
1329
- configurable: false,
1330
- enumerable: true,
1331
- get() {
1332
- return createChainableStyle([...steps, {
1333
- kind: "named",
1334
- name
1335
- }]);
1336
- }
1337
- });
1338
- for (const kind of dynamicColorKinds) Object.defineProperty(styleFn, kind, {
1339
- configurable: false,
1340
- enumerable: true,
1341
- value: (input) => createChainableStyle([...steps, colorStep(kind, input)]),
1342
- writable: false
1343
- });
1344
- let open = "";
1345
- let close = "";
1346
- for (const step of steps) {
1347
- const pair = stepPair(step, pairDepth);
1348
- open += pair.open;
1349
- close = pair.close + close;
1350
- }
1351
- Object.defineProperty(styleFn, "open", {
1352
- value: open,
1353
- writable: false,
1354
- configurable: false,
1355
- enumerable: true
1356
- });
1357
- Object.defineProperty(styleFn, "close", {
1358
- value: close,
1359
- writable: false,
1360
- configurable: false,
1361
- enumerable: true
1362
- });
1363
- return Object.freeze(styleFn);
311
+ /**
312
+ * Resolve the text of a style function call: a direct `fn(text)` call passes
313
+ * `text` through; a tagged template `` fn`a ${b}` `` joins its cooked strings
314
+ * with each value coerced via `String(...)`.
315
+ */
316
+ function callText(first, values) {
317
+ if (!isTemplateStringsArray(first)) return first;
318
+ let text = "";
319
+ for (let i = 0; i < first.length; i++) {
320
+ text += first[i] ?? "";
321
+ if (i < values.length) text += String(values[i]);
1364
322
  }
1365
- return createChainableStyle;
1366
- }
1367
- function buildStyleMethods(createChainableStyle) {
1368
- const methods = {};
1369
- for (const methodName of styleMethodNames) methods[methodName] = createChainableStyle([{
1370
- kind: "named",
1371
- name: methodName
1372
- }]);
1373
- return methods;
1374
- }
1375
- function resolveStyleCapabilities(options) {
1376
- const mode = options?.mode ?? "auto";
1377
- const modifiersEnabled = resolveModifierCapability(mode, options?.overrides);
1378
- const colorDepth = resolveColorDepth(mode, options?.overrides);
1379
- return {
1380
- modifiersEnabled,
1381
- colorDepth,
1382
- colorsEnabled: colorDepth !== "none",
1383
- trueColorEnabled: colorDepth === "truecolor"
1384
- };
323
+ return text;
1385
324
  }
1386
- function createStyleInstance(options, runtime) {
1387
- const resolveCapabilities = runtime ? resolveStyleCapabilities : (() => {
1388
- const capabilities = resolveStyleCapabilities(options);
1389
- return () => capabilities;
1390
- })();
1391
- const createChainableStyle = buildChainableStyleFactory(resolveCapabilities, runtime);
1392
- const methods = buildStyleMethods(createChainableStyle);
1393
- const instance = {
1394
- get enabled() {
1395
- const { modifiersEnabled, colorsEnabled } = resolveCapabilities();
1396
- return modifiersEnabled || colorsEnabled;
1397
- },
1398
- get colorsEnabled() {
1399
- return resolveCapabilities().colorsEnabled;
1400
- },
1401
- get trueColorEnabled() {
1402
- return resolveCapabilities().trueColorEnabled;
1403
- },
1404
- get colorDepth() {
1405
- return resolveCapabilities().colorDepth;
1406
- },
1407
- link(text, url, hyperlinkOptions) {
1408
- if (resolveCapabilities().modifiersEnabled) return link$1(text, url, hyperlinkOptions);
1409
- linkCode(url, hyperlinkOptions);
1410
- return text;
1411
- },
1412
- ...Object.fromEntries(dynamicColorKinds.map((kind) => [kind, (...args) => {
1413
- if (args.length === 1) return createChainableStyle([colorStep(kind, args[0])]);
1414
- return paint(kind, args[0], args[1], resolveCapabilities().colorDepth);
1415
- }])),
1416
- ...methods
325
+ /** A plain (non-chaining) style function that applies `pair` while `enabled()` is true. */
326
+ function plainStyleFn(pair, enabled) {
327
+ return (first, ...values) => {
328
+ const text = callText(first, values);
329
+ return enabled() ? applyStyle(text, pair) : text == null ? "" : String(text);
1417
330
  };
1418
- return Object.freeze(instance);
1419
331
  }
1420
- /**
1421
- * Default style instance using `"auto"` mode.
1422
- *
1423
- * Emits color ANSI codes when stdout is a TTY and `NO_COLOR` is not set.
1424
- * Non-color modifiers (bold, italic, etc.) follow TTY only; `FORCE_COLOR`,
1425
- * when set, decides unconditionally for both.
1426
- * Import this for convenient access without explicit configuration.
1427
- *
1428
- * @example
1429
- * ```ts
1430
- * import { style } from "@crustjs/style";
1431
- *
1432
- * console.log(style.bold("hello"));
1433
- * console.log(style.red("error"));
1434
- * ```
1435
- */
1436
- const style = createStyleInstance(void 0, true);
1437
- style.black;
1438
- style.red;
1439
- const green = style.green;
1440
- const yellow = style.yellow;
1441
- style.blue;
1442
- style.magenta;
1443
- const cyan = style.cyan;
1444
- style.white;
1445
- style.gray;
1446
- style.brightRed;
1447
- style.brightGreen;
1448
- style.brightYellow;
1449
- style.brightBlue;
1450
- style.brightMagenta;
1451
- style.brightCyan;
1452
- style.brightWhite;
1453
- style.bgBlack;
1454
- style.bgRed;
1455
- style.bgGreen;
1456
- style.bgYellow;
1457
- style.bgBlue;
1458
- style.bgMagenta;
1459
- style.bgCyan;
1460
- style.bgWhite;
1461
- style.bgBrightBlack;
1462
- style.bgBrightRed;
1463
- style.bgBrightGreen;
1464
- style.bgBrightYellow;
1465
- style.bgBrightBlue;
1466
- style.bgBrightMagenta;
1467
- style.bgBrightCyan;
1468
- style.bgBrightWhite;
1469
- const bold = style.bold;
1470
- const dim = style.dim;
1471
- style.italic;
1472
- style.underline;
1473
- style.inverse;
1474
- style.hidden;
1475
- style.strikethrough;
1476
- style.link;
1477
- style.fg;
1478
- style.bg;
1479
- //#endregion
1480
- //#region ../utils/dist/error.js
1481
- /** Narrows a caught Node.js system error to its stable errno contract. */
1482
- function isErrnoException(error) {
1483
- return error instanceof Error && "code" in error && typeof error.code === "string";
1484
- }
1485
- //#endregion
1486
- //#region ../utils/dist/json.js
1487
- /** Narrow a JSON document root to an object (not array/scalar/null). */
1488
- function isJsonObject(value) {
1489
- return typeof value === "object" && value !== null && !Array.isArray(value);
332
+ const modifiersEnabled = () => resolveModifierCapability("auto");
333
+ const colorsEnabled = () => resolveColorDepth("auto") !== "none";
334
+ function modifier(open, close) {
335
+ return plainStyleFn({
336
+ open: `\x1b[${open}m`,
337
+ close: `\x1b[${close}m`
338
+ }, modifiersEnabled);
1490
339
  }
1491
- //#endregion
1492
- //#region ../utils/dist/path.js
340
+ function color(open, close) {
341
+ return plainStyleFn({
342
+ open: `\x1b[${open}m`,
343
+ close: `\x1b[${close}m`
344
+ }, colorsEnabled);
345
+ }
346
+ const green = /* @__PURE__ */ color(32, 39);
347
+ const yellow = /* @__PURE__ */ color(33, 39);
348
+ const cyan = /* @__PURE__ */ color(36, 39);
349
+ const bold = /* @__PURE__ */ modifier(1, 22);
350
+ const dim = /* @__PURE__ */ modifier(2, 22);
351
+ // ../utils/dist/path.js
1493
352
  /** Returns whether child is parent itself or lies below it. */
1494
353
  function isWithin(parent, child) {
354
+ const { isAbsolute, relative, sep } = process.getBuiltinModule("node:path");
1495
355
  const path = relative(parent, child);
1496
356
  return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
1497
357
  }
1498
- //#endregion
1499
- //#region src/artifacts.ts
358
+ // src/artifacts.ts
1500
359
  /**
1501
360
  * `crust.include` directories normalized to cwd-relative POSIX names. They are
1502
361
  * staged exactly like Extension artifacts.
@@ -1525,20 +384,35 @@ function collectIncludeDirs(cwd, stageDir, include, artifactNames) {
1525
384
  return includeDirs;
1526
385
  }
1527
386
  /**
387
+ * Names a template ships on purpose that `npm pack`/`npm publish` drop from
388
+ * every directory of a package whatever its `files` says, in any letter case.
389
+ * A dropped `.gitignore` or `.npmignore` also acts as ignore rules for its own
390
+ * directory. The rest of npm's ignore list (`.DS_Store`, `*.orig`, …) is junk
391
+ * nobody means to publish, so it stays silently dropped.
392
+ */
393
+ const NPM_DROPPED_NAME = /^\.(?:gitignore|npmignore|npmrc)$/i;
394
+ /**
1528
395
  * Walks `dir` the way the dereferencing copy will (through symlinked
1529
- * directories) and rejects any path whose real location leaves the project.
396
+ * directories) and rejects any path whose real location leaves the project,
397
+ * and any {@link NPM_DROPPED_NAME} name.
1530
398
  */
1531
399
  function assertResolvesInsideProject(cwd, entry, dir) {
1532
400
  const realCwd = realpathSync(cwd);
1533
401
  const seen = /* @__PURE__ */ new Set();
402
+ const npmDropped = [];
1534
403
  const walk = (path) => {
1535
404
  const real = realpathSync(path);
1536
405
  if (!isWithin(realCwd, real)) throw new Error(`package.json crust.include entry ${JSON.stringify(entry)} resolves outside the project root: ${relative(cwd, path)} -> ${real}`);
406
+ if (NPM_DROPPED_NAME.test(basename(path))) npmDropped.push(relative(cwd, path));
1537
407
  if (seen.has(real) || !statSync(path).isDirectory()) return;
1538
408
  seen.add(real);
1539
409
  for (const child of readdirSync(path)) walk(join(path, child));
1540
410
  };
1541
411
  walk(dir);
412
+ if (npmDropped.length > 0) {
413
+ const listed = npmDropped.sort().map((path) => `\n ${path}`).join("");
414
+ throw new Error(`package.json crust.include entry ${JSON.stringify(entry)} contains files npm leaves out of published packages:${listed}\n Rename each (e.g. .gitignore -> _gitignore) and restore the name when your CLI copies it; scaffold() from @crustjs/create turns a leading _ on a file name into a dot.`);
415
+ }
1542
416
  }
1543
417
  /**
1544
418
  * Copies one entry's Extension build hook output into the shared artifact
@@ -1591,8 +465,46 @@ function collectArtifacts(artifactOutDir) {
1591
465
  manPages: names.includes("man") ? readdirSync(join(artifactOutDir, "man"), { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).sort() : []
1592
466
  };
1593
467
  }
1594
- //#endregion
1595
- //#region ../utils/dist/process.js
468
+ // ../utils/dist/process.js
469
+ const LIFETIME_SIGNALS = [
470
+ "SIGINT",
471
+ "SIGTERM",
472
+ "SIGHUP",
473
+ "SIGQUIT"
474
+ ];
475
+ const bindings = /* @__PURE__ */ new Set();
476
+ function onLifetimeSignal(signal) {
477
+ const pending = [...bindings];
478
+ bindings.clear();
479
+ removeLifetimeListeners();
480
+ for (const binding of pending) binding.onSignal(signal);
481
+ if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
482
+ }
483
+ function onLifetimeExit() {
484
+ for (const binding of bindings) binding.onExit();
485
+ }
486
+ function removeLifetimeListeners() {
487
+ for (const signal of LIFETIME_SIGNALS) process.removeListener(signal, onLifetimeSignal);
488
+ process.removeListener("exit", onLifetimeExit);
489
+ }
490
+ /**
491
+ * Ties a detached POSIX child group to this process, which the group no longer shares
492
+ * terminal signals with. Until the returned release runs, SIGINT, SIGTERM, SIGHUP or SIGQUIT
493
+ * calls `onSignal` once and is then re-raised when no other listener handles it, and a
494
+ * synchronous exit (`process.exit`, an uncaught error) calls `onExit`. SIGKILL and native
495
+ * crashes cannot be observed and still orphan the group. A signal this process inherited as
496
+ * ignored (Bun on Linux, such as SIGHUP under `nohup`) stays ignored and is never listened for.
497
+ */
498
+ function bindToProcessLifetime(binding) {
499
+ if (bindings.size === 0) {
500
+ for (const signal of LIFETIME_SIGNALS) if (!isSignalIgnored(signal)) process.prependListener(signal, onLifetimeSignal);
501
+ process.on("exit", onLifetimeExit);
502
+ }
503
+ bindings.add(binding);
504
+ return () => {
505
+ if (bindings.delete(binding) && bindings.size === 0) removeLifetimeListeners();
506
+ };
507
+ }
1596
508
  const WINDOWS_SHELL_UNSAFE = /[\0\r\n"%!^`<>&|]/;
1597
509
  const WINDOWS_SHELL_META = /([()\][%!^"`<>&|;, *?])/g;
1598
510
  function escapeWindowsShellArgument(value) {
@@ -1604,7 +516,7 @@ function escapeWindowsShellArgument(value) {
1604
516
  /** @internal Build Node's escaped Windows command-shim workaround. */
1605
517
  function getWindowsShimCommand(command, args, shell, platform = process.platform) {
1606
518
  if (shell || platform !== "win32" || !/\.(cmd|bat)$/i.test(command)) return null;
1607
- const shimCommand = win32.normalize(command);
519
+ const shimCommand = process.getBuiltinModule("node:path").win32.normalize(command);
1608
520
  for (const [index, value] of [shimCommand, ...args].entries()) if (WINDOWS_SHELL_UNSAFE.test(value)) {
1609
521
  const label = index === 0 ? "command" : `argument ${index}`;
1610
522
  throw new Error(`Windows command shim ${label} ${JSON.stringify(value)} contains unsafe shell characters`);
@@ -1630,6 +542,7 @@ function killProcessTree(child) {
1630
542
  if (child.pid === void 0) return;
1631
543
  if (process.platform === "win32") {
1632
544
  if (child.exitCode === null && child.signalCode === null) {
545
+ const { spawnSync } = process.getBuiltinModule("node:child_process");
1633
546
  const result = spawnSync("taskkill", [
1634
547
  "/pid",
1635
548
  String(child.pid),
@@ -1660,9 +573,12 @@ function killProcessTree(child) {
1660
573
  * calls are passed through unchanged and own their shell escaping.
1661
574
  */
1662
575
  async function runProcess(command, args = [], options = {}) {
576
+ options.signal?.throwIfAborted();
1663
577
  const windowsShimCommand = getWindowsShimCommand(command, args, options.shell);
1664
578
  const collect = (options.stdio ?? "collect") === "collect";
1665
579
  const collectStdout = collect && options.stdout !== "ignore";
580
+ const detached = options.timeout !== void 0 && process.platform !== "win32";
581
+ const { spawn } = process.getBuiltinModule("node:child_process");
1666
582
  const proc = spawn(windowsShimCommand?.command ?? command, windowsShimCommand?.args ?? args, {
1667
583
  cwd: options.cwd,
1668
584
  env: options.env,
@@ -1672,31 +588,54 @@ async function runProcess(command, args = [], options = {}) {
1672
588
  collectStdout ? "pipe" : "ignore",
1673
589
  "pipe"
1674
590
  ] : "inherit",
1675
- detached: options.timeout !== void 0 && process.platform !== "win32",
591
+ detached,
1676
592
  windowsVerbatimArguments: windowsShimCommand?.windowsVerbatimArguments
1677
593
  });
1678
- let timer;
1679
- const deadline = new Promise((_, reject) => {
1680
- if (options.timeout === void 0) return;
1681
- const timeout = options.timeout;
1682
- timer = setTimeout(() => {
1683
- let failure = /* @__PURE__ */ new Error(`${[command, ...args].join(" ")} did not exit within ${timeout} ms and was killed.`);
594
+ const commandLine = [command, ...args].join(" ");
595
+ let stop;
596
+ const stopped = new Promise((_, reject) => {
597
+ stop = (message, reason) => {
598
+ let failure = new Error(message);
1684
599
  try {
1685
600
  killProcessTree(proc);
1686
601
  } catch (cause) {
1687
- failure = new Error("Process-tree cleanup failed after timeout.", { cause });
602
+ failure = new Error(`Process-tree cleanup failed after ${reason}.`, { cause });
1688
603
  }
1689
604
  proc.stdout?.destroy();
1690
605
  proc.stderr?.destroy();
1691
606
  reject(failure);
1692
- }, timeout);
607
+ };
1693
608
  });
609
+ const timeout = options.timeout;
610
+ const timer = timeout === void 0 ? void 0 : setTimeout(() => stop(`${commandLine} did not exit within ${timeout} ms and was killed.`, "timeout"), timeout);
611
+ const release = detached ? bindToProcessLifetime({
612
+ onSignal: (signal) => stop(`${commandLine} was interrupted by ${signal}.`, signal),
613
+ onExit: () => {
614
+ try {
615
+ killProcessTree(proc);
616
+ } catch {}
617
+ }
618
+ }) : () => {};
619
+ let forceKillTimer;
620
+ const onAbort = () => {
621
+ try {
622
+ if (detached || process.platform === "win32") killProcessTree(proc);
623
+ else {
624
+ proc.kill("SIGTERM");
625
+ forceKillTimer = setTimeout(() => proc.kill("SIGKILL"), 5e3);
626
+ }
627
+ } catch {}
628
+ };
629
+ options.signal?.addEventListener("abort", onAbort, { once: true });
630
+ const { text } = process.getBuiltinModule("node:stream/consumers");
631
+ const { once } = process.getBuiltinModule("node:events");
1694
632
  try {
1695
633
  const [stdout, stderr, [exitCode]] = await Promise.race([Promise.all([
1696
634
  collectStdout ? text(proc.stdout) : "",
1697
635
  collect ? text(proc.stderr) : "",
1698
636
  once(proc, "close")
1699
- ]), deadline]);
637
+ ]), stopped]);
638
+ options.signal?.throwIfAborted();
1700
639
  return {
1701
640
  exitCode,
1702
641
  stdout,
@@ -1704,9 +643,13 @@ async function runProcess(command, args = [], options = {}) {
1704
643
  };
1705
644
  } finally {
1706
645
  clearTimeout(timer);
646
+ clearTimeout(forceKillTimer);
647
+ release();
648
+ options.signal?.removeEventListener("abort", onAbort);
1707
649
  }
1708
650
  }
1709
651
  function isExecutableFile(path) {
652
+ const { accessSync, constants, statSync } = process.getBuiltinModule("node:fs");
1710
653
  try {
1711
654
  accessSync(path, constants.X_OK);
1712
655
  return statSync(path).isFile();
@@ -1716,6 +659,7 @@ function isExecutableFile(path) {
1716
659
  }
1717
660
  /** Resolve a bare executable name from PATH (and PATHEXT on Windows). */
1718
661
  function which(command) {
662
+ const { delimiter, extname, join, resolve, sep } = process.getBuiltinModule("node:path");
1719
663
  if (command.includes(sep) || command.includes("/")) return isExecutableFile(command) ? command : null;
1720
664
  const extensions = process.platform === "win32" && !extname(command) ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
1721
665
  for (const directory of process.env.PATH?.split(delimiter) ?? []) for (const extension of extensions) {
@@ -1724,8 +668,7 @@ function which(command) {
1724
668
  }
1725
669
  return null;
1726
670
  }
1727
- //#endregion
1728
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/lrucache.js
671
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/lrucache.js
1729
672
  var require_lrucache = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1730
673
  var LRUCache = class {
1731
674
  constructor() {
@@ -1757,8 +700,7 @@ var require_lrucache = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1757
700
  };
1758
701
  module.exports = LRUCache;
1759
702
  }));
1760
- //#endregion
1761
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/parse-options.js
703
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/parse-options.js
1762
704
  var require_parse_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1763
705
  const looseOption = Object.freeze({ loose: true });
1764
706
  const emptyOpts = Object.freeze({});
@@ -1769,8 +711,7 @@ var require_parse_options = /* @__PURE__ */ __commonJSMin(((exports, module) =>
1769
711
  };
1770
712
  module.exports = parseOptions;
1771
713
  }));
1772
- //#endregion
1773
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/constants.js
714
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/constants.js
1774
715
  var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1775
716
  module.exports = {
1776
717
  MAX_LENGTH: 256,
@@ -1792,13 +733,11 @@ var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1792
733
  FLAG_LOOSE: 2
1793
734
  };
1794
735
  }));
1795
- //#endregion
1796
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/debug.js
736
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/debug.js
1797
737
  var require_debug = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1798
738
  module.exports = typeof process === "object" && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error("SEMVER", ...args) : () => {};
1799
739
  }));
1800
- //#endregion
1801
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/re.js
740
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/re.js
1802
741
  var require_re = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1803
742
  const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH } = require_constants();
1804
743
  const debug = require_debug();
@@ -1876,8 +815,7 @@ var require_re = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1876
815
  createToken("GTE0", "^\\s*>=\\s*0\\.0\\.0\\s*$");
1877
816
  createToken("GTE0PRE", "^\\s*>=\\s*0\\.0\\.0-0\\s*$");
1878
817
  }));
1879
- //#endregion
1880
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/identifiers.js
818
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/identifiers.js
1881
819
  var require_identifiers = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1882
820
  const numeric = /^[0-9]+$/;
1883
821
  const compareIdentifiers = (a, b) => {
@@ -1896,8 +834,7 @@ var require_identifiers = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1896
834
  rcompareIdentifiers
1897
835
  };
1898
836
  }));
1899
- //#endregion
1900
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/semver.js
837
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/semver.js
1901
838
  var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1902
839
  const debug = require_debug();
1903
840
  const { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants();
@@ -2082,57 +1019,49 @@ var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2082
1019
  }
2083
1020
  };
2084
1021
  }));
2085
- //#endregion
2086
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare.js
1022
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare.js
2087
1023
  var require_compare = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2088
1024
  const SemVer = require_semver();
2089
1025
  const compare = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose));
2090
1026
  module.exports = compare;
2091
1027
  }));
2092
- //#endregion
2093
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/eq.js
1028
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/eq.js
2094
1029
  var require_eq = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2095
1030
  const compare = require_compare();
2096
1031
  const eq = (a, b, loose) => compare(a, b, loose) === 0;
2097
1032
  module.exports = eq;
2098
1033
  }));
2099
- //#endregion
2100
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/neq.js
1034
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/neq.js
2101
1035
  var require_neq = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2102
1036
  const compare = require_compare();
2103
1037
  const neq = (a, b, loose) => compare(a, b, loose) !== 0;
2104
1038
  module.exports = neq;
2105
1039
  }));
2106
- //#endregion
2107
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/gt.js
1040
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/gt.js
2108
1041
  var require_gt = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2109
1042
  const compare = require_compare();
2110
1043
  const gt = (a, b, loose) => compare(a, b, loose) > 0;
2111
1044
  module.exports = gt;
2112
1045
  }));
2113
- //#endregion
2114
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/gte.js
1046
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/gte.js
2115
1047
  var require_gte = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2116
1048
  const compare = require_compare();
2117
1049
  const gte = (a, b, loose) => compare(a, b, loose) >= 0;
2118
1050
  module.exports = gte;
2119
1051
  }));
2120
- //#endregion
2121
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/lt.js
1052
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/lt.js
2122
1053
  var require_lt = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2123
1054
  const compare = require_compare();
2124
1055
  const lt = (a, b, loose) => compare(a, b, loose) < 0;
2125
1056
  module.exports = lt;
2126
1057
  }));
2127
- //#endregion
2128
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/lte.js
1058
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/lte.js
2129
1059
  var require_lte = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2130
1060
  const compare = require_compare();
2131
1061
  const lte = (a, b, loose) => compare(a, b, loose) <= 0;
2132
1062
  module.exports = lte;
2133
1063
  }));
2134
- //#endregion
2135
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/cmp.js
1064
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/cmp.js
2136
1065
  var require_cmp = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2137
1066
  const eq = require_eq();
2138
1067
  const neq = require_neq();
@@ -2163,8 +1092,7 @@ var require_cmp = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2163
1092
  };
2164
1093
  module.exports = cmp;
2165
1094
  }));
2166
- //#endregion
2167
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/comparator.js
1095
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/comparator.js
2168
1096
  var require_comparator = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2169
1097
  const ANY = Symbol("SemVer ANY");
2170
1098
  module.exports = class Comparator {
@@ -2235,8 +1163,7 @@ var require_comparator = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2235
1163
  const SemVer = require_semver();
2236
1164
  const Range = require_range();
2237
1165
  }));
2238
- //#endregion
2239
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/range.js
1166
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/range.js
2240
1167
  var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2241
1168
  const SPACE_CHARACTERS = /\s+/g;
2242
1169
  module.exports = class Range {
@@ -2518,8 +1445,7 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2518
1445
  return true;
2519
1446
  };
2520
1447
  }));
2521
- //#endregion
2522
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/satisfies.js
1448
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/satisfies.js
2523
1449
  var require_satisfies = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2524
1450
  const Range = require_range();
2525
1451
  const satisfies = (version, range, options) => {
@@ -2532,8 +1458,7 @@ var require_satisfies = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2532
1458
  };
2533
1459
  module.exports = satisfies;
2534
1460
  }));
2535
- //#endregion
2536
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js
1461
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js
2537
1462
  var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2538
1463
  const SemVer = require_semver();
2539
1464
  const parse = (version, options, throwErrors = false) => {
@@ -2547,8 +1472,7 @@ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2547
1472
  };
2548
1473
  module.exports = parse;
2549
1474
  }));
2550
- //#endregion
2551
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/valid.js
1475
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/valid.js
2552
1476
  var require_valid$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2553
1477
  const parse = require_parse();
2554
1478
  const valid = (version, options) => {
@@ -2557,8 +1481,7 @@ var require_valid$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2557
1481
  };
2558
1482
  module.exports = valid;
2559
1483
  }));
2560
- //#endregion
2561
- //#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/valid.js
1484
+ // ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/valid.js
2562
1485
  var require_valid = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2563
1486
  const Range = require_range();
2564
1487
  const validRange = (range, options) => {
@@ -2570,16 +1493,11 @@ var require_valid = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2570
1493
  };
2571
1494
  module.exports = validRange;
2572
1495
  }));
2573
- //#endregion
2574
- //#region src/targets.ts
1496
+ // src/targets.ts
2575
1497
  var import_satisfies = /* @__PURE__ */ __toESM(require_satisfies(), 1);
2576
1498
  var import_valid = /* @__PURE__ */ __toESM(require_valid$1(), 1);
2577
1499
  var import_valid$1 = /* @__PURE__ */ __toESM(require_valid(), 1);
2578
- const BUILD_RUNTIMES = [
2579
- "bun",
2580
- "deno",
2581
- "node"
2582
- ];
1500
+ const BUILD_RUNTIMES = ["bun", "node"];
2583
1501
  const BUN_TARGETS = {
2584
1502
  runtime: "Bun",
2585
1503
  targets: [
@@ -2647,57 +1565,6 @@ const BUN_TARGETS = {
2647
1565
  }
2648
1566
  }
2649
1567
  };
2650
- const DENO_TARGETS = {
2651
- runtime: "Deno",
2652
- targets: [
2653
- "x86_64-unknown-linux-gnu",
2654
- "aarch64-unknown-linux-gnu",
2655
- "x86_64-apple-darwin",
2656
- "aarch64-apple-darwin",
2657
- "x86_64-pc-windows-msvc",
2658
- "aarch64-pc-windows-msvc"
2659
- ],
2660
- info: {
2661
- "x86_64-unknown-linux-gnu": {
2662
- alias: "linux-x64",
2663
- platformKey: "linux-x64",
2664
- os: "linux",
2665
- cpu: "x64",
2666
- libc: "glibc"
2667
- },
2668
- "aarch64-unknown-linux-gnu": {
2669
- alias: "linux-arm64",
2670
- platformKey: "linux-arm64",
2671
- os: "linux",
2672
- cpu: "arm64",
2673
- libc: "glibc"
2674
- },
2675
- "x86_64-apple-darwin": {
2676
- alias: "darwin-x64",
2677
- platformKey: "darwin-x64",
2678
- os: "darwin",
2679
- cpu: "x64"
2680
- },
2681
- "aarch64-apple-darwin": {
2682
- alias: "darwin-arm64",
2683
- platformKey: "darwin-arm64",
2684
- os: "darwin",
2685
- cpu: "arm64"
2686
- },
2687
- "x86_64-pc-windows-msvc": {
2688
- alias: "windows-x64",
2689
- platformKey: "win32-x64",
2690
- os: "win32",
2691
- cpu: "x64"
2692
- },
2693
- "aarch64-pc-windows-msvc": {
2694
- alias: "windows-arm64",
2695
- platformKey: "win32-arm64",
2696
- os: "win32",
2697
- cpu: "arm64"
2698
- }
2699
- }
2700
- };
2701
1568
  const NODE_TARGETS = {
2702
1569
  runtime: "Node",
2703
1570
  targets: [
@@ -2786,8 +1653,7 @@ function hostTarget(table) {
2786
1653
  const platformKey = hostPlatformKey();
2787
1654
  return table.targets.find((target) => table.info[target].platformKey === platformKey) ?? null;
2788
1655
  }
2789
- //#endregion
2790
- //#region src/compilers.ts
1656
+ // src/compilers.ts
2791
1657
  /** A finished compiler process's stderr, then stdout, for error messages. */
2792
1658
  function processOutput({ stdout, stderr }) {
2793
1659
  return [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
@@ -2851,15 +1717,6 @@ function assertTargetsBuildableWithoutBun(targets, runner, host = hostTarget(BUN
2851
1717
  throw new Error(`Cannot build ${host} without a separate bun executable on PATH.\n Bun reuses the running crust executable as the base for its own platform, which yields a binary that crashes on start.
2852
1718
  Install Bun (https://bun.sh), or ${alternative}.`);
2853
1719
  }
2854
- /** The external `deno` on PATH; Deno has no embedded fallback. */
2855
- function resolveDenoBuildRunner() {
2856
- const denoPath = which("deno");
2857
- if (!denoPath) throw new Error("Deno is required for the deno runtime but was not found on PATH.\n Install Deno from https://deno.com/ and try again.");
2858
- return {
2859
- command: denoPath,
2860
- env: { ...process.env }
2861
- };
2862
- }
2863
1720
  /** The external `node` on PATH, which runs tsdown's executable builder; there is no embedded fallback. */
2864
1721
  function resolveNodeBuildRunner() {
2865
1722
  const nodePath = which("node");
@@ -2871,14 +1728,13 @@ function resolveNodeBuildRunner() {
2871
1728
  }
2872
1729
  const RUNTIME_LABELS = {
2873
1730
  bun: "Bun",
2874
- deno: "Deno",
2875
1731
  node: "Node"
2876
1732
  };
2877
1733
  const COMPILER_VERSION_TIMEOUT_MS = 3e4;
2878
1734
  /**
2879
1735
  * Runtime version reported by the runner itself (`<command> --version`), never
2880
- * the runtime hosting this process: `bun` prints `1.4.2`, `deno` prints
2881
- * `deno 2.9.6 (…)`, `node` prints `v24.21.0`. Under the `BUN_BE_BUN` fallback
1736
+ * the runtime hosting this process: `bun` prints `1.4.2`, `node` prints
1737
+ * `v24.21.0`. Under the `BUN_BE_BUN` fallback
2882
1738
  * this is the embedded Bun, which is the compiler. `cwd` must be the directory
2883
1739
  * compilation runs in: a version-manager shim picks its runtime from it. A
2884
1740
  * probe still running after {@link COMPILER_VERSION_TIMEOUT_MS} is killed and
@@ -2892,8 +1748,7 @@ async function readCompilerVersion(runtime, runner, cwd) {
2892
1748
  timeout: COMPILER_VERSION_TIMEOUT_MS
2893
1749
  });
2894
1750
  const stdout = result.stdout.trim();
2895
- const reported = runtime === "deno" ? /^deno (\S+)/.exec(stdout)?.[1] : stdout;
2896
- const version = result.exitCode === 0 && reported !== void 0 ? (0, import_valid.default)(reported) : null;
1751
+ const version = result.exitCode === 0 ? (0, import_valid.default)(stdout) : null;
2897
1752
  if (version === null) {
2898
1753
  const output = processOutput(result);
2899
1754
  throw new Error(`Could not read the ${RUNTIME_LABELS[runtime]} version from ${runner.command} --version (exit ${result.exitCode})${output ? `:\n${output}` : "."}`);
@@ -2922,47 +1777,22 @@ function assertCompilerSatisfiesEngines(compiler, userPackageJson) {
2922
1777
  }
2923
1778
  }
2924
1779
  /**
2925
- * Select the binary compiler once for a build: the Bun runner (external bun
2926
- * first, then the embedded fallback) or the external deno. Returns its actual
2927
- * version, read in the project directory `cwd`, after validating it against
2928
- * `engines`. Pass `runner` and the same `cwd` on to the `exec*`/`buildEntrypoint`
1780
+ * Select the Bun binary compiler once for a build: external bun first, then
1781
+ * the embedded fallback. Returns its actual version, read in the project
1782
+ * directory `cwd`, after validating it against `engines`. Pass `runner` and the same `cwd` on to the `exec*`/`buildEntrypoint`
2929
1783
  * helpers so every step uses this same compiler.
2930
1784
  */
2931
- async function resolveBinaryCompiler(runtime, userPackageJson, cwd) {
2932
- const runner = runtime === "bun" ? resolveBunBuildRunner() : resolveDenoBuildRunner();
1785
+ async function resolveBinaryCompiler(userPackageJson, cwd) {
1786
+ const runner = resolveBunBuildRunner();
2933
1787
  const compiler = {
2934
- runtime,
1788
+ runtime: "bun",
2935
1789
  runner,
2936
- version: await readCompilerVersion(runtime, runner, cwd)
1790
+ version: await readCompilerVersion("bun", runner, cwd)
2937
1791
  };
2938
1792
  assertCompilerSatisfiesEngines(compiler, userPackageJson);
2939
1793
  return compiler;
2940
1794
  }
2941
- /**
2942
- * Oldest `deno` accepted to bundle a Deno runtime package. Deno 2.4 resolves a
2943
- * package.json project's npm dependencies from the registry instead of its
2944
- * installed node_modules, so it can bundle a different Crust than the project
2945
- * installed. Tested on Linux x64 with Deno 2.5.0 through 2.9.6.
2946
- */
2947
- const DENO_PACKAGE_MIN_VERSION = "2.5.0";
2948
- /**
2949
- * Select the external deno that bundles a Deno runtime package, reading its
2950
- * version in the project directory `cwd`. Call it before staging is replaced.
2951
- * A runtime package embeds no Deno, so `engines.deno` stays a consumer
2952
- * requirement and is not checked against this bundler.
2953
- */
2954
- async function resolveDenoPackageBundler(cwd, runner = resolveDenoBuildRunner()) {
2955
- const version = await readCompilerVersion("deno", runner, cwd);
2956
- if (!(0, import_satisfies.default)(version, `>=2.5.0`, { includePrerelease: true })) throw new Error(`Deno ${version} (${runner.command}) cannot bundle a Deno runtime package; deno ${DENO_PACKAGE_MIN_VERSION} or newer is required.\n Older deno bundle resolves package.json dependencies from the npm registry instead of the installed node_modules.
2957
- crust does not install or upgrade Deno; put a newer deno first on PATH (e.g. with your version manager).`);
2958
- return {
2959
- runtime: "deno",
2960
- runner,
2961
- version
2962
- };
2963
- }
2964
- //#endregion
2965
- //#region src/bundle.ts
1795
+ // src/bundle.ts
2966
1796
  function toBunEnvFileArgs(envFiles) {
2967
1797
  return envFiles.flatMap((envFile) => ["--env-file", envFile]);
2968
1798
  }
@@ -2978,8 +1808,7 @@ function resolveBunPluginSource(specifier, cwd) {
2978
1808
  /**
2979
1809
  * Marks every Bun/Node bundle crust produces so `resolveArtifactDir` (core)
2980
1810
  * can tell a staged bundle from source at runtime. Not a `PUBLIC_*` variable:
2981
- * it is internal to crust, never user-set. Deno compile has no define, but
2982
- * standalone Deno binaries are detected directly.
1811
+ * it is internal to crust, never user-set.
2983
1812
  */
2984
1813
  const CRUST_BUILD_DEFINE = { "process.env.CRUST_INTERNAL_BUILD": "\"1\"" };
2985
1814
  const CRUST_BUILD_DEFINE_ARG = "process.env.CRUST_INTERNAL_BUILD=\"1\"";
@@ -3010,6 +1839,12 @@ for (const { specifier, source } of options.plugins) {
3010
1839
  }
3011
1840
  plugins.push(plugin);
3012
1841
  }
1842
+ if (options.stripShebang) {
1843
+ // Last, so a project plugin's onLoad for the entry still wins. The emptied line keeps line numbers.
1844
+ plugins.push({ name: "crust-strip-shebang", setup(build) {
1845
+ build.onLoad({ filter: new RegExp(\`^(?:\${options.stripShebang.map(RegExp.escape).join("|")})$\`) }, async ({ path, loader }) => ({ contents: (await Bun.file(path).text()).replace(/^#![^\\n]*/, ""), loader }));
1846
+ } });
1847
+ }
3013
1848
  const result = await Bun.build({ ...options.build, define: ${JSON.stringify(CRUST_BUILD_DEFINE)}, plugins, throw: false });
3014
1849
  if (!result.success) {
3015
1850
  for (const log of result.logs) console.error(log);
@@ -3025,7 +1860,7 @@ if (!options.build.compile) {
3025
1860
  }
3026
1861
  `;
3027
1862
  }
3028
- async function runBunPluginDriver(build, outfilePath, { cwd, envFiles, bunPlugins }, runner) {
1863
+ async function runBunPluginDriver(build, outfilePath, { cwd, envFiles, bunPlugins }, runner, stripShebang) {
3029
1864
  const driverPath = join(cwd, `.crust-build-${randomBytes(6).toString("hex")}.ts`);
3030
1865
  await writeFile(driverPath, createBunPluginDriverScript({
3031
1866
  plugins: bunPlugins.map((specifier) => ({
@@ -3033,7 +1868,8 @@ async function runBunPluginDriver(build, outfilePath, { cwd, envFiles, bunPlugin
3033
1868
  source: resolveBunPluginSource(specifier, cwd)
3034
1869
  })),
3035
1870
  build,
3036
- outfile: outfilePath
1871
+ outfile: outfilePath,
1872
+ stripShebang
3037
1873
  }));
3038
1874
  try {
3039
1875
  await runBuildProcess(runner, [...toBunEnvFileArgs(envFiles), driverPath], outfilePath, cwd);
@@ -3067,13 +1903,17 @@ async function execBuild(entryPath, outfilePath, target, settings, runner = reso
3067
1903
  target: compileTarget,
3068
1904
  outfile: outfilePath,
3069
1905
  autoloadBunfig: false
3070
- }
1906
+ },
1907
+ ...settings.bytecode ? {
1908
+ bytecode: true,
1909
+ format: "esm"
1910
+ } : {}
3071
1911
  }, outfilePath, settings, runner);
3072
1912
  return;
3073
1913
  }
3074
- await runBuildProcess(runner, createBunCompileArgs(entryPath, outfilePath, settings.minify, compileTarget, settings.envFiles), outfilePath, settings.cwd);
1914
+ await runBuildProcess(runner, createBunCompileArgs(entryPath, outfilePath, settings.minify, compileTarget, settings.envFiles, settings.bytecode), outfilePath, settings.cwd);
3075
1915
  }
3076
- function createBunCompileArgs(entryPath, outfilePath, minify, target, envFiles = []) {
1916
+ function createBunCompileArgs(entryPath, outfilePath, minify, target, envFiles = [], bytecode = false) {
3077
1917
  return [
3078
1918
  "build",
3079
1919
  "--compile",
@@ -3085,17 +1925,7 @@ function createBunCompileArgs(entryPath, outfilePath, minify, target, envFiles =
3085
1925
  "--outfile",
3086
1926
  outfilePath,
3087
1927
  ...minify ? ["--minify"] : [],
3088
- "--target",
3089
- target,
3090
- entryPath
3091
- ];
3092
- }
3093
- function createDenoCompileArgs(entryPath, outfilePath, target) {
3094
- return [
3095
- "compile",
3096
- "-A",
3097
- "--output",
3098
- outfilePath,
1928
+ ...bytecode ? ["--bytecode", "--format=esm"] : [],
3099
1929
  "--target",
3100
1930
  target,
3101
1931
  entryPath
@@ -3118,14 +1948,15 @@ async function runBuildProcess(runner, args, outfilePath, cwd) {
3118
1948
  */
3119
1949
  async function execScriptBuild(target, entryPath, outfilePath, settings, runner = resolveBunBuildRunner()) {
3120
1950
  const { cwd, minify, envFiles, external } = settings;
3121
- if (settings.bunPlugins.length > 0) await runBunPluginDriver({
1951
+ const stripShebang = target === "node" && /^#![^\n]*\bbun\b/.test(await readFile(entryPath, "utf8")) ? [entryPath, await realpath(entryPath)] : void 0;
1952
+ if (settings.bunPlugins.length > 0 || stripShebang !== void 0) await runBunPluginDriver({
3122
1953
  entrypoints: [entryPath],
3123
1954
  minify,
3124
1955
  env: "PUBLIC_*",
3125
1956
  target,
3126
1957
  format: "esm",
3127
1958
  external
3128
- }, outfilePath, settings, runner);
1959
+ }, outfilePath, settings, runner, stripShebang);
3129
1960
  else await runBuildProcess(runner, [
3130
1961
  "build",
3131
1962
  ...toBunEnvFileArgs(envFiles),
@@ -3142,40 +1973,14 @@ async function execScriptBuild(target, entryPath, outfilePath, settings, runner
3142
1973
  ...minify ? ["--minify"] : [],
3143
1974
  entryPath
3144
1975
  ], outfilePath, cwd);
3145
- const output = await readFile(outfilePath, "utf8");
3146
- const shebang = `#!/usr/bin/env ${target}\n`;
3147
- await writeFile(outfilePath, shebang + output.replace(/^#![^\n]*(?:\n|$)/, ""));
1976
+ const output = (await readFile(outfilePath, "utf8")).replace(/^#![^\n]*(?:\n|$)/, "");
1977
+ if (target === "node" && output.startsWith("// @bun")) throw new Error(`Build failed for ${outfilePath}: Bun emitted Bun-only code for a Node bundle because the entry reached it starting with "#!/usr/bin/env bun".\n Remove that line from ${entryPath}, or from what a crust.bunPlugins onLoad hook returns for it.`);
1978
+ await writeFile(outfilePath, `#!/usr/bin/env ${target}\n${output}`);
3148
1979
  if (process.platform !== "win32") await chmod(outfilePath, 493);
3149
1980
  }
3150
- async function execDenoBuild(entryPath, outfilePath, target, cwd, runner = resolveDenoBuildRunner()) {
3151
- await runBuildProcess(runner, createDenoCompileArgs(entryPath, outfilePath, target), outfilePath, cwd);
3152
- }
3153
- /**
3154
- * Bundle a Deno runtime package entry with native `deno bundle`, which resolves
3155
- * dependencies through the project's own Deno setup (deno.json imports,
3156
- * package.json with its installed node_modules) and inlines them into one file.
3157
- *
3158
- * Bundle the declared command directly so Deno preserves its `import.meta.main`
3159
- * semantics without making dependencies main. Prepend the packaged-build marker
3160
- * (`PACKAGED_BUILD_KEY`) to the bundled code so it runs before the inlined
3161
- * application graph. The output has no shebang or permission flags: consumers
3162
- * choose grants with `deno run` / `deno install`.
3163
- * No env files or plugins: `deno bundle` has no PUBLIC_* filter or Bun plugins.
3164
- */
3165
- async function execDenoPackageBuild(entryPath, outfilePath, cwd, runner = resolveDenoBuildRunner()) {
3166
- await runBuildProcess(runner, [
3167
- "bundle",
3168
- "--platform=deno",
3169
- "--packages=bundle",
3170
- "--output",
3171
- outfilePath,
3172
- resolve(cwd, entryPath)
3173
- ], outfilePath, cwd);
3174
- const output = await readFile(outfilePath, "utf8");
3175
- await writeFile(outfilePath, `globalThis[Symbol.for(${JSON.stringify(PACKAGED_BUILD_KEY)})] = true;\n` + output.replace(/^#![^\n]*(?:\n|$)/, ""));
3176
- }
3177
- //#endregion
3178
- //#region src/launcher.ts
1981
+ // src/launcher.ts
1982
+ /** Node.js the launcher needs for `process.execve`: the same floor as every Crust package. */
1983
+ const LAUNCHER_NODE_RANGE = ">=24";
3179
1984
  /** Platform binary filename: `<command>-<target>`, `.exe` on Windows. */
3180
1985
  function binaryFilename(command, target) {
3181
1986
  return `${command}-${target.target}${target.os === "win32" ? ".exe" : ""}`;
@@ -3190,18 +1995,34 @@ function generateLauncher(command, targets) {
3190
1995
  const supportedPlatforms = targets.map((target) => target.targetAlias).join(", ");
3191
1996
  return `#!/usr/bin/env node
3192
1997
  // Auto-generated by crust build -- do not edit
3193
- import { spawn } from "node:child_process";
3194
- import { chmodSync, existsSync } from "node:fs";
3195
- import { dirname, resolve } from "node:path";
3196
- import process from "node:process";
3197
- import { fileURLToPath } from "node:url";
3198
-
3199
1998
  const NAME = ${JSON.stringify(command)};
1999
+ if (typeof process.getBuiltinModule !== "function" ||
2000
+ (process.platform !== "win32" && typeof process.execve !== "function")) {
2001
+ console.error("[" + NAME + "] Requires Node.js ${LAUNCHER_NODE_RANGE} (found " + process.version + ").");
2002
+ process.exit(1);
2003
+ }
2004
+
2005
+ // Builtins come from process.getBuiltinModule (cheaper than imports) and never from
2006
+ // node:process: importing it touches stdio, which leaves the shared terminal and pipes
2007
+ // non-blocking for the binary after execve.
2008
+ const { chmodSync, closeSync, existsSync, openSync, readSync } = process.getBuiltinModule("node:fs");
2009
+ const { resolve } = process.getBuiltinModule("node:path");
2010
+
3200
2011
  const PLATFORMS = ${JSON.stringify(targetMap, null, " ")};
3201
- const dir = dirname(fileURLToPath(import.meta.url));
2012
+ const dir = import.meta.dirname;
3202
2013
 
3203
- // Same check as Bun's own npm installer: glibc reports its version, musl does not.
3204
2014
  function isMusl() {
2015
+ // The first 2 KB of Node's ELF image hold its interpreter path (detect-libc reads the same
2016
+ // span), which names its libc without the milliseconds a diagnostic report costs on every start.
2017
+ try {
2018
+ const fd = openSync("/proc/self/exe", "r");
2019
+ const head = Buffer.alloc(2048);
2020
+ const interpreter = head.toString("latin1", 0, readSync(fd, head, 0, head.length, 0));
2021
+ closeSync(fd);
2022
+ if (interpreter.includes("/ld-musl-")) return true;
2023
+ if (interpreter.includes("/ld-linux-")) return false;
2024
+ } catch {}
2025
+ // Same check as Bun's own npm installer: glibc reports its version, musl does not.
3205
2026
  try {
3206
2027
  const report = process.report?.getReport();
3207
2028
  if (report?.header) return report.header.glibcVersionRuntime === undefined;
@@ -3239,39 +2060,55 @@ if (!binPath) {
3239
2060
  \tprocess.exit(1);
3240
2061
  }
3241
2062
 
3242
- if (process.platform !== "win32") {
2063
+ if (process.platform === "win32") {
2064
+ \tlaunchWindows(binPath);
2065
+ } else {
3243
2066
  \ttry {
3244
2067
  \t\tchmodSync(binPath, 0o755);
3245
2068
  \t} catch {
3246
- \t\t// Ignore permission adjustment failures and let spawn surface real errors.
2069
+ \t\t// Ignore permission adjustment failures and let execve surface real errors.
3247
2070
  \t}
2071
+ \t// Become the binary: one process, so signals and exit status reach it with no relay.
2072
+ \t// Node ignores SIGPIPE and SIGXFSZ, and ignored signals survive execve; handled ones reset.
2073
+ \t// Ignores Node inherited (nohup's SIGHUP) were reset at its startup, so none pass on.
2074
+ \tprocess.on("SIGPIPE", () => {});
2075
+ \tprocess.on("SIGXFSZ", () => {});
2076
+ \t// Early execve versions (e.g. Node 24.0.0) default to an empty environment.
2077
+ \tprocess.execve(binPath, [binPath, ...process.argv.slice(2)], process.env);
3248
2078
  }
3249
2079
 
3250
- const child = spawn(binPath, process.argv.slice(2), {
3251
- \tstdio: "inherit",
3252
- });
3253
-
3254
- child.on("error", (error) => {
3255
- \tconsole.error("[" + NAME + "] Failed to launch binary: " + error.message);
3256
- \tprocess.exit(1);
3257
- });
3258
-
3259
- child.on("exit", (code, signal) => {
3260
- \tif (signal) {
3261
- \t\ttry {
3262
- \t\t\tprocess.kill(process.pid, signal);
3263
- \t\t} catch {
3264
- \t\t\tprocess.exit(1);
2080
+ // Windows has no execve. Its console delivers Ctrl-C to the binary directly, and
2081
+ // child.kill("SIGINT") would hard-terminate it, so SIGINT is never forwarded.
2082
+ function launchWindows(binPath) {
2083
+ \tconst { spawn } = process.getBuiltinModule("node:child_process");
2084
+ \tconst child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
2085
+ \tconst forward = (signal) => child.kill(signal);
2086
+ \tconst ignore = () => {};
2087
+ \tprocess.on("SIGINT", ignore);
2088
+ \tprocess.on("SIGTERM", forward);
2089
+ \tprocess.on("SIGHUP", forward);
2090
+ \tchild.on("error", (error) => {
2091
+ \t\tconsole.error("[" + NAME + "] Failed to launch binary: " + error.message);
2092
+ \t\tprocess.exit(1);
2093
+ \t});
2094
+ \tchild.on("exit", (code, signal) => {
2095
+ \t\tprocess.off("SIGINT", ignore);
2096
+ \t\tprocess.off("SIGTERM", forward);
2097
+ \t\tprocess.off("SIGHUP", forward);
2098
+ \t\tif (signal) {
2099
+ \t\t\ttry {
2100
+ \t\t\t\tprocess.kill(process.pid, signal);
2101
+ \t\t\t} catch {
2102
+ \t\t\t\tprocess.exit(1);
2103
+ \t\t\t}
2104
+ \t\t\treturn;
3265
2105
  \t\t}
3266
- \t\treturn;
3267
- \t}
3268
-
3269
- \tprocess.exit(code ?? 0);
3270
- });
2106
+ \t\tprocess.exit(code ?? 0);
2107
+ \t});
2108
+ }
3271
2109
  `;
3272
2110
  }
3273
- //#endregion
3274
- //#region src/distribute.ts
2111
+ // src/distribute.ts
3275
2112
  /** Project-relative directory that `crust build` owns: wiped per build, read by `crust publish`. */
3276
2113
  const CRUST_DIR = ".crust";
3277
2114
  /**
@@ -3314,8 +2151,14 @@ function platformBinMap(commands, target) {
3314
2151
  return Object.fromEntries(commands.map((command) => [command, `bin/${binaryFilename(command, target)}`]));
3315
2152
  }
3316
2153
  function buildDistributionRootPackageJson(metadata, commands, targets, { artifactDirs, manPages }) {
2154
+ const { engines } = metadata.rootPackageJson;
2155
+ const launcherEngines = targets.length > 0 && metadata.exports === void 0 ? {
2156
+ ...engines !== void 0 && isJsonObject(engines) ? engines : {},
2157
+ node: LAUNCHER_NODE_RANGE
2158
+ } : void 0;
3317
2159
  return {
3318
2160
  ...metadata.rootPackageJson,
2161
+ ...launcherEngines ? { engines: launcherEngines } : {},
3319
2162
  name: metadata.rootPackageName,
3320
2163
  version: metadata.version,
3321
2164
  type: "module",
@@ -3456,7 +2299,7 @@ function resolveDistributionMetadata(pkgJson, external) {
3456
2299
  version: pkgJson.version,
3457
2300
  rootPackageJson: pickRootMetadata(pkgJson),
3458
2301
  sourceType: pkgJson.type,
3459
- ...pkgJson.exports !== void 0 ? { exports: pkgJson.exports } : {},
2302
+ ...pkgJson.exports !== void 0 && pkgJson.exports !== null ? { exports: pkgJson.exports } : {},
3460
2303
  ...pkgJson.peerDependencies !== void 0 ? validatePeerDependencies(pkgJson.peerDependencies, pkgJson.peerDependenciesMeta) : {},
3461
2304
  ...external.length > 0 ? { dependencies: externalDependencies(pkgJson, external) } : {}
3462
2305
  };
@@ -3639,14 +2482,12 @@ async function runDistributeBuild(plan, distribution, io, build) {
3639
2482
  io.stdout(`\n${dim("Manifest:")} ${manifestPath}`);
3640
2483
  return produced;
3641
2484
  }
3642
- //#endregion
3643
- //#region package.json
2485
+ // package.json
3644
2486
  var optionalDependencies = {
3645
- "@tsdown/exe": "0.23.1",
3646
- "tsdown": "0.23.1"
2487
+ "@tsdown/exe": "0.23.4",
2488
+ "tsdown": "0.23.4"
3647
2489
  };
3648
- //#endregion
3649
- //#region src/node-exe.ts
2490
+ // src/node-exe.ts
3650
2491
  /**
3651
2492
  * Crust's own optional dependencies that build Node standalone binaries.
3652
2493
  * Crust's staged root and platform packages declare them
@@ -3802,7 +2643,7 @@ await build({
3802
2643
  // Bun leaves only Node built-ins as imports; fail on anything else, which
3803
2644
  // an executable shipping no node_modules could not resolve.
3804
2645
  deps: { alwaysBundle: () => true, onlyBundle: false, onlyImport: [] },
3805
- exe: { outDir: options.exeDir, fileName: "app", targets: [options.target] },
2646
+ exe: { outDir: options.exeDir, fileName: "app", targets: [options.target], seaConfig: { useCodeCache: options.useCodeCache } },
3806
2647
  });
3807
2648
  `;
3808
2649
  }
@@ -3824,7 +2665,7 @@ await build({
3824
2665
  * signature) after a successful build
3825
2666
  * @throws {Error} If the build fails
3826
2667
  */
3827
- async function execNodeBinaryBuild(entryPath, outfilePath, target, settings, compiler, bunRunner, onWarning = () => {}) {
2668
+ async function execNodeBinaryBuild(entryPath, outfilePath, target, settings, compiler, bunRunner, onWarning = () => {}, useCodeCache = false) {
3828
2669
  const { cwd, minify } = settings;
3829
2670
  const workDir = await mkdtemp(join(tmpdir(), "crust-node-exe-"));
3830
2671
  try {
@@ -3843,7 +2684,8 @@ async function execNodeBinaryBuild(entryPath, outfilePath, target, settings, com
3843
2684
  bundleDir: join(workDir, "bundle"),
3844
2685
  exeDir,
3845
2686
  minify,
3846
- target: nodeExeTarget(target, compiler.version)
2687
+ target: nodeExeTarget(target, compiler.version),
2688
+ useCodeCache
3847
2689
  }));
3848
2690
  const output = await runBuildProcess(compiler.runner, [scriptPath], outfilePath, cwd);
3849
2691
  const produced = await readdir(exeDir);
@@ -3861,12 +2703,13 @@ async function execNodeBinaryBuild(entryPath, outfilePath, target, settings, com
3861
2703
  }
3862
2704
  function createNodeExeProvisionScript(tsdownPackageJson, targets) {
3863
2705
  return `// Generated by crust build to provision Node binaries for targets; deleted when it exits.
3864
- import { realpathSync } from "node:fs";
2706
+ import { readFileSync, realpathSync, statSync } from "node:fs";
3865
2707
  import { createRequire } from "node:module";
3866
2708
  import { pathToFileURL } from "node:url";
3867
2709
 
3868
2710
  const exe = createRequire(realpathSync(${JSON.stringify(tsdownPackageJson)})).resolve("@tsdown/exe");
3869
- const { resolveNodeBinary } = await import(pathToFileURL(exe).href);
2711
+ const { resolveNodeBinary, getCachedBinaryPath } = await import(pathToFileURL(exe).href);
2712
+ const useCodeCache = {};
3870
2713
  for (const { name, target } of ${JSON.stringify(targets)}) {
3871
2714
  try {
3872
2715
  await resolveNodeBinary(target, {});
@@ -3874,7 +2717,23 @@ for (const { name, target } of ${JSON.stringify(targets)}) {
3874
2717
  console.error(name + ": " + error.message + (error.cause?.message ? "\\n" + error.cause.message : ""));
3875
2718
  process.exit(1);
3876
2719
  }
3877
- }
2720
+ // tsdown runs --build-sea with this Node, which writes the code cache; V8
2721
+ // rejects it (with a warning on every start) unless the embedded Node is this
2722
+ // exact binary with the same V8 flags, so another platform's or a distro build
2723
+ // of the same version, or NODE_OPTIONS such as --jitless, gets none. Decided
2724
+ // once per build: comparing reads both ~150 MB binaries.
2725
+ const embedded = getCachedBinaryPath(target);
2726
+ const host = (process.platform === "win32" ? "win" : process.platform) + "-" + process.arch;
2727
+ useCodeCache[name] = process.env.NODE_OPTIONS
2728
+ ? "NODE_OPTIONS is set"
2729
+ : target.platform + "-" + target.arch !== host
2730
+ ? "cross-built"
2731
+ : statSync(embedded, { throwIfNoEntry: false })?.size === statSync(process.execPath).size &&
2732
+ readFileSync(embedded).equals(readFileSync(process.execPath))
2733
+ ? true
2734
+ : "the build's node is not the official v" + target.nodeVersion + " binary";
2735
+ }
2736
+ console.log(JSON.stringify(useCodeCache));
3878
2737
  `;
3879
2738
  }
3880
2739
  /**
@@ -3887,6 +2746,9 @@ for (const { name, target } of ${JSON.stringify(targets)}) {
3887
2746
  * download, or a checksum mismatch therefore fails here, before `.crust/` is
3888
2747
  * replaced; cached targets need nothing.
3889
2748
  *
2749
+ * @returns Per target, `true` when {@link execNodeBinaryBuild} may embed a V8 code
2750
+ * cache (the selected Node is byte-identical to the binary the target embeds and
2751
+ * `NODE_OPTIONS` is unset), or the reason it may not
3890
2752
  * @throws {Error} If a target cannot be provisioned
3891
2753
  */
3892
2754
  async function provisionNodeExeTargets(targets, cwd, compiler) {
@@ -3899,6 +2761,16 @@ async function provisionNodeExeTargets(targets, cwd, compiler) {
3899
2761
  throw new Error(`Could not provision the Node ${compiler.version} binary that node standalone binaries embed${output ? `:\n${output}` : "."}\n tsdown downloads each target's official Node archive once and unpacks it with tar (GNU tar also needs xz for linux targets), or unzip for win targets on non-Windows hosts.
3900
2762
  Install the missing tool, or build only targets this machine can provision (--target).`);
3901
2763
  }
2764
+ const decided = JSON.parse(provision.stdout.trim().split("\n").at(-1) || "{}");
2765
+ return isJsonObject(decided) ? decided : {};
2766
+ }
2767
+ /** The build log's `Code cache:` value: `on`, `off (reason)`, or per-target groups when they differ. */
2768
+ function formatCodeCache(decisions) {
2769
+ const groups = /* @__PURE__ */ new Map();
2770
+ for (const [target, decision] of Object.entries(decisions)) groups.set(decision, [...groups.get(decision) ?? [], target]);
2771
+ const entries = [...groups];
2772
+ if (entries.length === 1) return entries[0][0] === true ? "on" : `off (${entries[0][0]})`;
2773
+ return entries.map(([decision, targets]) => decision === true ? `on for ${targets.join(", ")}` : `off for ${targets.join(", ")} (${decision})`).join("; ");
3902
2774
  }
3903
2775
  /** Runs a generated `<name>.mjs` with `runner` from a temporary directory that is removed afterwards. */
3904
2776
  async function runGeneratedScript(runner, name, source, cwd) {
@@ -3918,9 +2790,7 @@ async function runGeneratedScript(runner, name, source, cwd) {
3918
2790
  });
3919
2791
  }
3920
2792
  }
3921
- //#endregion
3922
- //#region ../core/dist/index.js
3923
- /** Mint an Extension identity from any non-blank, trimmed string. */
2793
+ // ../core/dist/index.js
3924
2794
  function defineExtensionId(id) {
3925
2795
  if (!id || id !== id.trim()) throw new CrustError("DEFINITION", "Extension id must be a non-empty, trimmed string", {
3926
2796
  subject: "extension",
@@ -3928,15 +2798,8 @@ function defineExtensionId(id) {
3928
2798
  });
3929
2799
  return id;
3930
2800
  }
3931
- //#endregion
3932
- //#region src/snapshot.ts
2801
+ // src/snapshot.ts
3933
2802
  const SNAPSHOT_TIMEOUT_MS = 3e4;
3934
- const SNAPSHOT_FORWARDED_SIGNALS = [
3935
- "SIGINT",
3936
- "SIGTERM",
3937
- "SIGHUP",
3938
- "SIGQUIT"
3939
- ];
3940
2803
  function isBuildReport(value) {
3941
2804
  return isJsonObject(value) && Array.isArray(value.extensions) && value.extensions.every((extension) => isJsonObject(extension) && typeof extension.id === "string" && Array.isArray(extension.files) && extension.files.every((file) => typeof file === "string"));
3942
2805
  }
@@ -3997,27 +2860,18 @@ async function buildEntrypoint(entryPath, outDir, envFiles, io, cwd, runner = re
3997
2860
  };
3998
2861
  });
3999
2862
  const timer = setTimeout(() => stop(`Command Snapshot preparation timed out after ${timeoutMs / 1e3}s.\n An Extension build hook may be hanging. Use --no-validate to skip entry preparation and build hooks.`), timeoutMs);
4000
- const onSignal = (signal) => {
4001
- removeLifetimeListeners();
4002
- stop(`Command Snapshot preparation was interrupted by ${signal}.`);
4003
- if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
4004
- };
4005
2863
  const killRemainingGroup = () => {
4006
2864
  try {
4007
2865
  killProcessTree(proc);
4008
2866
  } catch {}
4009
2867
  };
4010
- const removeLifetimeListeners = () => {
4011
- for (const signal of SNAPSHOT_FORWARDED_SIGNALS) process.removeListener(signal, onSignal);
4012
- process.removeListener("exit", killRemainingGroup);
4013
- };
4014
- if (detached) {
4015
- for (const signal of SNAPSHOT_FORWARDED_SIGNALS) process.prependListener(signal, onSignal);
4016
- process.on("exit", killRemainingGroup);
4017
- }
2868
+ const release = detached ? bindToProcessLifetime({
2869
+ onSignal: (signal) => stop(`Command Snapshot preparation was interrupted by ${signal}.`),
2870
+ onExit: killRemainingGroup
2871
+ }) : () => {};
4018
2872
  const [rawStderr, [exitCode]] = await Promise.race([Promise.all([text(proc.stderr), once(proc, "close")]), stopped]).finally(() => {
4019
2873
  clearTimeout(timer);
4020
- removeLifetimeListeners();
2874
+ release();
4021
2875
  if (detached) killRemainingGroup();
4022
2876
  });
4023
2877
  const stderr = rawStderr.trim();
@@ -4075,8 +2929,7 @@ async function buildEntrypoint(entryPath, outDir, envFiles, io, cwd, runner = re
4075
2929
  });
4076
2930
  }
4077
2931
  }
4078
- //#endregion
4079
- //#region src/build.ts
2932
+ // src/build.ts
4080
2933
  function readUserPackageJson(cwd) {
4081
2934
  const packageJsonPath = join(cwd, "package.json");
4082
2935
  if (!existsSync(packageJsonPath)) throw new Error(`package.json not found in ${cwd}\n crust build requires a package.json with name and version fields.`);
@@ -4165,7 +3018,6 @@ const NON_DEPENDENCY_SECTIONS = [
4165
3018
  function validateExternal(external, pkg, runtime, artifact) {
4166
3019
  if (external.length === 0) return;
4167
3020
  if (artifact === "binary") throw new Error("package.json crust.external is not supported for standalone binaries (artifact binary).\n A binary bundles every dependency; remove crust.external or use artifact package.");
4168
- if (runtime === "deno") throw new Error("package.json crust.external is not supported with the deno runtime.\n crust.external applies to Node and Bun runtime packages; remove crust.external or set crust.runtime to node or bun.");
4169
3021
  const dependencies = pkg.dependencies !== void 0 && isJsonObject(pkg.dependencies) ? pkg.dependencies : {};
4170
3022
  const rejectCrustPackage = (quoted, detail) => {
4171
3023
  throw new Error(`package.json crust.external cannot name ${quoted}${detail}.\n Crust packages must stay bundled: core detects a packaged build through a define the bundler injects, and an external Crust package would load a second copy of core.`);
@@ -4186,23 +3038,17 @@ function validateExternal(external, pkg, runtime, artifact) {
4186
3038
  if (aliased !== void 0) rejectCrustPackage(quoted, ` (an alias of ${aliased})`);
4187
3039
  }
4188
3040
  }
4189
- const DENO_CONFIG_FILES = ["deno.json", "deno.jsonc"];
4190
3041
  /**
4191
3042
  * package.json `crust.runtime` > inference > Bun. Inference uses only
4192
- * unambiguous signals: a Deno config file, or `@types/node` without
4193
- * `@types/bun`. Lockfiles say which package manager installed dependencies,
4194
- * not which runtime runs the CLI, so they are not consulted.
3043
+ * unambiguous signals: `@types/node` without `@types/bun`. Lockfiles say which
3044
+ * package manager installed dependencies, not which runtime runs the CLI, so
3045
+ * they are not consulted.
4195
3046
  */
4196
- function resolveBuildRuntime(pkg, config, cwd) {
3047
+ function resolveBuildRuntime(pkg, config) {
4197
3048
  if (config.runtime !== void 0) return {
4198
3049
  runtime: config.runtime,
4199
3050
  source: "from package.json"
4200
3051
  };
4201
- const denoConfig = DENO_CONFIG_FILES.find((file) => existsSync(join(cwd, file)));
4202
- if (denoConfig) return {
4203
- runtime: "deno",
4204
- source: `inferred from ${denoConfig}`
4205
- };
4206
3052
  if (hasDependency(pkg, "@types/node") && !hasDependency(pkg, "@types/bun")) return {
4207
3053
  runtime: "node",
4208
3054
  source: "inferred from @types/node"
@@ -4285,30 +3131,18 @@ function resolveBinEntries(cwd, pkg) {
4285
3131
  };
4286
3132
  });
4287
3133
  }
4288
- /** What earlier releases built for each runtime without being asked; quoted in the migration error. */
4289
- const IMPLICIT_ARTIFACTS = {
4290
- bun: "binary",
4291
- deno: "binary",
4292
- node: "package"
4293
- };
4294
3134
  /** `option` (the `artifact` build option or `--artifact`) > package.json `crust.artifact`; no default. */
4295
- function resolveArtifact(option, config, runtime) {
3135
+ function resolveArtifact(option, config) {
4296
3136
  if (option !== void 0 && !isArtifactKind(option)) throw new Error(`Invalid artifact ${JSON.stringify(option)}. Valid artifacts: ${ARTIFACT_KINDS.join(", ")}`);
4297
3137
  const artifact = option ?? config.artifact;
4298
- if (artifact === void 0) {
4299
- const previous = IMPLICIT_ARTIFACTS[runtime];
4300
- throw new Error(`crust build needs an artifact kind: pass --artifact package|binary or set package.json "crust": { "artifact": "package" | "binary" }.
4301
- package: one JavaScript bundle per command that runs on the consumer's installed runtime.
4302
- binary: standalone executables in per-platform npm packages behind a Node launcher.
4303
- Earlier versions built a ${previous} for the ${runtime} runtime implicitly; set "artifact": "${previous}" to keep that output.`);
4304
- }
3138
+ if (artifact === void 0) throw new Error("crust build needs an artifact kind: pass --artifact package|binary or set package.json \"crust\": { \"artifact\": \"package\" | \"binary\" }.\n package: one JavaScript bundle per command that runs on the consumer's installed runtime.\n binary: standalone executables in per-platform npm packages behind a Node launcher.");
4305
3139
  return artifact;
4306
3140
  }
4307
3141
  function planBuild(options, cwd) {
4308
3142
  const userPackageJson = readUserPackageJson(cwd);
4309
3143
  const config = readCrustConfig(userPackageJson);
4310
- const { runtime, source: runtimeSource } = resolveBuildRuntime(userPackageJson, config, cwd);
4311
- const artifact = resolveArtifact(options.artifact, config, runtime);
3144
+ const { runtime, source: runtimeSource } = resolveBuildRuntime(userPackageJson, config);
3145
+ const artifact = resolveArtifact(options.artifact, config);
4312
3146
  const entries = resolveBinEntries(cwd, userPackageJson);
4313
3147
  validatePackageIdentity(userPackageJson, "package.json");
4314
3148
  const envFiles = resolveEnvFilePaths(cwd, options.envFiles);
@@ -4316,10 +3150,8 @@ function planBuild(options, cwd) {
4316
3150
  const external = config.external ?? [];
4317
3151
  if (artifact === "package" && options.targets?.length) throw new Error("--target cannot be used with runtime packages (artifact package).\n A runtime package is one portable JavaScript bundle; drop --target or use artifact binary.");
4318
3152
  if (artifact === "package" && config.targets !== void 0) throw new Error("package.json crust.targets is not supported for runtime packages (artifact package).\n A runtime package is one portable JavaScript bundle; remove crust.targets or set \"artifact\": \"binary\".");
4319
- const denoTool = artifact === "binary" ? "deno compile" : "deno bundle";
4320
- if (runtime === "deno" && options.minify) throw new Error(artifact === "binary" ? "--minify is not supported with the deno runtime.\n deno compile has no minification step; drop the flag." : "--minify is not supported with the deno runtime.\n crust does not minify Deno runtime packages; drop the flag.");
4321
- if (runtime === "deno" && envFiles.length > 0) throw new Error(artifact === "binary" ? "--env-file is not supported with the deno runtime.\n deno compile embeds every variable from the file into the binary — secrets included —\n with no PUBLIC_* filter. Load configuration at runtime instead (e.g. deno run --env-file)." : "--env-file is not supported with the deno runtime.\n deno bundle has no PUBLIC_* build-time constants. Load configuration at runtime instead (e.g. deno run --env-file).");
4322
- if (runtime === "deno" && bunPlugins.length > 0) throw new Error(`package.json crust.bunPlugins is not supported with the deno runtime.\n ${denoTool} has no Bun bundler; remove crust.bunPlugins or set crust.runtime to bun.`);
3153
+ const bunBinary = runtime === "bun" && artifact === "binary";
3154
+ if (options.bytecode && !bunBinary) throw new Error(artifact === "package" ? "--bytecode is not supported for runtime packages (artifact package).\n Bun bytecode only runs on the exact Bun version that built it, so it is embedded in bun binaries only; drop the flag." : `--bytecode is not supported with the ${runtime} runtime.\n Bytecode is Bun's JavaScriptCore format; Node binaries cannot use it. Drop the flag.`);
4323
3155
  if (runtime === "node" && artifact === "binary" && bunPlugins.length > 0) throw new Error("package.json crust.bunPlugins is not supported for node standalone binaries.\n Remove crust.bunPlugins, or use artifact package or the bun runtime.");
4324
3156
  validateExternal(external, userPackageJson, runtime, artifact);
4325
3157
  const stageDir = resolve(cwd, CRUST_DIR);
@@ -4335,7 +3167,8 @@ function planBuild(options, cwd) {
4335
3167
  outDir: join(stageDir, "artifacts"),
4336
3168
  stageDir,
4337
3169
  validate: options.validate ?? true,
4338
- minify: runtime === "deno" ? false : options.minify ?? true
3170
+ minify: options.minify ?? true,
3171
+ bytecode: bunBinary && (options.bytecode ?? true)
4339
3172
  };
4340
3173
  if (artifact === "package") return {
4341
3174
  ...common,
@@ -4349,17 +3182,11 @@ function planBuild(options, cwd) {
4349
3182
  artifact,
4350
3183
  targets: resolveTargets(BUN_TARGETS, targetInputs)
4351
3184
  };
4352
- if (runtime === "node") return {
4353
- ...common,
4354
- runtime,
4355
- artifact,
4356
- targets: resolveTargets(NODE_TARGETS, targetInputs)
4357
- };
4358
3185
  return {
4359
3186
  ...common,
4360
3187
  runtime,
4361
3188
  artifact,
4362
- targets: resolveTargets(DENO_TARGETS, targetInputs)
3189
+ targets: resolveTargets(NODE_TARGETS, targetInputs)
4363
3190
  };
4364
3191
  }
4365
3192
  /**
@@ -4368,20 +3195,10 @@ function planBuild(options, cwd) {
4368
3195
  * the selected compiler's own version (`resolveBinaryCompiler`, or
4369
3196
  * `resolveNodeBinaryCompiler` with tsdown's requirements for node, whose
4370
3197
  * targets' embedded Node binaries are also provisioned here); runtime
4371
- * packages are bundled by Bun (or, for Deno, by `deno bundle`) and embed no
4372
- * runtime, so engines stay a consumer requirement there and are not checked
4373
- * against the bundler.
3198
+ * packages are bundled by Bun and embed no runtime, so engines stay a consumer
3199
+ * requirement there and are not checked against the bundler.
4374
3200
  */
4375
3201
  async function selectCompilers(plan, io) {
4376
- if (plan.artifact === "package" && plan.runtime === "deno") {
4377
- const bundler = await resolveDenoPackageBundler(plan.cwd);
4378
- io.stdout(`${dim("Compiler:")} deno ${bundler.version} ${dim(`(${bundler.runner.command}, deno bundle)`)}`);
4379
- io.stderr("Deno runtime packages are experimental: they are bundled with deno bundle, which Deno marks experimental.");
4380
- return {
4381
- snapshotRunner: plan.validate ? resolveBunBuildRunner() : void 0,
4382
- stage: (reports) => runDistributeBuild(plan, { execute: (entry, outfile) => execDenoPackageBuild(entry, outfile, plan.cwd, bundler.runner) }, io, reports)
4383
- };
4384
- }
4385
3202
  if (plan.artifact === "package") {
4386
3203
  const runner = resolveBunBuildRunner();
4387
3204
  const target = plan.runtime === "bun" ? "bun" : "node";
@@ -4394,42 +3211,31 @@ async function selectCompilers(plan, io) {
4394
3211
  if (plan.runtime === "node") {
4395
3212
  const compiler = await resolveNodeBinaryCompiler(plan.userPackageJson, plan.cwd);
4396
3213
  printCompiler(compiler);
4397
- await provisionNodeExeTargets(plan.targets, plan.cwd, compiler);
3214
+ const useCodeCache = await provisionNodeExeTargets(plan.targets, plan.cwd, compiler);
3215
+ io.stdout(`${dim("Code cache:")} ${formatCodeCache(useCodeCache)}`);
4398
3216
  const bunRunner = resolveBunBuildRunner();
4399
3217
  const distribution = {
4400
3218
  table: NODE_TARGETS,
4401
3219
  targets: plan.targets,
4402
3220
  embeddedRuntimeVersion: compiler.version,
4403
- execute: (entry, outfile, target) => execNodeBinaryBuild(entry, outfile, target, plan, compiler, bunRunner, io.stderr)
3221
+ execute: (entry, outfile, target) => execNodeBinaryBuild(entry, outfile, target, plan, compiler, bunRunner, io.stderr, useCodeCache[target] === true)
4404
3222
  };
4405
3223
  return {
4406
3224
  snapshotRunner: bunRunner,
4407
3225
  stage: (reports) => runDistributeBuild(plan, distribution, io, reports)
4408
3226
  };
4409
3227
  }
4410
- const compiler = await resolveBinaryCompiler(plan.runtime, plan.userPackageJson, plan.cwd);
3228
+ const compiler = await resolveBinaryCompiler(plan.userPackageJson, plan.cwd);
4411
3229
  printCompiler(compiler);
4412
- if (plan.runtime === "bun") {
4413
- assertTargetsBuildableWithoutBun(plan.targets, compiler.runner);
4414
- const distribution = {
4415
- table: BUN_TARGETS,
4416
- targets: plan.targets,
4417
- embeddedRuntimeVersion: compiler.version,
4418
- execute: (entry, outfile, target) => execBuild(entry, outfile, target, plan, compiler.runner)
4419
- };
4420
- return {
4421
- snapshotRunner: compiler.runner,
4422
- stage: (reports) => runDistributeBuild(plan, distribution, io, reports)
4423
- };
4424
- }
3230
+ assertTargetsBuildableWithoutBun(plan.targets, compiler.runner);
4425
3231
  const distribution = {
4426
- table: DENO_TARGETS,
3232
+ table: BUN_TARGETS,
4427
3233
  targets: plan.targets,
4428
3234
  embeddedRuntimeVersion: compiler.version,
4429
- execute: (entry, outfile, target) => execDenoBuild(entry, outfile, target, plan.cwd, compiler.runner)
3235
+ execute: (entry, outfile, target) => execBuild(entry, outfile, target, plan, compiler.runner)
4430
3236
  };
4431
3237
  return {
4432
- snapshotRunner: plan.validate ? resolveBunBuildRunner() : void 0,
3238
+ snapshotRunner: compiler.runner,
4433
3239
  stage: (reports) => runDistributeBuild(plan, distribution, io, reports)
4434
3240
  };
4435
3241
  }
@@ -4469,10 +3275,8 @@ async function prepareEntries(plan, io, runner) {
4469
3275
  * come from `bin`; the runtime, artifact, Bun plugins, extra directories, and
4470
3276
  * external dependencies from package.json `crust`. Bundling and Command
4471
3277
  * Snapshots run in bun subprocesses (bun on PATH, or the running Bun
4472
- * executable), so this works under Node as well when Bun is installed; Deno
4473
- * binaries and Deno runtime packages (experimental, deno 2.5.0 or newer) need
4474
- * deno on PATH, and Node binaries a node on PATH that Crust's tsdown can build
4475
- * executables with.
3278
+ * executable), so this works under Node as well when Bun is installed; Node
3279
+ * binaries need a node on PATH that Crust's tsdown can build executables with.
4476
3280
  *
4477
3281
  * Throws on any failure. Planning and compiler-selection failures (bad
4478
3282
  * options or package.json, a missing or hung compiler, an engines mismatch) leave the
@@ -4500,6 +3304,10 @@ async function build(options = {}) {
4500
3304
  const plan = planBuild(options, cwd);
4501
3305
  io.stdout(`${dim("Runtime:")} ${plan.runtime} ${dim(`(${plan.runtimeSource})`)}`);
4502
3306
  io.stdout(`${dim("Artifact:")} ${plan.artifact}`);
3307
+ for (const { entryPath } of plan.entries) {
3308
+ const named = /^#![^\n]*\b(bun|node)\b/.exec(readFileSync(entryPath, "utf8"))?.[1];
3309
+ if (named !== void 0 && named !== plan.runtime) io.stderr(`${yellow("Warning:")} ${entryPath} starts with a ${named} shebang but crust.runtime is "${plan.runtime}". The build targets ${plan.runtime}; a linked command (npm link) still starts ${named}.`);
3310
+ }
4503
3311
  const compilers = await selectCompilers(plan, io);
4504
3312
  rmSync(plan.stageDir, {
4505
3313
  recursive: true,
@@ -4516,5 +3324,4 @@ async function build(options = {}) {
4516
3324
  rmdirSync(lockDir);
4517
3325
  }
4518
3326
  }
4519
- //#endregion
4520
3327
  export { build };