@poe-platform/safe-bash 0.1.441 → 0.1.443

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.
Files changed (167) hide show
  1. package/dist/safe-bash/browser.js +111 -69
  2. package/dist/safe-bash/browser.js.map +4 -4
  3. package/dist/safe-bash/commands/archive/index.d.ts.map +1 -1
  4. package/dist/safe-bash/commands/archive/index.js +2 -1
  5. package/dist/safe-bash/commands/archive/index.js.map +1 -1
  6. package/dist/safe-bash/commands/archive/internal.d.ts.map +1 -1
  7. package/dist/safe-bash/commands/archive/internal.js +2 -1
  8. package/dist/safe-bash/commands/archive/internal.js.map +1 -1
  9. package/dist/safe-bash/commands/archive/stream.d.ts.map +1 -1
  10. package/dist/safe-bash/commands/archive/stream.js +11 -4
  11. package/dist/safe-bash/commands/archive/stream.js.map +1 -1
  12. package/dist/safe-bash/commands/bytes/checksums/index.d.ts.map +1 -1
  13. package/dist/safe-bash/commands/bytes/checksums/index.js +7 -6
  14. package/dist/safe-bash/commands/bytes/checksums/index.js.map +1 -1
  15. package/dist/safe-bash/commands/bytes/compression/errors.d.ts +2 -0
  16. package/dist/safe-bash/commands/bytes/compression/errors.d.ts.map +1 -0
  17. package/dist/safe-bash/commands/bytes/compression/errors.js +19 -0
  18. package/dist/safe-bash/commands/bytes/compression/errors.js.map +1 -0
  19. package/dist/safe-bash/commands/bytes/compression/files.d.ts.map +1 -1
  20. package/dist/safe-bash/commands/bytes/compression/files.js +5 -2
  21. package/dist/safe-bash/commands/bytes/compression/files.js.map +1 -1
  22. package/dist/safe-bash/commands/bytes/compression/gunzip.d.ts.map +1 -1
  23. package/dist/safe-bash/commands/bytes/compression/gunzip.js +20 -12
  24. package/dist/safe-bash/commands/bytes/compression/gunzip.js.map +1 -1
  25. package/dist/safe-bash/commands/bytes/compression/index.d.ts.map +1 -1
  26. package/dist/safe-bash/commands/bytes/compression/index.js +2 -1
  27. package/dist/safe-bash/commands/bytes/compression/index.js.map +1 -1
  28. package/dist/safe-bash/commands/bytes/compression/stream.d.ts.map +1 -1
  29. package/dist/safe-bash/commands/bytes/compression/stream.js +3 -1
  30. package/dist/safe-bash/commands/bytes/compression/stream.js.map +1 -1
  31. package/dist/safe-bash/commands/bytes/encoding/base.d.ts.map +1 -1
  32. package/dist/safe-bash/commands/bytes/encoding/base.js +3 -2
  33. package/dist/safe-bash/commands/bytes/encoding/base.js.map +1 -1
  34. package/dist/safe-bash/commands/bytes/encoding/shared.d.ts.map +1 -1
  35. package/dist/safe-bash/commands/bytes/encoding/shared.js +3 -2
  36. package/dist/safe-bash/commands/bytes/encoding/shared.js.map +1 -1
  37. package/dist/safe-bash/commands/bytes/encoding/xxd.d.ts.map +1 -1
  38. package/dist/safe-bash/commands/bytes/encoding/xxd.js +8 -7
  39. package/dist/safe-bash/commands/bytes/encoding/xxd.js.map +1 -1
  40. package/dist/safe-bash/commands/column/internal.d.ts.map +1 -1
  41. package/dist/safe-bash/commands/column/internal.js +2 -1
  42. package/dist/safe-bash/commands/column/internal.js.map +1 -1
  43. package/dist/safe-bash/commands/diff-patch/patch-gnu-paths.d.ts.map +1 -1
  44. package/dist/safe-bash/commands/diff-patch/patch-gnu-paths.js +2 -1
  45. package/dist/safe-bash/commands/diff-patch/patch-gnu-paths.js.map +1 -1
  46. package/dist/safe-bash/commands/diff-patch/patch.d.ts.map +1 -1
  47. package/dist/safe-bash/commands/diff-patch/patch.js +3 -2
  48. package/dist/safe-bash/commands/diff-patch/patch.js.map +1 -1
  49. package/dist/safe-bash/commands/diff-patch/shared.d.ts +2 -1
  50. package/dist/safe-bash/commands/diff-patch/shared.d.ts.map +1 -1
  51. package/dist/safe-bash/commands/diff-patch/shared.js +3 -2
  52. package/dist/safe-bash/commands/diff-patch/shared.js.map +1 -1
  53. package/dist/safe-bash/commands/du/budget.d.ts.map +1 -1
  54. package/dist/safe-bash/commands/du/budget.js +3 -1
  55. package/dist/safe-bash/commands/du/budget.js.map +1 -1
  56. package/dist/safe-bash/commands/du/du.d.ts.map +1 -1
  57. package/dist/safe-bash/commands/du/du.js +8 -7
  58. package/dist/safe-bash/commands/du/du.js.map +1 -1
  59. package/dist/safe-bash/commands/du/format.d.ts +2 -1
  60. package/dist/safe-bash/commands/du/format.d.ts.map +1 -1
  61. package/dist/safe-bash/commands/du/format.js +2 -1
  62. package/dist/safe-bash/commands/du/format.js.map +1 -1
  63. package/dist/safe-bash/commands/expr/command.d.ts.map +1 -1
  64. package/dist/safe-bash/commands/expr/command.js +3 -1
  65. package/dist/safe-bash/commands/expr/command.js.map +1 -1
  66. package/dist/safe-bash/commands/find.d.ts.map +1 -1
  67. package/dist/safe-bash/commands/find.js +2 -1
  68. package/dist/safe-bash/commands/find.js.map +1 -1
  69. package/dist/safe-bash/commands/html-to-markdown/index.d.ts.map +1 -1
  70. package/dist/safe-bash/commands/html-to-markdown/index.js +2 -1
  71. package/dist/safe-bash/commands/html-to-markdown/index.js.map +1 -1
  72. package/dist/safe-bash/commands/index.d.ts.map +1 -1
  73. package/dist/safe-bash/commands/index.js +2 -1
  74. package/dist/safe-bash/commands/index.js.map +1 -1
  75. package/dist/safe-bash/commands/internal.d.ts +2 -1
  76. package/dist/safe-bash/commands/internal.d.ts.map +1 -1
  77. package/dist/safe-bash/commands/internal.js +3 -2
  78. package/dist/safe-bash/commands/internal.js.map +1 -1
  79. package/dist/safe-bash/commands/move.d.ts.map +1 -1
  80. package/dist/safe-bash/commands/move.js +2 -1
  81. package/dist/safe-bash/commands/move.js.map +1 -1
  82. package/dist/safe-bash/commands/regex-execution/bounded-provider.d.ts.map +1 -1
  83. package/dist/safe-bash/commands/regex-execution/bounded-provider.js +14 -3
  84. package/dist/safe-bash/commands/regex-execution/bounded-provider.js.map +1 -1
  85. package/dist/safe-bash/commands/regex-execution/matching.d.ts.map +1 -1
  86. package/dist/safe-bash/commands/regex-execution/matching.js +12 -5
  87. package/dist/safe-bash/commands/regex-execution/matching.js.map +1 -1
  88. package/dist/safe-bash/commands/regex-execution/portable.d.ts.map +1 -1
  89. package/dist/safe-bash/commands/regex-execution/portable.js +1 -1
  90. package/dist/safe-bash/commands/regex-execution/portable.js.map +1 -1
  91. package/dist/safe-bash/commands/regex-execution/protocol.d.ts +3 -2
  92. package/dist/safe-bash/commands/regex-execution/protocol.d.ts.map +1 -1
  93. package/dist/safe-bash/commands/regex-execution/protocol.js +4 -3
  94. package/dist/safe-bash/commands/regex-execution/protocol.js.map +1 -1
  95. package/dist/safe-bash/commands/regex-execution/worker.js +6 -3
  96. package/dist/safe-bash/commands/regex-execution/worker.js.map +1 -1
  97. package/dist/safe-bash/commands/safejs/index.d.ts.map +1 -1
  98. package/dist/safe-bash/commands/safejs/index.js +24 -7
  99. package/dist/safe-bash/commands/safejs/index.js.map +1 -1
  100. package/dist/safe-bash/commands/search/options.d.ts +2 -1
  101. package/dist/safe-bash/commands/search/options.d.ts.map +1 -1
  102. package/dist/safe-bash/commands/search/options.js +2 -1
  103. package/dist/safe-bash/commands/search/options.js.map +1 -1
  104. package/dist/safe-bash/commands/search/shared.d.ts.map +1 -1
  105. package/dist/safe-bash/commands/search/shared.js +2 -1
  106. package/dist/safe-bash/commands/search/shared.js.map +1 -1
  107. package/dist/safe-bash/commands/split/names.d.ts.map +1 -1
  108. package/dist/safe-bash/commands/split/names.js +3 -2
  109. package/dist/safe-bash/commands/split/names.js.map +1 -1
  110. package/dist/safe-bash/commands/split/options.d.ts.map +1 -1
  111. package/dist/safe-bash/commands/split/options.js +17 -16
  112. package/dist/safe-bash/commands/split/options.js.map +1 -1
  113. package/dist/safe-bash/commands/split/split.d.ts.map +1 -1
  114. package/dist/safe-bash/commands/split/split.js +2 -1
  115. package/dist/safe-bash/commands/split/split.js.map +1 -1
  116. package/dist/safe-bash/commands/standard.d.ts.map +1 -1
  117. package/dist/safe-bash/commands/standard.js +2 -1
  118. package/dist/safe-bash/commands/standard.js.map +1 -1
  119. package/dist/safe-bash/commands/stream-format/rev.d.ts.map +1 -1
  120. package/dist/safe-bash/commands/stream-format/rev.js +2 -1
  121. package/dist/safe-bash/commands/stream-format/rev.js.map +1 -1
  122. package/dist/safe-bash/commands/table-text/internal.d.ts.map +1 -1
  123. package/dist/safe-bash/commands/table-text/internal.js +3 -2
  124. package/dist/safe-bash/commands/table-text/internal.js.map +1 -1
  125. package/dist/safe-bash/commands/tail-follow.d.ts.map +1 -1
  126. package/dist/safe-bash/commands/tail-follow.js +4 -3
  127. package/dist/safe-bash/commands/tail-follow.js.map +1 -1
  128. package/dist/safe-bash/commands/text-programs/shared.d.ts +2 -1
  129. package/dist/safe-bash/commands/text-programs/shared.d.ts.map +1 -1
  130. package/dist/safe-bash/commands/text-programs/shared.js +3 -2
  131. package/dist/safe-bash/commands/text-programs/shared.js.map +1 -1
  132. package/dist/safe-bash/commands/text.d.ts.map +1 -1
  133. package/dist/safe-bash/commands/text.js +2 -1
  134. package/dist/safe-bash/commands/text.js.map +1 -1
  135. package/dist/safe-bash/commands/tree/io.d.ts.map +1 -1
  136. package/dist/safe-bash/commands/tree/io.js +2 -4
  137. package/dist/safe-bash/commands/tree/io.js.map +1 -1
  138. package/dist/safe-bash/contracts/command.d.ts +5 -0
  139. package/dist/safe-bash/contracts/command.d.ts.map +1 -1
  140. package/dist/safe-bash/contracts/command.js +6 -1
  141. package/dist/safe-bash/contracts/command.js.map +1 -1
  142. package/dist/safe-bash/diagnostics.d.ts +5 -0
  143. package/dist/safe-bash/diagnostics.d.ts.map +1 -0
  144. package/dist/safe-bash/diagnostics.js +22 -0
  145. package/dist/safe-bash/diagnostics.js.map +1 -0
  146. package/dist/safe-bash/plugins/composition.d.ts.map +1 -1
  147. package/dist/safe-bash/plugins/composition.js +2 -1
  148. package/dist/safe-bash/plugins/composition.js.map +1 -1
  149. package/dist/safe-bash/portable.js +238 -150
  150. package/dist/safe-bash/portable.js.map +4 -4
  151. package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
  152. package/dist/safe-bash/shell/arithmetic.js +10 -9
  153. package/dist/safe-bash/shell/arithmetic.js.map +1 -1
  154. package/dist/safe-bash/shell/input.d.ts.map +1 -1
  155. package/dist/safe-bash/shell/input.js +2 -1
  156. package/dist/safe-bash/shell/input.js.map +1 -1
  157. package/dist/safe-bash/shell/runtime.d.ts +3 -1
  158. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  159. package/dist/safe-bash/shell/runtime.js +52 -38
  160. package/dist/safe-bash/shell/runtime.js.map +1 -1
  161. package/dist/safe-bash/shell/shell.d.ts.map +1 -1
  162. package/dist/safe-bash/shell/shell.js +5 -1
  163. package/dist/safe-bash/shell/shell.js.map +1 -1
  164. package/dist/safe-bash/shell/types.d.ts +3 -1
  165. package/dist/safe-bash/shell/types.d.ts.map +1 -1
  166. package/dist/safe-bash/shell/types.js.map +1 -1
  167. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { PublicDiagnostic, publicDiagnosticMessage } from "../diagnostics.js";
1
2
  import { writeDiagnostic } from "../escaping.js";
2
3
  import { cancelTurn, monotonicNow, registerYieldCheckpoint, scheduleTurn, yieldTurn } from "../contracts/yield.js";
3
4
  import { ACCESS_MODES, FsError, composeMiddleware, createBytePipe, pipeBytes, resolvePath, toByteSource, validateExitCode, writeText, } from "../contracts/index.js";
@@ -77,6 +78,7 @@ export function resolveLimits(...limits) {
77
78
  const budgetedSinks = new WeakMap();
78
79
  export class Budget {
79
80
  limits;
81
+ onInternalError;
80
82
  executionScope = Object.freeze({});
81
83
  pathLookup = new PathLookup();
82
84
  parsing;
@@ -92,8 +94,9 @@ export class Budget {
92
94
  #pipelineStages = 0;
93
95
  #fileSystemOperations = 0;
94
96
  #cpuStarted = monotonicNow();
95
- constructor(limits, signal) {
97
+ constructor(limits, signal, onInternalError) {
96
98
  this.limits = limits;
99
+ this.onInternalError = onInternalError;
97
100
  this.signal = signal ? AbortSignal.any([signal, this.controller.signal]) : this.controller.signal;
98
101
  this.parsing = new ParseBudget(limits.maxParseUnits, this.signal, error => this.controller.abort(error));
99
102
  this.values = new ValueArena(limits.maxExpansionBytes, limits.maxExpansionFields, () => this.signal.throwIfAborted(), limit => this.fail(limit));
@@ -539,11 +542,18 @@ function saturatedSum(left, right) {
539
542
  function errorCode(error) {
540
543
  return typeof error === "object" && error !== null && "code" in error ? String(error.code) : undefined;
541
544
  }
542
- function message(error) { return error instanceof Error ? error.message : String(error); }
543
- function filesystemDiagnostic(error, target) {
545
+ function message(error, onInternalError) {
546
+ if (error instanceof ExpansionFailure || error instanceof CommandFailure || error instanceof ArrayFailure || error instanceof BraceExpansionFailure)
547
+ return error.message;
548
+ return publicDiagnosticMessage(error, onInternalError);
549
+ }
550
+ function filesystemDiagnostic(error, target, onInternalError) {
544
551
  const descriptions = { ENOENT: "No such file or directory", EACCES: "Permission denied", EPERM: "Operation not permitted", ENOTDIR: "Not a directory", EISDIR: "Is a directory", ELOOP: "Too many levels of symbolic links", ENOSPC: "No space left on device", EROFS: "Read-only file system" };
545
552
  const description = descriptions[errorCode(error) ?? ""];
546
- return description ? `${target}: ${description}` : undefined;
553
+ if (!description)
554
+ return undefined;
555
+ publicDiagnosticMessage(error, onInternalError);
556
+ return `${target}: ${description}`;
547
557
  }
548
558
  function cdUtf8Width(codePoint) {
549
559
  return codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
@@ -583,7 +593,7 @@ class CdLookup {
583
593
  async charge(amount) {
584
594
  this.signal.throwIfAborted();
585
595
  if (amount > 8_388_608 - this.spent)
586
- throw new Error("cd: helper work limit exceeded");
596
+ throw new PublicDiagnostic("cd: helper work limit exceeded");
587
597
  while (amount > 0) {
588
598
  const step = Math.min(amount, 128 - this.spent % 128);
589
599
  this.spent += step;
@@ -605,9 +615,9 @@ class CdLookup {
605
615
  const codePoint = value.codePointAt(index);
606
616
  const width = cdUtf8Width(codePoint);
607
617
  if (bytes + width > 65_536)
608
- throw new Error(search ? "cd: CDPATH exceeds 65536 UTF-8 bytes" : "cd: path exceeds 65536 UTF-8 bytes");
618
+ throw new PublicDiagnostic(search ? "cd: CDPATH exceeds 65536 UTF-8 bytes" : "cd: path exceeds 65536 UTF-8 bytes");
609
619
  if (search && codePoint === 58 && ++slots > 4096)
610
- throw new Error("cd: CDPATH exceeds 4096 components");
620
+ throw new PublicDiagnostic("cd: CDPATH exceeds 4096 components");
611
621
  await this.charge(width);
612
622
  if (search && codePoint === 58) {
613
623
  components.push({ start, end: index, bytes: bytes - startBytes });
@@ -631,7 +641,7 @@ class CdLookup {
631
641
  const rawBytes = absolute ? targetBytes : component.startsWith("/") ? componentBytes + 1 + targetBytes
632
642
  : cwdBytes + 1 + (component ? componentBytes + 1 : 0) + targetBytes;
633
643
  if (rawBytes > 65_536)
634
- throw new Error("cd: path exceeds 65536 UTF-8 bytes");
644
+ throw new PublicDiagnostic("cd: path exceeds 65536 UTF-8 bytes");
635
645
  await this.charge(2 * rawBytes);
636
646
  const raw = absolute ? target : component.startsWith("/") ? `${component}/${target}`
637
647
  : component ? `${cwd}/${component}/${target}` : `${cwd}/${target}`;
@@ -639,7 +649,7 @@ class CdLookup {
639
649
  await this.scan(path);
640
650
  this.signal.throwIfAborted();
641
651
  if (++this.probes > 4097)
642
- throw new Error("cd: probe limit exceeded");
652
+ throw new PublicDiagnostic("cd: probe limit exceeded");
643
653
  await this.charge(1);
644
654
  this.signal.throwIfAborted();
645
655
  const stat = await fs.stat(path, { signal: this.signal });
@@ -812,7 +822,7 @@ class DirectoryStackWork {
812
822
  await this.flushOutput();
813
823
  }
814
824
  }
815
- const closedSink = { async write() { throw Object.assign(new Error("Bad file descriptor"), { code: "EBADF" }); } };
825
+ const closedSink = { async write() { throw Object.assign(new PublicDiagnostic("Bad file descriptor"), { code: "EBADF" }); } };
816
826
  const closedSource = { [Symbol.asyncIterator]() {
817
827
  let closed = false;
818
828
  let completion = Promise.resolve();
@@ -827,7 +837,7 @@ const closedSource = { [Symbol.asyncIterator]() {
827
837
  if (closed)
828
838
  return { done: true, value: undefined };
829
839
  closed = true;
830
- throw Object.assign(new Error("Bad file descriptor"), { code: "EBADF" });
840
+ throw Object.assign(new PublicDiagnostic("Bad file descriptor"), { code: "EBADF" });
831
841
  });
832
842
  },
833
843
  return(value) { return enqueue(async () => { closed = true; return { done: true, value: await value }; }); },
@@ -1307,7 +1317,7 @@ export class Runtime {
1307
1317
  if (arrayStore(state)?.get(name))
1308
1318
  throw new ArrayFailure(origin === "arithmetic" ? "indexed arithmetic is unsupported" : "indexed write requires prepared publication");
1309
1319
  if (state.readonlyVariables?.has(name))
1310
- throw new Error(`${name}: readonly variable`);
1320
+ throw new PublicDiagnostic(`${name}: readonly variable`);
1311
1321
  if (shellValueByteLength(value) > this.budget.limits.maxExpansionBytes)
1312
1322
  this.budget.fail("maxExpansionBytes");
1313
1323
  if (name === "OPTIND" && state.getopts?.integer && origin !== "arithmetic") {
@@ -1316,7 +1326,7 @@ export class Runtime {
1316
1326
  }
1317
1327
  catch (error) {
1318
1328
  this.rethrowArithmeticControl(error);
1319
- throw new ExpansionFailure(message(error));
1329
+ throw new ExpansionFailure(message(error, this.budget.onInternalError));
1320
1330
  }
1321
1331
  }
1322
1332
  publishVariable(state, name, value);
@@ -1339,7 +1349,7 @@ export class Runtime {
1339
1349
  }
1340
1350
  unsetVariable(state, name, internal = false) {
1341
1351
  if (state.readonlyVariables?.has(name))
1342
- throw new Error(`${name}: readonly variable`);
1352
+ throw new PublicDiagnostic(`${name}: readonly variable`);
1343
1353
  delete state.variables[name];
1344
1354
  state.exported.delete(name);
1345
1355
  if (name === "OPTIND" && !internal)
@@ -2140,7 +2150,7 @@ export class Runtime {
2140
2150
  }
2141
2151
  catch (error) {
2142
2152
  this.rethrowArithmeticControl(error);
2143
- throw new Error(`((: ${message(error)}`);
2153
+ throw new PublicDiagnostic(`((: ${message(error, this.budget.onInternalError)}`);
2144
2154
  }
2145
2155
  }
2146
2156
  if (command.kind === "subshell") {
@@ -2255,9 +2265,10 @@ export class Runtime {
2255
2265
  this.errexit(141, state, io);
2256
2266
  return 141;
2257
2267
  }
2268
+ const publicMessage = message(error, this.budget.onInternalError);
2258
2269
  const line = error instanceof ExpansionFailure ? error.line ?? io.diagnosticLine ?? 1 : io.diagnosticLine ?? 1;
2259
2270
  if (error instanceof NounsetFailure || error instanceof ParameterExpansionFailure) {
2260
- const detail = error instanceof ParameterExpansionFailure ? diagnostic ?? message(error) : message(error);
2271
+ const detail = error instanceof ParameterExpansionFailure ? diagnostic ?? publicMessage : publicMessage;
2261
2272
  try {
2262
2273
  await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${detail}\n`);
2263
2274
  }
@@ -2271,13 +2282,14 @@ export class Runtime {
2271
2282
  throw completedExit(error instanceof ParameterExpansionFailure && !state.isolated ? 127 : 1);
2272
2283
  }
2273
2284
  if (error instanceof ArrayFailure)
2274
- await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? message(error)}\n`);
2285
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? publicMessage}\n`);
2275
2286
  else {
2276
2287
  try {
2277
- await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? message(error)}\n`);
2288
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? publicMessage}\n`);
2278
2289
  }
2279
- catch {
2290
+ catch (failure) {
2280
2291
  this.signal.throwIfAborted();
2292
+ publicDiagnosticMessage(failure, this.budget.onInternalError);
2281
2293
  }
2282
2294
  }
2283
2295
  if (error instanceof ExpansionFailure || error instanceof BraceExpansionFailure)
@@ -2406,7 +2418,7 @@ export class Runtime {
2406
2418
  if (error instanceof ParameterExpansionFailure)
2407
2419
  throw new CommandFailure(error.message, state.isolated ? 1 : 127);
2408
2420
  if (error instanceof ExpansionFailure)
2409
- throw new Error(error.message);
2421
+ throw new PublicDiagnostic(error.message);
2410
2422
  throw error;
2411
2423
  }
2412
2424
  if (hereString) {
@@ -2421,7 +2433,7 @@ export class Runtime {
2421
2433
  }
2422
2434
  const targets = await this.word(redirect.target, state, currentIO());
2423
2435
  if (targets.length !== 1)
2424
- throw new Error("Ambiguous redirect");
2436
+ throw new PublicDiagnostic("Ambiguous redirect");
2425
2437
  const target = targets[0];
2426
2438
  errorTarget = target;
2427
2439
  if (redirect.operator.endsWith("&")) {
@@ -2429,14 +2441,14 @@ export class Runtime {
2429
2441
  descriptors.delete(redirect.descriptor);
2430
2442
  else {
2431
2443
  if (!/^\d+-?$/u.test(target))
2432
- throw new Error(`${target}: Bad file descriptor`);
2444
+ throw new PublicDiagnostic(`${target}: Bad file descriptor`);
2433
2445
  const move = target.endsWith("-");
2434
2446
  if (move && !redirect.move)
2435
- throw new Error(`${target}: ambiguous redirect`);
2447
+ throw new PublicDiagnostic(`${target}: ambiguous redirect`);
2436
2448
  const sourceDescriptor = Number(move ? target.slice(0, -1) : target);
2437
2449
  const descriptor = descriptors.get(sourceDescriptor);
2438
2450
  if (!descriptor || descriptor.closed || (!move && (redirect.operator === "<&" ? !descriptor.input : !descriptor.output)))
2439
- throw new Error(`${move ? sourceDescriptor : target}: Bad file descriptor`);
2451
+ throw new PublicDiagnostic(`${move ? sourceDescriptor : target}: Bad file descriptor`);
2440
2452
  descriptors.set(redirect.descriptor, { ...descriptor });
2441
2453
  if (move && sourceDescriptor !== redirect.descriptor) {
2442
2454
  descriptors.delete(sourceDescriptor);
@@ -2452,7 +2464,7 @@ export class Runtime {
2452
2464
  await interruptible(this.fs.access(path, 4, options), this.signal);
2453
2465
  const stat = await interruptible(this.fs.stat(path, options), this.signal);
2454
2466
  if (stat.type === "directory" && !fileShortcut)
2455
- throw new Error(`${target}: Is a directory`);
2467
+ throw new PublicDiagnostic(`${target}: Is a directory`);
2456
2468
  const source = stat.type === "directory" ? toByteSource("")
2457
2469
  : await fileInput(this.fs, path, this.budget.limits.maxInputBytes, this.signal);
2458
2470
  const input = new ShellInput(source, this.budget, this.signal);
@@ -2761,7 +2773,7 @@ export class Runtime {
2761
2773
  if (fileShortcut) {
2762
2774
  const input = io.descriptors?.get(command.redirects[0].descriptor)?.input;
2763
2775
  if (!input)
2764
- throw new Error("Bad file descriptor");
2776
+ throw new PublicDiagnostic("Bad file descriptor");
2765
2777
  await pipeBytes(input, io.stdout, this.signal);
2766
2778
  return 0;
2767
2779
  }
@@ -2773,7 +2785,7 @@ export class Runtime {
2773
2785
  this.signal.throwIfAborted();
2774
2786
  const original = error instanceof ExecutionFailure ? error.original : error;
2775
2787
  if (special && !(original instanceof ShellLimitError) && !(original instanceof ExpansionFailure) && !(original instanceof Flow) && !(original instanceof ShellSyntaxError)) {
2776
- throw new ExecutionFailure(new FatalCommandFailure(message(original), 1), error instanceof ExecutionFailure ? error.io : io, error instanceof ExecutionFailure ? error.diagnostic : undefined);
2788
+ throw new ExecutionFailure(new FatalCommandFailure(message(original, this.budget.onInternalError), 1), error instanceof ExecutionFailure ? error.io : io, error instanceof ExecutionFailure ? error.diagnostic : undefined);
2777
2789
  }
2778
2790
  if (error instanceof ExecutionFailure)
2779
2791
  throw error;
@@ -2822,6 +2834,7 @@ export class Runtime {
2822
2834
  const context = {
2823
2835
  ...publicIO, command: name, args: argumentValues.args, argumentValues, env, cwd: state.cwd, fs: this.fs, signal: this.commandSignal,
2824
2836
  executionScope: this.budget.executionScope,
2837
+ onInternalError: this.budget.onInternalError,
2825
2838
  registerCleanup: (cleanup) => { scope.register(cleanup); },
2826
2839
  invoke: (name, args, options) => {
2827
2840
  const invocation = this.invoke(name, args, options, context, state, scope);
@@ -3271,8 +3284,8 @@ export class Runtime {
3271
3284
  if (code === "ENOENT" || code === "ENOTDIR")
3272
3285
  continue;
3273
3286
  if (code !== "EACCES" && code !== "EPERM")
3274
- throw new CommandFailure(filesystemDiagnostic(error, target) ?? `${target}: ${message(error)}`, 126);
3275
- denied ??= new CommandFailure(filesystemDiagnostic(error, target) ?? `${target}: ${message(error)}`, 126);
3287
+ throw new CommandFailure(filesystemDiagnostic(error, target, this.budget.onInternalError) ?? `${target}: ${message(error, this.budget.onInternalError)}`, 126);
3288
+ denied ??= new CommandFailure(filesystemDiagnostic(error, target, this.budget.onInternalError) ?? `${target}: ${message(error, this.budget.onInternalError)}`, 126);
3276
3289
  }
3277
3290
  }
3278
3291
  if (denied && !matches.length && !discovery)
@@ -3479,6 +3492,7 @@ export class Runtime {
3479
3492
  const context = {
3480
3493
  ...incoming, args: argumentValues.args, argumentValues,
3481
3494
  executionScope: this.budget.executionScope,
3495
+ onInternalError: this.budget.onInternalError,
3482
3496
  env: Object.assign(Object.create(null), incoming.env),
3483
3497
  stdin: input ?? incoming.stdin,
3484
3498
  stdout: this.budget.sink(incoming.stdout, runtime.signal), stderr: this.budget.sink(incoming.stderr, runtime.signal),
@@ -3675,7 +3689,7 @@ export class Runtime {
3675
3689
  throw error;
3676
3690
  if (errorCode(error) === "EFBIG")
3677
3691
  this.budget.fail("maxSourceBytes");
3678
- throw new CommandFailure(filesystemDiagnostic(error, target) ?? `${target}: ${message(error)}`, errorCode(error) === "ENOENT" ? 127 : 126);
3692
+ throw new CommandFailure(filesystemDiagnostic(error, target, this.budget.onInternalError) ?? `${target}: ${message(error, this.budget.onInternalError)}`, errorCode(error) === "ENOENT" ? 127 : 126);
3679
3693
  }
3680
3694
  if (direct && environmentInterpreter)
3681
3695
  return this.envShebang(context, state, io, environmentInterpreter[1], target, args, { path, source });
@@ -3848,7 +3862,7 @@ export class Runtime {
3848
3862
  throw error;
3849
3863
  if (errorCode(error) === "EFBIG")
3850
3864
  this.budget.fail("maxSourceBytes");
3851
- const diagnostic = error instanceof CommandFailure ? error.message : filesystemDiagnostic(error, target) ?? `${target}: ${message(error)}`;
3865
+ const diagnostic = error instanceof CommandFailure ? error.message : filesystemDiagnostic(error, target, this.budget.onInternalError) ?? `${target}: ${message(error, this.budget.onInternalError)}`;
3852
3866
  if (special)
3853
3867
  throw new FatalCommandFailure(diagnostic, 1);
3854
3868
  throw new CommandFailure(diagnostic, error instanceof CommandFailure ? error.status : 1);
@@ -4095,7 +4109,7 @@ export class Runtime {
4095
4109
  }
4096
4110
  catch (error) {
4097
4111
  this.rethrowArithmeticControl(error);
4098
- throw new Error(`let: ${message(error)}`);
4112
+ throw new PublicDiagnostic(`let: ${message(error, this.budget.onInternalError)}`);
4099
4113
  }
4100
4114
  if ((index - offset + 1) % 128 === 0)
4101
4115
  await checkpoint();
@@ -4207,7 +4221,7 @@ export class Runtime {
4207
4221
  else
4208
4222
  this.unsetVariable(state, "OPTARG", true);
4209
4223
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name))
4210
- throw new Error(`getopts: \`${name}': not a valid identifier`);
4224
+ throw new PublicDiagnostic(`getopts: \`${name}': not a valid identifier`);
4211
4225
  await this.assignVariable(state, name, result.option, "getopts");
4212
4226
  return result.status;
4213
4227
  }
@@ -4229,8 +4243,8 @@ export class Runtime {
4229
4243
  }
4230
4244
  catch (error) {
4231
4245
  this.signal.throwIfAborted();
4232
- const description = filesystemDiagnostic(error, "");
4233
- const text = description ? "" : message(error);
4246
+ const description = filesystemDiagnostic(error, "", this.budget.onInternalError);
4247
+ const text = description ? "" : message(error, this.budget.onInternalError);
4234
4248
  diagnose?.(error, cdDiagnostic(description ? [name, ": ", target, description]
4235
4249
  : stackHooks && text.startsWith("cd: ") ? [name, text.slice(2)] : [text]));
4236
4250
  throw error;
@@ -5216,7 +5230,7 @@ export class Runtime {
5216
5230
  }
5217
5231
  catch (error) {
5218
5232
  this.rethrowArithmeticControl(error);
5219
- throw new ExpansionFailure(message(error), io.diagnosticLine ?? part.line);
5233
+ throw new ExpansionFailure(message(error, this.budget.onInternalError), io.diagnosticLine ?? part.line);
5220
5234
  }
5221
5235
  }
5222
5236
  if (part.kind === "substitution") {
@@ -5336,7 +5350,7 @@ export class Runtime {
5336
5350
  throw new ParameterExpansionFailure(`${part.name}: ${alternate || (part.operator.startsWith(":") ? "parameter null or not set" : "parameter not set")}`, io.diagnosticLine ?? part.line);
5337
5351
  if (operator === "=") {
5338
5352
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name))
5339
- throw new Error("Cannot assign special parameter");
5353
+ throw new PublicDiagnostic("Cannot assign special parameter");
5340
5354
  this.writeVariable(state, part.name, retained);
5341
5355
  }
5342
5356
  value = alternate;
@@ -5405,7 +5419,7 @@ export class Runtime {
5405
5419
  }
5406
5420
  catch (error) {
5407
5421
  this.rethrowArithmeticControl(error);
5408
- throw new ExpansionFailure(`${part.name}: ${message(error)}`, line);
5422
+ throw new ExpansionFailure(`${part.name}: ${message(error, this.budget.onInternalError)}`, line);
5409
5423
  }
5410
5424
  finally {
5411
5425
  retained?.release();