@poe-platform/safe-bash 0.1.157 → 0.1.159

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 (81) hide show
  1. package/dist/safe-bash/browser.js +274 -228
  2. package/dist/safe-bash/browser.js.map +4 -4
  3. package/dist/safe-bash/commands/apply-patch/shared.d.ts.map +1 -1
  4. package/dist/safe-bash/commands/apply-patch/shared.js +4 -2
  5. package/dist/safe-bash/commands/apply-patch/shared.js.map +1 -1
  6. package/dist/safe-bash/commands/archive/create.d.ts.map +1 -1
  7. package/dist/safe-bash/commands/archive/create.js +2 -1
  8. package/dist/safe-bash/commands/archive/create.js.map +1 -1
  9. package/dist/safe-bash/commands/archive/index.d.ts.map +1 -1
  10. package/dist/safe-bash/commands/archive/index.js +2 -1
  11. package/dist/safe-bash/commands/archive/index.js.map +1 -1
  12. package/dist/safe-bash/commands/basic.d.ts.map +1 -1
  13. package/dist/safe-bash/commands/basic.js +5 -4
  14. package/dist/safe-bash/commands/basic.js.map +1 -1
  15. package/dist/safe-bash/commands/column/internal.d.ts.map +1 -1
  16. package/dist/safe-bash/commands/column/internal.js +2 -1
  17. package/dist/safe-bash/commands/column/internal.js.map +1 -1
  18. package/dist/safe-bash/commands/diff-patch/shared.d.ts.map +1 -1
  19. package/dist/safe-bash/commands/diff-patch/shared.js +3 -2
  20. package/dist/safe-bash/commands/diff-patch/shared.js.map +1 -1
  21. package/dist/safe-bash/commands/du/budget.d.ts.map +1 -1
  22. package/dist/safe-bash/commands/du/budget.js +2 -1
  23. package/dist/safe-bash/commands/du/budget.js.map +1 -1
  24. package/dist/safe-bash/commands/execution.d.ts.map +1 -1
  25. package/dist/safe-bash/commands/execution.js +4 -3
  26. package/dist/safe-bash/commands/execution.js.map +1 -1
  27. package/dist/safe-bash/commands/expr/index.d.ts.map +1 -1
  28. package/dist/safe-bash/commands/expr/index.js +5 -3
  29. package/dist/safe-bash/commands/expr/index.js.map +1 -1
  30. package/dist/safe-bash/commands/filesystem.d.ts.map +1 -1
  31. package/dist/safe-bash/commands/filesystem.js +10 -9
  32. package/dist/safe-bash/commands/filesystem.js.map +1 -1
  33. package/dist/safe-bash/commands/find.d.ts.map +1 -1
  34. package/dist/safe-bash/commands/find.js +3 -2
  35. package/dist/safe-bash/commands/find.js.map +1 -1
  36. package/dist/safe-bash/commands/html-to-markdown/index.d.ts.map +1 -1
  37. package/dist/safe-bash/commands/html-to-markdown/index.js +2 -1
  38. package/dist/safe-bash/commands/html-to-markdown/index.js.map +1 -1
  39. package/dist/safe-bash/commands/internal.d.ts.map +1 -1
  40. package/dist/safe-bash/commands/internal.js +2 -1
  41. package/dist/safe-bash/commands/internal.js.map +1 -1
  42. package/dist/safe-bash/commands/network/shared.js +2 -2
  43. package/dist/safe-bash/commands/network/shared.js.map +1 -1
  44. package/dist/safe-bash/commands/node/host.d.ts.map +1 -1
  45. package/dist/safe-bash/commands/node/host.js +4 -2
  46. package/dist/safe-bash/commands/node/host.js.map +1 -1
  47. package/dist/safe-bash/commands/safejs/index.d.ts.map +1 -1
  48. package/dist/safe-bash/commands/safejs/index.js +2 -1
  49. package/dist/safe-bash/commands/safejs/index.js.map +1 -1
  50. package/dist/safe-bash/commands/search/shared.d.ts.map +1 -1
  51. package/dist/safe-bash/commands/search/shared.js +2 -1
  52. package/dist/safe-bash/commands/search/shared.js.map +1 -1
  53. package/dist/safe-bash/commands/split/split.d.ts.map +1 -1
  54. package/dist/safe-bash/commands/split/split.js +3 -2
  55. package/dist/safe-bash/commands/split/split.js.map +1 -1
  56. package/dist/safe-bash/commands/structured/jq.d.ts.map +1 -1
  57. package/dist/safe-bash/commands/structured/jq.js +5 -4
  58. package/dist/safe-bash/commands/structured/jq.js.map +1 -1
  59. package/dist/safe-bash/commands/text-programs/shared.d.ts.map +1 -1
  60. package/dist/safe-bash/commands/text-programs/shared.js +2 -1
  61. package/dist/safe-bash/commands/text-programs/shared.js.map +1 -1
  62. package/dist/safe-bash/commands/time-env/shared.d.ts.map +1 -1
  63. package/dist/safe-bash/commands/time-env/shared.js +2 -1
  64. package/dist/safe-bash/commands/time-env/shared.js.map +1 -1
  65. package/dist/safe-bash/commands/tree/io.d.ts.map +1 -1
  66. package/dist/safe-bash/commands/tree/io.js +2 -8
  67. package/dist/safe-bash/commands/tree/io.js.map +1 -1
  68. package/dist/safe-bash/commands/which/which.d.ts.map +1 -1
  69. package/dist/safe-bash/commands/which/which.js +3 -1
  70. package/dist/safe-bash/commands/which/which.js.map +1 -1
  71. package/dist/safe-bash/escaping.d.ts +4 -0
  72. package/dist/safe-bash/escaping.d.ts.map +1 -0
  73. package/dist/safe-bash/escaping.js +47 -0
  74. package/dist/safe-bash/escaping.js.map +1 -0
  75. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  76. package/dist/safe-bash/shell/runtime.js +58 -57
  77. package/dist/safe-bash/shell/runtime.js.map +1 -1
  78. package/dist/safe-bash/shell/shell.d.ts.map +1 -1
  79. package/dist/safe-bash/shell/shell.js +7 -6
  80. package/dist/safe-bash/shell/shell.js.map +1 -1
  81. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { writeDiagnostic } from "../escaping.js";
1
2
  import { cancelTurn, monotonicNow, registerYieldCheckpoint, scheduleTurn, yieldTurn } from "../contracts/yield.js";
2
3
  import { ACCESS_MODES, FsError, composeMiddleware, createBytePipe, pipeBytes, resolvePath, toByteSource, validateExitCode, writeText, } from "../contracts/index.js";
3
4
  import { concatShellValues, shellValueByteLength, shellValueBytes, shellValueFromBytes, shellValueText } from "../contracts/value.js";
@@ -1283,7 +1284,7 @@ export class Runtime {
1283
1284
  this.outcomeFrame.report = undefined;
1284
1285
  }
1285
1286
  diagnostic(io, text) {
1286
- return writeText(io.stderr, `${io.scriptName ?? "shell"}: line ${io.diagnosticLine ?? 1}: ${text}\n`);
1287
+ return writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${io.diagnosticLine ?? 1}: ${text}\n`);
1287
1288
  }
1288
1289
  writeVariable(state, name, value, origin = "assignment") {
1289
1290
  if (arrayStore(state)?.get(name))
@@ -2227,7 +2228,7 @@ export class Runtime {
2227
2228
  throw error;
2228
2229
  this.clearOutcomeReport();
2229
2230
  if (error instanceof HereDocumentSyntaxError) {
2230
- await writeText(io.stderr, error.diagnostic);
2231
+ await writeDiagnostic(io.stderr, error.diagnostic);
2231
2232
  if (command.kind !== "simple" && command.kind !== "subshell" && command.kind !== "arithmetic" && command.kind !== "conditional")
2232
2233
  this.errexit(1, state, io);
2233
2234
  return 1;
@@ -2241,7 +2242,7 @@ export class Runtime {
2241
2242
  if (error instanceof NounsetFailure || error instanceof ParameterExpansionFailure) {
2242
2243
  const detail = error instanceof ParameterExpansionFailure ? diagnostic ?? message(error) : message(error);
2243
2244
  try {
2244
- await writeText(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${detail}\n`);
2245
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${detail}\n`);
2245
2246
  }
2246
2247
  catch (reason) {
2247
2248
  this.signal.throwIfAborted();
@@ -2253,10 +2254,10 @@ export class Runtime {
2253
2254
  throw completedExit(error instanceof ParameterExpansionFailure && !state.isolated ? 127 : 1);
2254
2255
  }
2255
2256
  if (error instanceof ArrayFailure)
2256
- await writeText(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? message(error)}\n`);
2257
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? message(error)}\n`);
2257
2258
  else {
2258
2259
  try {
2259
- await writeText(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? message(error)}\n`);
2260
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? message(error)}\n`);
2260
2261
  }
2261
2262
  catch {
2262
2263
  this.signal.throwIfAborted();
@@ -2312,7 +2313,7 @@ export class Runtime {
2312
2313
  for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings, this.budget.parsing)) {
2313
2314
  this.signal.throwIfAborted();
2314
2315
  for (const warning of warnings.splice(0))
2315
- await writeText(io.stderr, `shell: warning: ${warning}\n`);
2316
+ await writeDiagnostic(io.stderr, `shell: warning: ${warning}\n`);
2316
2317
  if (++words % 128 === 0)
2317
2318
  await yieldTurn(this.signal);
2318
2319
  const part = (await this.word(word, state, io, false)).join("");
@@ -2324,7 +2325,7 @@ export class Runtime {
2324
2325
  }
2325
2326
  finally {
2326
2327
  for (const warning of warnings.splice(0))
2327
- await writeText(io.stderr, `shell: warning: ${warning}\n`);
2328
+ await writeDiagnostic(io.stderr, `shell: warning: ${warning}\n`);
2328
2329
  }
2329
2330
  return value;
2330
2331
  }
@@ -3124,10 +3125,10 @@ export class Runtime {
3124
3125
  else {
3125
3126
  if (command && flag !== "p") {
3126
3127
  await this.diagnostic({ ...io, ...context }, `command: -${flag}: invalid option`);
3127
- await writeText(context.stderr, "command: usage: command [-pVv] command [arg ...]\n");
3128
+ await writeDiagnostic(context.stderr, "command: usage: command [-pVv] command [arg ...]\n");
3128
3129
  }
3129
3130
  else
3130
- await writeText(context.stderr, `${context.command}: ${option}: unsupported option\n`);
3131
+ await writeDiagnostic(context.stderr, `${context.command}: ${option}: unsupported option\n`);
3131
3132
  return 2;
3132
3133
  }
3133
3134
  }
@@ -3181,7 +3182,7 @@ export class Runtime {
3181
3182
  }
3182
3183
  if (!matches.length) {
3183
3184
  if (mode === "describe")
3184
- await writeText(context.stderr, `${io.scriptName ?? "shell"}: line ${io.diagnosticLine ?? 1}: ${context.command}: ${name}: not found\n`);
3185
+ await writeDiagnostic(context.stderr, `${io.scriptName ?? "shell"}: line ${io.diagnosticLine ?? 1}: ${context.command}: ${name}: not found\n`);
3185
3186
  continue;
3186
3187
  }
3187
3188
  found++;
@@ -3283,7 +3284,7 @@ export class Runtime {
3283
3284
  }
3284
3285
  const flags = option.slice(1);
3285
3286
  if (!flags.length || [...flags].some(flag => !(option[0] === "-" ? "cseB" : "eB").includes(flag))) {
3286
- await writeText(context.stderr, `${context.command}: ${option}: unsupported option; supported flags are -c, -s, -e, +e, -B, +B and +/-o braceexpand\n`);
3287
+ await writeDiagnostic(context.stderr, `${context.command}: ${option}: unsupported option; supported flags are -c, -s, -e, +e, -B, +B and +/-o braceexpand\n`);
3287
3288
  return 2;
3288
3289
  }
3289
3290
  commandString ||= option.includes("c");
@@ -3297,7 +3298,7 @@ export class Runtime {
3297
3298
  return this.scriptFile(context, state, io, args[0], args.slice(1), false, errexit, loadedSource, braceexpand);
3298
3299
  const source = commandString ? args.shift() : undefined;
3299
3300
  if (commandString && source === undefined) {
3300
- await writeText(context.stderr, `${context.command}: -c: option requires an argument\n`);
3301
+ await writeDiagnostic(context.stderr, `${context.command}: -c: option requires an argument\n`);
3301
3302
  return 2;
3302
3303
  }
3303
3304
  const arg0 = commandString ? args.shift() ?? context.command : context.command;
@@ -3317,14 +3318,14 @@ export class Runtime {
3317
3318
  const line = source.slice(0, error.offset).split("\n").length;
3318
3319
  const prefix = `${io.scriptName ?? "shell"}:${commandString ? " -c:" : ""}`;
3319
3320
  if (error.unclosedQuote)
3320
- await writeText(io.stderr, `${prefix} line ${offset + error.unclosedQuote.line}: unexpected EOF while looking for matching \`${error.unclosedQuote.quote}'\n`);
3321
+ await writeDiagnostic(io.stderr, `${prefix} line ${offset + error.unclosedQuote.line}: unexpected EOF while looking for matching \`${error.unclosedQuote.quote}'\n`);
3321
3322
  else if (error.offset >= source.length && !/Unterminated|nesting|Unsupported/u.test(error.reason)) {
3322
3323
  const context = error.incompleteCommand ? ` from \`${error.incompleteCommand.name}' command on line ${offset + error.incompleteCommand.line}` : "";
3323
- await writeText(io.stderr, `${prefix} line ${offset + source.split("\n").length + Number(!source.endsWith("\n"))}: syntax error: unexpected end of file${context}\n`);
3324
+ await writeDiagnostic(io.stderr, `${prefix} line ${offset + source.split("\n").length + Number(!source.endsWith("\n"))}: syntax error: unexpected end of file${context}\n`);
3324
3325
  }
3325
3326
  else {
3326
3327
  const token = /^[;&|()<>]|^[^\s;&|()<>]+/u.exec(source.slice(error.offset))?.[0] ?? "newline";
3327
- await writeText(io.stderr, `${prefix} line ${offset + line}: syntax error near unexpected token \`${token}'\n${prefix} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'\n`);
3328
+ await writeDiagnostic(io.stderr, `${prefix} line ${offset + line}: syntax error near unexpected token \`${token}'\n${prefix} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'\n`);
3328
3329
  }
3329
3330
  return error.exitCode;
3330
3331
  }
@@ -3337,7 +3338,7 @@ export class Runtime {
3337
3338
  this.signal.throwIfAborted();
3338
3339
  const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing, lineIndex);
3339
3340
  for (const warning of unit.script.warnings ?? [])
3340
- await writeText(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
3341
+ await writeDiagnostic(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
3341
3342
  if (unit.script.lists.length) {
3342
3343
  const result = await this.runUnit(unit.script, state, io);
3343
3344
  status = result.exitCode;
@@ -3385,7 +3386,7 @@ export class Runtime {
3385
3386
  const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables), this.budget.parsing, lineIndex) : parseShellInputUnit(source, byteLocale(state.variables), this.budget.parsing, lineIndex);
3386
3387
  if (unit) {
3387
3388
  for (const warning of unit.script.warnings ?? [])
3388
- await writeText(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
3389
+ await writeDiagnostic(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
3389
3390
  if (unit.script.lists.length) {
3390
3391
  const result = await this.runUnit(unit.script, state, unitIO);
3391
3392
  status = result.exitCode;
@@ -3555,7 +3556,7 @@ export class Runtime {
3555
3556
  return { exitCode: await runtime.scriptFile(forwarded, state, childIO, command, forwarded.args, true) };
3556
3557
  if (definition)
3557
3558
  return definition.execute(forwarded);
3558
- await writeText(forwarded.stderr, `env: ${command}: command not found\n`);
3559
+ await writeDiagnostic(forwarded.stderr, `env: ${command}: command not found\n`);
3559
3560
  return { exitCode: 127 };
3560
3561
  }, undefined, options.stdin !== context.stdin ? options.stdin : undefined, scope);
3561
3562
  return { exitCode };
@@ -3660,7 +3661,7 @@ export class Runtime {
3660
3661
  if (!(error instanceof ShellSyntaxError))
3661
3662
  throw error;
3662
3663
  const line = source.slice(0, error.offset).split("\n").length;
3663
- await writeText(context.stderr, `${target}: line ${line}: syntax error: ${error.reason}\n`);
3664
+ await writeDiagnostic(context.stderr, `${target}: line ${line}: syntax error: ${error.reason}\n`);
3664
3665
  return error.exitCode;
3665
3666
  }
3666
3667
  const child = this.processState(context, state, target, args);
@@ -3672,7 +3673,7 @@ export class Runtime {
3672
3673
  let status = 0;
3673
3674
  for (const unit of units) {
3674
3675
  for (const warning of unit.warnings ?? [])
3675
- await writeText(context.stderr, `${target}: warning: ${warning}\n`);
3676
+ await writeDiagnostic(context.stderr, `${target}: warning: ${warning}\n`);
3676
3677
  if (!unit.lists.length)
3677
3678
  continue;
3678
3679
  const result = await this.runUnit(unit, child, childIO);
@@ -3692,7 +3693,7 @@ export class Runtime {
3692
3693
  this.signal.throwIfAborted();
3693
3694
  const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing, lineIndex);
3694
3695
  for (const warning of unit.script.warnings ?? [])
3695
- await writeText(io.stderr, `${io.scriptName ?? "shell"}: warning: ${warning}\n`);
3696
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: warning: ${warning}\n`);
3696
3697
  if (unit.script.lists.length) {
3697
3698
  status = await this.script(unit.script, state, io);
3698
3699
  executed = true;
@@ -3716,7 +3717,7 @@ export class Runtime {
3716
3717
  args.shift();
3717
3718
  else if (args[0]?.startsWith("-") && args[0] !== "-") {
3718
3719
  await this.diagnostic(io, `eval: -${args[0][1]}: invalid option`);
3719
- await writeText(io.stderr, "eval: usage: eval [arg ...]\n");
3720
+ await writeDiagnostic(io.stderr, "eval: usage: eval [arg ...]\n");
3720
3721
  if (special)
3721
3722
  throw new Flow("exit", 2);
3722
3723
  return 2;
@@ -3760,7 +3761,7 @@ export class Runtime {
3760
3761
  const filename = args.shift();
3761
3762
  if (filename === undefined) {
3762
3763
  await this.diagnostic(io, `${context.command}: filename argument required`);
3763
- await writeText(io.stderr, `${context.command}: usage: ${context.command} [-p path] filename [arguments]\n`);
3764
+ await writeDiagnostic(io.stderr, `${context.command}: usage: ${context.command} [-p path] filename [arguments]\n`);
3764
3765
  if (special)
3765
3766
  throw new Flow("exit", 2);
3766
3767
  return 2;
@@ -4011,7 +4012,7 @@ export class Runtime {
4011
4012
  valid = false;
4012
4013
  }
4013
4014
  if (!valid) {
4014
- await writeText(stderr, "set: unsupported shell option; supported forms are +/- e/u/B clusters, -- arguments and terminal o with braceexpand, pipefail, errexit or nounset\n");
4015
+ await writeDiagnostic(stderr, "set: unsupported shell option; supported forms are +/- e/u/B clusters, -- arguments and terminal o with braceexpand, pipefail, errexit or nounset\n");
4015
4016
  return 1;
4016
4017
  }
4017
4018
  index++;
@@ -4106,11 +4107,11 @@ export class Runtime {
4106
4107
  const offset = context.args[0] === "--" ? 1 : 0;
4107
4108
  if (!offset && context.args[0]?.startsWith("-") && context.args[0] !== "-") {
4108
4109
  await this.diagnostic(context, `getopts: -${context.args[0][1]}: invalid option`);
4109
- await writeText(context.stderr, "getopts: usage: getopts optstring name [arg ...]\n");
4110
+ await writeDiagnostic(context.stderr, "getopts: usage: getopts optstring name [arg ...]\n");
4110
4111
  return 2;
4111
4112
  }
4112
4113
  if (context.args.length - offset < 2) {
4113
- await writeText(context.stderr, "getopts: usage: getopts optstring name [arg ...]\n");
4114
+ await writeDiagnostic(context.stderr, "getopts: usage: getopts optstring name [arg ...]\n");
4114
4115
  return 2;
4115
4116
  }
4116
4117
  const optstring = context.args[offset];
@@ -4144,7 +4145,7 @@ export class Runtime {
4144
4145
  state.getopts.cursor = result.state;
4145
4146
  if (result.diagnostic) {
4146
4147
  const explanation = result.diagnostic.kind === "unknown-option" ? "illegal option" : "option requires an argument";
4147
- await writeText(context.stderr, `${state.arg0 ?? context.scriptName ?? "shell"}: ${explanation} -- ${result.diagnostic.option}\n`);
4148
+ await writeDiagnostic(context.stderr, `${state.arg0 ?? context.scriptName ?? "shell"}: ${explanation} -- ${result.diagnostic.option}\n`);
4148
4149
  }
4149
4150
  this.signal.throwIfAborted();
4150
4151
  this.writeVariable(state, "OPTIND", String(result.optind), "getopts");
@@ -4161,12 +4162,12 @@ export class Runtime {
4161
4162
  const name = stackHooks?.name ?? "cd";
4162
4163
  this.signal.throwIfAborted();
4163
4164
  if (args.length > 1) {
4164
- await writeText(context.stderr, `${name}: too many arguments\n`);
4165
+ await writeDiagnostic(context.stderr, `${name}: too many arguments\n`);
4165
4166
  return 1;
4166
4167
  }
4167
4168
  const target = args[0] === "-" ? state.variables.OLDPWD : (args[0] ?? state.variables.HOME);
4168
4169
  if (target === undefined) {
4169
- await writeText(context.stderr, `${name}: ${args[0] === "-" ? "OLDPWD" : "HOME"} not set\n`);
4170
+ await writeDiagnostic(context.stderr, `${name}: ${args[0] === "-" ? "OLDPWD" : "HOME"} not set\n`);
4170
4171
  return 1;
4171
4172
  }
4172
4173
  let selected;
@@ -4387,7 +4388,7 @@ export class Runtime {
4387
4388
  unset = true;
4388
4389
  else {
4389
4390
  await this.diagnostic(context, `shopt: ${option.startsWith("--") ? option : `-${flag}`}: unsupported option`);
4390
- await writeText(context.stderr, "shopt: usage: shopt [-pqsu] [--] [dotglob ...]\n");
4391
+ await writeDiagnostic(context.stderr, "shopt: usage: shopt [-pqsu] [--] [dotglob ...]\n");
4391
4392
  return 2;
4392
4393
  }
4393
4394
  }
@@ -4432,19 +4433,19 @@ export class Runtime {
4432
4433
  const option = incoming.args[offset++];
4433
4434
  const target = option.slice(2) || incoming.args[offset++];
4434
4435
  if (target === undefined) {
4435
- await writeText(context.stderr, "printf: -v: option requires an argument\n");
4436
+ await writeDiagnostic(context.stderr, "printf: -v: option requires an argument\n");
4436
4437
  return 2;
4437
4438
  }
4438
4439
  const bracket = target.indexOf("[");
4439
4440
  name = bracket < 0 ? target : target.slice(0, bracket);
4440
4441
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name) || bracket >= 0 && !target.endsWith("]")) {
4441
- await writeText(context.stderr, `printf: '${target}': not a valid identifier\n`);
4442
+ await writeDiagnostic(context.stderr, `printf: '${target}': not a valid identifier\n`);
4442
4443
  return 2;
4443
4444
  }
4444
4445
  index = undefined;
4445
4446
  if (bracket >= 0) {
4446
4447
  if (target[bracket + 1] === "'" || target[bracket + 1] === '"') {
4447
- await writeText(context.stderr, `printf: '${target}': unsupported indexed-array subscript\n`);
4448
+ await writeDiagnostic(context.stderr, `printf: '${target}': unsupported indexed-array subscript\n`);
4448
4449
  return 2;
4449
4450
  }
4450
4451
  try {
@@ -4454,11 +4455,11 @@ export class Runtime {
4454
4455
  this.signal.throwIfAborted();
4455
4456
  if (!(error instanceof ShellSyntaxError))
4456
4457
  throw error;
4457
- await writeText(context.stderr, `printf: '${target}': unsupported indexed-array subscript\n`);
4458
+ await writeDiagnostic(context.stderr, `printf: '${target}': unsupported indexed-array subscript\n`);
4458
4459
  return 2;
4459
4460
  }
4460
4461
  if (numericIndex(index) === undefined) {
4461
- await writeText(context.stderr, "printf: index outside 0..2147483647\n");
4462
+ await writeDiagnostic(context.stderr, "printf: index outside 0..2147483647\n");
4462
4463
  return 2;
4463
4464
  }
4464
4465
  }
@@ -4466,7 +4467,7 @@ export class Runtime {
4466
4467
  const arguments_ = incoming.slice(offset);
4467
4468
  const format = arguments_.args[0] === "--" ? arguments_.args[1] : arguments_.args[0];
4468
4469
  if (format === undefined || arguments_.args[0] !== "--" && format.startsWith("-")) {
4469
- await writeText(context.stderr, "printf: usage: printf [-v var] format [arguments]\n");
4470
+ await writeDiagnostic(context.stderr, "printf: usage: printf [-v var] format [arguments]\n");
4470
4471
  return 2;
4471
4472
  }
4472
4473
  const allocation = this.budget.values.scope();
@@ -4496,13 +4497,13 @@ export class Runtime {
4496
4497
  this.signal.throwIfAborted();
4497
4498
  if (!(error instanceof UsageError))
4498
4499
  throw error;
4499
- await writeText(context.stderr, `printf: ${error.message}\n`);
4500
+ await writeDiagnostic(context.stderr, `printf: ${error.message}\n`);
4500
4501
  status = 1;
4501
4502
  }
4502
4503
  const value = concatShellValues(chunks, allocation);
4503
4504
  this.signal.throwIfAborted();
4504
4505
  if (state.readonlyVariables?.has(name)) {
4505
- await writeText(context.stderr, `printf: ${name}: readonly variable\n`);
4506
+ await writeDiagnostic(context.stderr, `printf: ${name}: readonly variable\n`);
4506
4507
  return 1;
4507
4508
  }
4508
4509
  if (index || arrayStore(state)?.get(name)) {
@@ -4525,7 +4526,7 @@ export class Runtime {
4525
4526
  text = undefined;
4526
4527
  }
4527
4528
  if (text === undefined) {
4528
- await writeText(context.stderr, "printf: indexed variables do not support non-UTF-8 bytes\n");
4529
+ await writeDiagnostic(context.stderr, "printf: indexed variables do not support non-UTF-8 bytes\n");
4529
4530
  return 1;
4530
4531
  }
4531
4532
  if (index)
@@ -4564,7 +4565,7 @@ export class Runtime {
4564
4565
  return this.directoryStackBuiltin(context, state, diagnose);
4565
4566
  if (command === "pwd") {
4566
4567
  if (args.some((arg) => arg !== "-L" && arg !== "-P")) {
4567
- await writeText(stderr, "pwd: invalid option\n");
4568
+ await writeDiagnostic(stderr, "pwd: invalid option\n");
4568
4569
  return 2;
4569
4570
  }
4570
4571
  const path = args.at(-1) === "-P" ? await this.fs.realpath(state.cwd, { signal: this.signal }) : state.cwd;
@@ -4607,7 +4608,7 @@ export class Runtime {
4607
4608
  continue;
4608
4609
  }
4609
4610
  if (/^[+-]/u.test(option)) {
4610
- await writeText(stderr, "set: unsupported shell option; supported forms are +/- e/u clusters, -- arguments and terminal o with pipefail, errexit or nounset\n");
4611
+ await writeDiagnostic(stderr, "set: unsupported shell option; supported forms are +/- e/u clusters, -- arguments and terminal o with pipefail, errexit or nounset\n");
4611
4612
  if (state.profile === "sh" && suppressSpecial)
4612
4613
  return 2;
4613
4614
  throw new Flow("exit", 2);
@@ -4621,7 +4622,7 @@ export class Runtime {
4621
4622
  }
4622
4623
  if (args.length)
4623
4624
  return 0;
4624
- await writeText(stderr, "set: supported forms are +/- e/u clusters, -- arguments and terminal o with pipefail, errexit or nounset\n");
4625
+ await writeDiagnostic(stderr, "set: supported forms are +/- e/u clusters, -- arguments and terminal o with pipefail, errexit or nounset\n");
4625
4626
  return 2;
4626
4627
  }
4627
4628
  if (command === "shift") {
@@ -4637,13 +4638,13 @@ export class Runtime {
4637
4638
  if (command === "local") {
4638
4639
  const options = localDeclarationOptions(declarationArgs, this.signal);
4639
4640
  if (options.error !== undefined) {
4640
- await writeText(stderr, `local: ${options.error}: unsupported option\n`);
4641
+ await writeDiagnostic(stderr, `local: ${options.error}: unsupported option\n`);
4641
4642
  return 2;
4642
4643
  }
4643
4644
  indexedLocal = options.indexed;
4644
4645
  declarationArgs.splice(0, options.offset);
4645
4646
  if (indexedLocal && declarationArgs.length === 0) {
4646
- await writeText(stderr, "local: -a requires a variable name\n");
4647
+ await writeDiagnostic(stderr, "local: -a requires a variable name\n");
4647
4648
  return 2;
4648
4649
  }
4649
4650
  }
@@ -4653,14 +4654,14 @@ export class Runtime {
4653
4654
  if (option === "--")
4654
4655
  break;
4655
4656
  if (option !== "-p") {
4656
- await writeText(stderr, `readonly: ${option}: unsupported option\n`);
4657
+ await writeDiagnostic(stderr, `readonly: ${option}: unsupported option\n`);
4657
4658
  return 2;
4658
4659
  }
4659
4660
  }
4660
4661
  }
4661
4662
  const locals = state.locals.at(-1);
4662
4663
  if (command === "local" && !locals) {
4663
- await writeText(stderr, "local: not in a function\n");
4664
+ await writeDiagnostic(stderr, "local: not in a function\n");
4664
4665
  return 1;
4665
4666
  }
4666
4667
  let status = 0;
@@ -4942,7 +4943,7 @@ export class Runtime {
4942
4943
  continue;
4943
4944
  }
4944
4945
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name)) {
4945
- await writeText(stderr, `unset: ${name}: not a valid identifier\n`);
4946
+ await writeDiagnostic(stderr, `unset: ${name}: not a valid identifier\n`);
4946
4947
  status = 1;
4947
4948
  continue;
4948
4949
  }
@@ -4990,7 +4991,7 @@ export class Runtime {
4990
4991
  delimiter = new TextEncoder().encode(value)[0] ?? 0;
4991
4992
  else if (exact && (!/^[ \t]*[+-]?\d+[ \t]*$/u.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 0)) {
4992
4993
  const diagnosticIO = context;
4993
- await writeText(stderr, `${diagnosticIO.scriptName ?? "shell"}: line ${diagnosticIO.diagnosticLine ?? 1}: read: ${value}: invalid ${/^[+-]?0[xX]/u.test(value) ? "hex " : ""}number\n`);
4994
+ await writeDiagnostic(stderr, `${diagnosticIO.scriptName ?? "shell"}: line ${diagnosticIO.diagnosticLine ?? 1}: read: ${value}: invalid ${/^[+-]?0[xX]/u.test(value) ? "hex " : ""}number\n`);
4994
4995
  return 1;
4995
4996
  }
4996
4997
  else if (!exact && (!/^\d+$/u.test(value) || !Number.isSafeInteger(Number(value))))
@@ -5007,11 +5008,11 @@ export class Runtime {
5007
5008
  const invalidName = names.find(name => !/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name));
5008
5009
  if (exact && !invalid && invalidName !== undefined) {
5009
5010
  const diagnosticIO = context;
5010
- await writeText(stderr, `${diagnosticIO.scriptName ?? "shell"}: line ${diagnosticIO.diagnosticLine ?? 1}: read: \`${invalidName}': not a valid identifier\n`);
5011
+ await writeDiagnostic(stderr, `${diagnosticIO.scriptName ?? "shell"}: line ${diagnosticIO.diagnosticLine ?? 1}: read: \`${invalidName}': not a valid identifier\n`);
5011
5012
  return 1;
5012
5013
  }
5013
5014
  if (invalid || names.some((name) => !/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name))) {
5014
- await writeText(stderr, "read: invalid variable name or unsupported option\n");
5015
+ await writeDiagnostic(stderr, "read: invalid variable name or unsupported option\n");
5015
5016
  return 2;
5016
5017
  }
5017
5018
  const input = context.stdin instanceof ShellInput ? context.stdin : new ShellInput(context.stdin, this.budget, this.signal);
@@ -5070,15 +5071,15 @@ export class Runtime {
5070
5071
  }
5071
5072
  if (command === "exit" || command === "return") {
5072
5073
  if (command === "return" && state.functionDepth === 0 && !state.sourceDepth) {
5073
- await writeText(stderr, "return: not in a function\n");
5074
+ await writeDiagnostic(stderr, "return: not in a function\n");
5074
5075
  return 1;
5075
5076
  }
5076
5077
  if (args.length > 1) {
5077
- await writeText(stderr, `${command}: too many arguments\n`);
5078
+ await writeDiagnostic(stderr, `${command}: too many arguments\n`);
5078
5079
  return 1;
5079
5080
  }
5080
5081
  if (args[0] !== undefined && !/^[+-]?\d+$/u.test(args[0])) {
5081
- await writeText(stderr, `${command}: ${args[0]}: numeric argument required\n`);
5082
+ await writeDiagnostic(stderr, `${command}: ${args[0]}: numeric argument required\n`);
5082
5083
  throw completedExit(2, command);
5083
5084
  }
5084
5085
  const status = args[0] === undefined ? state.status : Number((BigInt(args[0]) % 256n + 256n) % 256n);
@@ -5087,11 +5088,11 @@ export class Runtime {
5087
5088
  if (command === "break" || command === "continue") {
5088
5089
  const levels = args[0] === undefined ? 1 : Number(args[0]);
5089
5090
  if (args.length > 1 || !Number.isSafeInteger(levels) || levels < 1) {
5090
- await writeText(stderr, `${command}: invalid loop count\n`);
5091
+ await writeDiagnostic(stderr, `${command}: invalid loop count\n`);
5091
5092
  return 1;
5092
5093
  }
5093
5094
  if (!state.loopDepth) {
5094
- await writeText(stderr, `${command}: only meaningful in a loop\n`);
5095
+ await writeDiagnostic(stderr, `${command}: only meaningful in a loop\n`);
5095
5096
  return 0;
5096
5097
  }
5097
5098
  throw completedExit(0, command, Math.min(levels, state.loopDepth));
@@ -5145,7 +5146,7 @@ export class Runtime {
5145
5146
  if (part.kind === "failed-substitution") {
5146
5147
  if (state.depth >= this.budget.limits.maxSubstitutionDepth)
5147
5148
  this.budget.fail("maxSubstitutionDepth");
5148
- await writeText(io.stderr, part.diagnostic);
5149
+ await writeDiagnostic(io.stderr, part.diagnostic);
5149
5150
  state.status = state.substitutionStatus = 2;
5150
5151
  return "";
5151
5152
  }
@@ -5200,7 +5201,7 @@ export class Runtime {
5200
5201
  state.status = state.substitutionStatus;
5201
5202
  const bytes = capture.bytes();
5202
5203
  if (bytes.includes(0))
5203
- await writeText(io.stderr, `${io.scriptName ?? "shell"}: line ${warningLine}: warning: command substitution: ignored null byte in input\n`);
5204
+ await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${warningLine}: warning: command substitution: ignored null byte in input\n`);
5204
5205
  let length = 0;
5205
5206
  for (const byte of bytes)
5206
5207
  if (byte !== 0)