@poe-platform/safe-bash 0.1.53 → 0.1.55

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 (137) hide show
  1. package/dist/safe-bash/browser.d.ts +2 -0
  2. package/dist/safe-bash/browser.d.ts.map +1 -1
  3. package/dist/safe-bash/browser.js +3951 -874
  4. package/dist/safe-bash/browser.js.map +4 -4
  5. package/dist/safe-bash/commands/basic.d.ts.map +1 -1
  6. package/dist/safe-bash/commands/basic.js +7 -1
  7. package/dist/safe-bash/commands/basic.js.map +1 -1
  8. package/dist/safe-bash/commands/filesystem-requirements.d.ts +67 -0
  9. package/dist/safe-bash/commands/filesystem-requirements.d.ts.map +1 -0
  10. package/dist/safe-bash/commands/filesystem-requirements.js +81 -0
  11. package/dist/safe-bash/commands/filesystem-requirements.js.map +1 -0
  12. package/dist/safe-bash/commands/filesystem.d.ts.map +1 -1
  13. package/dist/safe-bash/commands/filesystem.js +106 -9
  14. package/dist/safe-bash/commands/filesystem.js.map +1 -1
  15. package/dist/safe-bash/commands/grep.d.ts.map +1 -1
  16. package/dist/safe-bash/commands/grep.js +3 -109
  17. package/dist/safe-bash/commands/grep.js.map +1 -1
  18. package/dist/safe-bash/commands/internal.d.ts +1 -0
  19. package/dist/safe-bash/commands/internal.d.ts.map +1 -1
  20. package/dist/safe-bash/commands/internal.js +22 -0
  21. package/dist/safe-bash/commands/internal.js.map +1 -1
  22. package/dist/safe-bash/commands/move.d.ts.map +1 -1
  23. package/dist/safe-bash/commands/move.js +21 -0
  24. package/dist/safe-bash/commands/move.js.map +1 -1
  25. package/dist/safe-bash/commands/network/curl.js +2 -0
  26. package/dist/safe-bash/commands/network/curl.js.map +1 -1
  27. package/dist/safe-bash/commands/network/output.d.ts.map +1 -1
  28. package/dist/safe-bash/commands/network/output.js +37 -33
  29. package/dist/safe-bash/commands/network/output.js.map +1 -1
  30. package/dist/safe-bash/commands/portable-requirements.d.ts +6 -0
  31. package/dist/safe-bash/commands/portable-requirements.d.ts.map +1 -0
  32. package/dist/safe-bash/commands/portable-requirements.js +18 -0
  33. package/dist/safe-bash/commands/portable-requirements.js.map +1 -0
  34. package/dist/safe-bash/commands/predicates.d.ts.map +1 -1
  35. package/dist/safe-bash/commands/predicates.js +9 -1
  36. package/dist/safe-bash/commands/predicates.js.map +1 -1
  37. package/dist/safe-bash/commands/regex-execution/client.d.ts +5 -67
  38. package/dist/safe-bash/commands/regex-execution/client.d.ts.map +1 -1
  39. package/dist/safe-bash/commands/regex-execution/client.js +13 -379
  40. package/dist/safe-bash/commands/regex-execution/client.js.map +1 -1
  41. package/dist/safe-bash/commands/regex-execution/portable.d.ts +71 -0
  42. package/dist/safe-bash/commands/regex-execution/portable.d.ts.map +1 -0
  43. package/dist/safe-bash/commands/regex-execution/portable.js +385 -0
  44. package/dist/safe-bash/commands/regex-execution/portable.js.map +1 -0
  45. package/dist/safe-bash/commands/regex-execution/protocol.js +1 -1
  46. package/dist/safe-bash/commands/regex-execution/protocol.js.map +1 -1
  47. package/dist/safe-bash/commands/regex-execution/provider.d.ts +24 -0
  48. package/dist/safe-bash/commands/regex-execution/provider.d.ts.map +1 -0
  49. package/dist/safe-bash/commands/regex-execution/provider.js +2 -0
  50. package/dist/safe-bash/commands/regex-execution/provider.js.map +1 -0
  51. package/dist/safe-bash/commands/regex-execution/public.d.ts +9 -0
  52. package/dist/safe-bash/commands/regex-execution/public.d.ts.map +1 -0
  53. package/dist/safe-bash/commands/regex-execution/public.js +6 -0
  54. package/dist/safe-bash/commands/regex-execution/public.js.map +1 -0
  55. package/dist/safe-bash/commands/search/glob.d.ts +1 -1
  56. package/dist/safe-bash/commands/search/glob.d.ts.map +1 -1
  57. package/dist/safe-bash/commands/search/glob.js +1 -1
  58. package/dist/safe-bash/commands/search/glob.js.map +1 -1
  59. package/dist/safe-bash/commands/search/grep.d.ts +4 -0
  60. package/dist/safe-bash/commands/search/grep.d.ts.map +1 -0
  61. package/dist/safe-bash/commands/search/grep.js +119 -0
  62. package/dist/safe-bash/commands/search/grep.js.map +1 -0
  63. package/dist/safe-bash/commands/search/matcher.d.ts +1 -1
  64. package/dist/safe-bash/commands/search/matcher.d.ts.map +1 -1
  65. package/dist/safe-bash/commands/search/matcher.js +1 -1
  66. package/dist/safe-bash/commands/search/matcher.js.map +1 -1
  67. package/dist/safe-bash/commands/search/output.d.ts +1 -1
  68. package/dist/safe-bash/commands/search/output.d.ts.map +1 -1
  69. package/dist/safe-bash/commands/search/output.js +8 -2
  70. package/dist/safe-bash/commands/search/output.js.map +1 -1
  71. package/dist/safe-bash/commands/search/portable.d.ts +14 -0
  72. package/dist/safe-bash/commands/search/portable.d.ts.map +1 -0
  73. package/dist/safe-bash/commands/search/portable.js +22 -0
  74. package/dist/safe-bash/commands/search/portable.js.map +1 -0
  75. package/dist/safe-bash/commands/search/requirements.d.ts +9 -0
  76. package/dist/safe-bash/commands/search/requirements.d.ts.map +1 -0
  77. package/dist/safe-bash/commands/search/requirements.js +72 -0
  78. package/dist/safe-bash/commands/search/requirements.js.map +1 -0
  79. package/dist/safe-bash/commands/search/rg-command.d.ts +5 -0
  80. package/dist/safe-bash/commands/search/rg-command.d.ts.map +1 -0
  81. package/dist/safe-bash/commands/search/rg-command.js +230 -0
  82. package/dist/safe-bash/commands/search/rg-command.js.map +1 -0
  83. package/dist/safe-bash/commands/search/rg.d.ts +2 -2
  84. package/dist/safe-bash/commands/search/rg.d.ts.map +1 -1
  85. package/dist/safe-bash/commands/search/rg.js +3 -216
  86. package/dist/safe-bash/commands/search/rg.js.map +1 -1
  87. package/dist/safe-bash/commands/search/shared.d.ts.map +1 -1
  88. package/dist/safe-bash/commands/search/shared.js +2 -0
  89. package/dist/safe-bash/commands/search/shared.js.map +1 -1
  90. package/dist/safe-bash/commands/search/walk.d.ts +1 -1
  91. package/dist/safe-bash/commands/search/walk.d.ts.map +1 -1
  92. package/dist/safe-bash/commands/search/walk.js +8 -1
  93. package/dist/safe-bash/commands/search/walk.js.map +1 -1
  94. package/dist/safe-bash/commands/streams.d.ts.map +1 -1
  95. package/dist/safe-bash/commands/streams.js +52 -108
  96. package/dist/safe-bash/commands/streams.js.map +1 -1
  97. package/dist/safe-bash/commands/text-programs/sed.d.ts.map +1 -1
  98. package/dist/safe-bash/commands/text-programs/sed.js +23 -14
  99. package/dist/safe-bash/commands/text-programs/sed.js.map +1 -1
  100. package/dist/safe-bash/commands/text-programs/shared.d.ts.map +1 -1
  101. package/dist/safe-bash/commands/text-programs/shared.js +3 -5
  102. package/dist/safe-bash/commands/text-programs/shared.js.map +1 -1
  103. package/dist/safe-bash/commands/text.d.ts.map +1 -1
  104. package/dist/safe-bash/commands/text.js +22 -4
  105. package/dist/safe-bash/commands/text.js.map +1 -1
  106. package/dist/safe-bash/contracts/command-requirements.d.ts +22 -0
  107. package/dist/safe-bash/contracts/command-requirements.d.ts.map +1 -0
  108. package/dist/safe-bash/contracts/command-requirements.js +47 -0
  109. package/dist/safe-bash/contracts/command-requirements.js.map +1 -0
  110. package/dist/safe-bash/contracts/command.d.ts +2 -0
  111. package/dist/safe-bash/contracts/command.d.ts.map +1 -1
  112. package/dist/safe-bash/contracts/command.js.map +1 -1
  113. package/dist/safe-bash/contracts/filesystem-output.d.ts +12 -0
  114. package/dist/safe-bash/contracts/filesystem-output.d.ts.map +1 -0
  115. package/dist/safe-bash/contracts/filesystem-output.js +160 -0
  116. package/dist/safe-bash/contracts/filesystem-output.js.map +1 -0
  117. package/dist/safe-bash/contracts/index.d.ts +1 -0
  118. package/dist/safe-bash/contracts/index.d.ts.map +1 -1
  119. package/dist/safe-bash/contracts/index.js +1 -0
  120. package/dist/safe-bash/contracts/index.js.map +1 -1
  121. package/dist/safe-bash/contracts/io.d.ts +2 -0
  122. package/dist/safe-bash/contracts/io.d.ts.map +1 -1
  123. package/dist/safe-bash/contracts/io.js +2 -0
  124. package/dist/safe-bash/contracts/io.js.map +1 -1
  125. package/dist/safe-bash/contracts/output.d.ts +1 -0
  126. package/dist/safe-bash/contracts/output.d.ts.map +1 -1
  127. package/dist/safe-bash/contracts/output.js +6 -1
  128. package/dist/safe-bash/contracts/output.js.map +1 -1
  129. package/dist/safe-bash/index.d.ts +2 -1
  130. package/dist/safe-bash/index.d.ts.map +1 -1
  131. package/dist/safe-bash/index.js +2 -0
  132. package/dist/safe-bash/index.js.map +1 -1
  133. package/dist/safe-bash/shell/runtime.d.ts +7 -2
  134. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  135. package/dist/safe-bash/shell/runtime.js +247 -185
  136. package/dist/safe-bash/shell/runtime.js.map +1 -1
  137. package/package.json +2 -2
@@ -13,6 +13,8 @@ import { byteLocale } from "./locale.js";
13
13
  import { functionDisplay } from "./display.js";
14
14
  import { ConditionalUnsupported, evaluateConditional } from "./conditional.js";
15
15
  import { invocationScope, throwCleanupFailures } from "./cleanup.js";
16
+ import { bindFileOutputBudget, openFileOutput } from "../contracts/filesystem-output.js";
17
+ import { outputFailure } from "../contracts/io.js";
16
18
  import { executionCommands } from "../commands/execution.js";
17
19
  import { cloneGetoptsState, createGetoptsState, GetoptsError, scanGetopts, withGetoptsIndex } from "./getopts.js";
18
20
  import { activateChildCancellation, prepareChildCancellation, selectRuntimeCancellationOutcome, subscribeCancellation, } from "./cancellation.js";
@@ -140,6 +142,7 @@ export class Budget {
140
142
  if (ownership?.budget === this && ownership.write === sink.write)
141
143
  return signalSink(sink, signal);
142
144
  const output = {
145
+ ...(sink[outputFailure] ? { [outputFailure]: sink[outputFailure] } : {}),
143
146
  ...(sink.ownedOutput ? { ownedOutput: {
144
147
  consumerClosed: sink.ownedOutput.consumerClosed,
145
148
  write: async (chunk) => {
@@ -299,6 +302,7 @@ function signalSink(sink, signal) {
299
302
  const owned = ownership?.write === sink.write ? ownership : undefined;
300
303
  const write = owned ? owned.write.bind(sink) : (chunk) => sink.write(chunk);
301
304
  const output = {
305
+ ...(sink[outputFailure] ? { [outputFailure]: sink[outputFailure] } : {}),
302
306
  ...(sink.ownedOutput ? { ownedOutput: {
303
307
  consumerClosed: sink.ownedOutput.consumerClosed,
304
308
  async write(chunk) { signal.throwIfAborted(); await interruptible(sink.ownedOutput.write(chunk), signal); },
@@ -1796,7 +1800,7 @@ export class Runtime {
1796
1800
  const frame = {};
1797
1801
  const runtime = new Runtime(this.fs, this.commands, this.middleware, this.budget, signal, this.fileWrites, this.outputFiles, boundary.deliverySignal, boundary, this.cancellationState, owner, childDepth, this.cancellationMaxDepth, frame);
1798
1802
  const input = new ShellInput(incoming?.readable ?? io.stdin, this.budget, signal);
1799
- const pipeOutput = outgoing && { ownedOutput: outgoing.writable.ownedOutput, write: async (chunk) => {
1803
+ const pipeOutput = outgoing && { [outputFailure]: outgoing.abort, ownedOutput: outgoing.writable.ownedOutput, write: async (chunk) => {
1800
1804
  try {
1801
1805
  await outgoing.writable.write(chunk);
1802
1806
  if (chunk.byteLength)
@@ -1962,181 +1966,192 @@ export class Runtime {
1962
1966
  this.signal.throwIfAborted();
1963
1967
  const inputs = new Set();
1964
1968
  const outputs = new Set();
1969
+ const finishOutputs = async (status) => {
1970
+ const pending = [...outputs];
1971
+ outputs.clear();
1972
+ const settled = await Promise.allSettled(pending.map(close => close({ status })));
1973
+ throwCleanupFailures(settled.filter(result => result.status === "rejected").map(result => result.reason));
1974
+ };
1965
1975
  const allocation = this.budget.values.scope();
1966
1976
  originalIO = { ...originalIO, [valueScope]: allocation };
1967
1977
  let io = originalIO;
1968
1978
  let diagnosticFailure;
1969
1979
  try {
1970
- if (command.kind === "function") {
1971
- if (state.profile === "sh" && specialBuiltinNames.has(command.name)) {
1972
- await this.diagnostic(io, `\`${command.name}': is a special builtin`);
1973
- throw new Flow("exit", 2);
1974
- }
1975
- state.functions.set(command.name, { ...command.body, sourceName: io.scriptName ?? "shell" });
1976
- return 0;
1977
- }
1978
- if (command.kind === "simple")
1979
- return await this.simple(command, state, originalIO, inputs, outputs, fileShortcut);
1980
- io = await this.redirect(command.redirects, state, io, inputs, outputs, command.kind === "subshell", command.kind !== "subshell");
1981
- if (command.kind === "conditional") {
1982
- try {
1983
- return await evaluateConditional(command.expression, {
1984
- fs: this.fs, cwd: state.cwd, signal: this.signal,
1985
- locale: state.variables.LC_ALL || state.variables.LC_COLLATE || state.variables.LANG || "C",
1986
- work: { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") },
1987
- expand: async (word, pattern = false) => (await this.word(word, state, io, false, pattern, false, pattern)).join(""),
1988
- regex: (subject, pattern) => this.ere(subject, pattern, state, io),
1989
- option: name => name === "errexit" ? !!state.errexit : name === "nounset" ? !!state.nounset : name === "pipefail" ? state.pipefail : false,
1990
- present: name => {
1991
- const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[(0|[1-9][0-9]*|[@*])\])?$/u.exec(name);
1992
- if (!match)
1993
- throw new ConditionalUnsupported("[[ variable selector: unsupported conditional profile");
1994
- const binding = arrayStore(state)?.get(match[1]);
1995
- const selector = match[2];
1996
- if (selector === "@" || selector === "*")
1997
- return binding ? binding.values.size > 0 : this.variable(state, match[1]) !== undefined;
1998
- const index = selector === undefined ? 0 : numericIndex({ decimal: selector });
1999
- if (index === undefined)
2000
- throw new ConditionalUnsupported("[[ variable index: unsupported conditional profile");
2001
- return binding ? binding.get(index) !== undefined : index === 0 && this.variable(state, match[1]) !== undefined;
2002
- },
2003
- });
1980
+ const execute = async () => {
1981
+ if (command.kind === "function") {
1982
+ if (state.profile === "sh" && specialBuiltinNames.has(command.name)) {
1983
+ await this.diagnostic(io, `\`${command.name}': is a special builtin`);
1984
+ throw new Flow("exit", 2);
1985
+ }
1986
+ state.functions.set(command.name, { ...command.body, sourceName: io.scriptName ?? "shell" });
1987
+ return 0;
2004
1988
  }
2005
- catch (error) {
2006
- this.signal.throwIfAborted();
2007
- if (error instanceof NounsetDiagnosticFailure) {
2008
- diagnosticFailure = error;
2009
- throw error;
1989
+ if (command.kind === "simple")
1990
+ return await this.simple(command, state, originalIO, inputs, outputs, fileShortcut);
1991
+ io = await this.redirect(command.redirects, state, io, inputs, outputs, command.kind === "subshell", command.kind !== "subshell");
1992
+ if (command.kind === "conditional") {
1993
+ try {
1994
+ return await evaluateConditional(command.expression, {
1995
+ fs: this.fs, cwd: state.cwd, signal: this.signal,
1996
+ locale: state.variables.LC_ALL || state.variables.LC_COLLATE || state.variables.LANG || "C",
1997
+ work: { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") },
1998
+ expand: async (word, pattern = false) => (await this.word(word, state, io, false, pattern, false, pattern)).join(""),
1999
+ regex: (subject, pattern) => this.ere(subject, pattern, state, io),
2000
+ option: name => name === "errexit" ? !!state.errexit : name === "nounset" ? !!state.nounset : name === "pipefail" ? state.pipefail : false,
2001
+ present: name => {
2002
+ const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[(0|[1-9][0-9]*|[@*])\])?$/u.exec(name);
2003
+ if (!match)
2004
+ throw new ConditionalUnsupported("[[ variable selector: unsupported conditional profile");
2005
+ const binding = arrayStore(state)?.get(match[1]);
2006
+ const selector = match[2];
2007
+ if (selector === "@" || selector === "*")
2008
+ return binding ? binding.values.size > 0 : this.variable(state, match[1]) !== undefined;
2009
+ const index = selector === undefined ? 0 : numericIndex({ decimal: selector });
2010
+ if (index === undefined)
2011
+ throw new ConditionalUnsupported("[[ variable index: unsupported conditional profile");
2012
+ return binding ? binding.get(index) !== undefined : index === 0 && this.variable(state, match[1]) !== undefined;
2013
+ },
2014
+ });
2010
2015
  }
2011
- if (error instanceof EreProfileLimitError) {
2012
- try {
2013
- await this.diagnostic(io, `[[ ${error.message}`);
2014
- }
2015
- catch (reason) {
2016
- this.signal.throwIfAborted();
2017
- if (reason instanceof ShellLimitError)
2018
- throw reason;
2019
- diagnosticFailure = new NounsetDiagnosticFailure(reason);
2020
- throw diagnosticFailure;
2016
+ catch (error) {
2017
+ this.signal.throwIfAborted();
2018
+ if (error instanceof NounsetDiagnosticFailure) {
2019
+ diagnosticFailure = error;
2020
+ throw error;
2021
2021
  }
2022
- return 3;
2023
- }
2024
- if (error instanceof ConditionalUnsupported) {
2025
- try {
2026
- await this.diagnostic(io, error.message);
2022
+ if (error instanceof EreProfileLimitError) {
2023
+ try {
2024
+ await this.diagnostic(io, `[[ ${error.message}`);
2025
+ }
2026
+ catch (reason) {
2027
+ this.signal.throwIfAborted();
2028
+ if (reason instanceof ShellLimitError)
2029
+ throw reason;
2030
+ diagnosticFailure = new NounsetDiagnosticFailure(reason);
2031
+ throw diagnosticFailure;
2032
+ }
2033
+ return 3;
2027
2034
  }
2028
- catch (reason) {
2029
- this.signal.throwIfAborted();
2030
- if (reason instanceof ShellLimitError)
2031
- throw reason;
2032
- diagnosticFailure = new NounsetDiagnosticFailure(reason);
2033
- throw diagnosticFailure;
2035
+ if (error instanceof ConditionalUnsupported) {
2036
+ try {
2037
+ await this.diagnostic(io, error.message);
2038
+ }
2039
+ catch (reason) {
2040
+ this.signal.throwIfAborted();
2041
+ if (reason instanceof ShellLimitError)
2042
+ throw reason;
2043
+ diagnosticFailure = new NounsetDiagnosticFailure(reason);
2044
+ throw diagnosticFailure;
2045
+ }
2046
+ return 2;
2034
2047
  }
2035
- return 2;
2048
+ if (error instanceof ExpansionFailure || error instanceof Flow || error instanceof ShellLimitError || error instanceof ShellSyntaxError || error instanceof ArrayFailure)
2049
+ throw error;
2050
+ diagnosticFailure = new NounsetDiagnosticFailure(error);
2051
+ throw diagnosticFailure;
2036
2052
  }
2037
- if (error instanceof ExpansionFailure || error instanceof Flow || error instanceof ShellLimitError || error instanceof ShellSyntaxError || error instanceof ArrayFailure)
2038
- throw error;
2039
- diagnosticFailure = new NounsetDiagnosticFailure(error);
2040
- throw diagnosticFailure;
2041
- }
2042
- }
2043
- if (command.kind === "arithmetic") {
2044
- try {
2045
- return Number(evaluatePositionalArithmetic(command.expression, {
2046
- positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
2047
- maximumBytes: this.budget.limits.maxExpansionBytes,
2048
- checkpoint: () => this.signal.throwIfAborted(),
2049
- requireParameter: (name, value) => this.requireParameter(value, name, state, io),
2050
- limit: () => this.budget.fail("maxExpansionBytes"),
2051
- }, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine))) === 0n);
2052
2053
  }
2053
- catch (error) {
2054
- this.rethrowArithmeticControl(error);
2055
- throw new Error(`((: ${message(error)}`);
2054
+ if (command.kind === "arithmetic") {
2055
+ try {
2056
+ return Number(evaluatePositionalArithmetic(command.expression, {
2057
+ positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
2058
+ maximumBytes: this.budget.limits.maxExpansionBytes,
2059
+ checkpoint: () => this.signal.throwIfAborted(),
2060
+ requireParameter: (name, value) => this.requireParameter(value, name, state, io),
2061
+ limit: () => this.budget.fail("maxExpansionBytes"),
2062
+ }, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine))) === 0n);
2063
+ }
2064
+ catch (error) {
2065
+ this.rethrowArithmeticControl(error);
2066
+ throw new Error(`((: ${message(error)}`);
2067
+ }
2056
2068
  }
2057
- }
2058
- if (command.kind === "subshell") {
2059
- const child = await cloneState(state, this.signal);
2060
- child.isolated = true;
2061
- child.loopDepth = 0;
2062
- try {
2063
- return await this.run(command.body, child, io);
2069
+ if (command.kind === "subshell") {
2070
+ const child = await cloneState(state, this.signal);
2071
+ child.isolated = true;
2072
+ child.loopDepth = 0;
2073
+ try {
2074
+ return await this.run(command.body, child, io);
2075
+ }
2076
+ finally {
2077
+ stateMonitor(child)?.closeValues();
2078
+ }
2064
2079
  }
2065
- finally {
2066
- stateMonitor(child)?.closeValues();
2080
+ if (command.kind === "group")
2081
+ return await this.script(command.body, state, io);
2082
+ if (command.kind === "if") {
2083
+ for (const branch of command.branches) {
2084
+ if (await this.script(branch.condition, state, { ...io, execution: { ignoreErrexit: true } }) === 0)
2085
+ return await this.script(branch.body, state, io);
2086
+ }
2087
+ return command.otherwise ? await this.script(command.otherwise, state, io) : 0;
2067
2088
  }
2068
- }
2069
- if (command.kind === "group")
2070
- return await this.script(command.body, state, io);
2071
- if (command.kind === "if") {
2072
- for (const branch of command.branches) {
2073
- if (await this.script(branch.condition, state, { ...io, execution: { ignoreErrexit: true } }) === 0)
2074
- return await this.script(branch.body, state, io);
2089
+ if (command.kind === "case") {
2090
+ const subject = (await this.word(command.subject, state, io, false)).join("");
2091
+ const work = { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
2092
+ let status = 0;
2093
+ let fallthrough = false;
2094
+ let patterns = 0;
2095
+ for (const clause of command.clauses) {
2096
+ let matched = fallthrough;
2097
+ if (!matched)
2098
+ for (const word of clause.patterns) {
2099
+ if (++patterns % 128 === 0)
2100
+ await yieldTurn(this.signal);
2101
+ const pattern = (await this.word(word, state, io, false, true)).join("");
2102
+ if (await matchesPattern(pattern, subject, work)) {
2103
+ matched = true;
2104
+ break;
2105
+ }
2106
+ }
2107
+ if (!matched)
2108
+ continue;
2109
+ if (clause.body.lists.length)
2110
+ status = await this.script(clause.body, state, io);
2111
+ if (clause.terminator === ";;" || clause.terminator === "esac")
2112
+ break;
2113
+ fallthrough = clause.terminator === ";&";
2114
+ }
2115
+ return status;
2075
2116
  }
2076
- return command.otherwise ? await this.script(command.otherwise, state, io) : 0;
2077
- }
2078
- if (command.kind === "case") {
2079
- const subject = (await this.word(command.subject, state, io, false)).join("");
2080
- const work = { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
2081
2117
  let status = 0;
2082
- let fallthrough = false;
2083
- let patterns = 0;
2084
- for (const clause of command.clauses) {
2085
- let matched = fallthrough;
2086
- if (!matched)
2087
- for (const word of clause.patterns) {
2088
- if (++patterns % 128 === 0)
2089
- await yieldTurn(this.signal);
2090
- const pattern = (await this.word(word, state, io, false, true)).join("");
2091
- if (await matchesPattern(pattern, subject, work)) {
2092
- matched = true;
2118
+ const loopRestoration = stateMonitor(state)?.restoration();
2119
+ state.loopDepth++;
2120
+ try {
2121
+ if (command.kind === "for") {
2122
+ const values = command.words ? await this.valueWords(command.words, state, io) : this.positionalValues(state);
2123
+ for (const value of values) {
2124
+ this.budget.loop();
2125
+ await this.assignVariable(state, command.name, value);
2126
+ const result = await this.loopBody(command.body, state, io);
2127
+ status = result.status;
2128
+ if (result.stop)
2093
2129
  break;
2094
- }
2095
2130
  }
2096
- if (!matched)
2097
- continue;
2098
- if (clause.body.lists.length)
2099
- status = await this.script(clause.body, state, io);
2100
- if (clause.terminator === ";;" || clause.terminator === "esac")
2101
- break;
2102
- fallthrough = clause.terminator === ";&";
2103
- }
2104
- return status;
2105
- }
2106
- let status = 0;
2107
- const loopRestoration = stateMonitor(state)?.restoration();
2108
- state.loopDepth++;
2109
- try {
2110
- if (command.kind === "for") {
2111
- const values = command.words ? await this.valueWords(command.words, state, io) : this.positionalValues(state);
2112
- for (const value of values) {
2113
- this.budget.loop();
2114
- await this.assignVariable(state, command.name, value);
2115
- const result = await this.loopBody(command.body, state, io);
2116
- status = result.status;
2117
- if (result.stop)
2118
- break;
2119
2131
  }
2120
- }
2121
- else {
2122
- while (true) {
2123
- this.budget.loop();
2124
- const condition = await this.script(command.condition, state, { ...io, execution: { ignoreErrexit: true } });
2125
- if ((condition === 0) !== (command.kind === "while"))
2126
- break;
2127
- const result = await this.loopBody(command.body, state, io);
2128
- status = result.status;
2129
- if (result.stop)
2130
- break;
2132
+ else {
2133
+ while (true) {
2134
+ this.budget.loop();
2135
+ const condition = await this.script(command.condition, state, { ...io, execution: { ignoreErrexit: true } });
2136
+ if ((condition === 0) !== (command.kind === "while"))
2137
+ break;
2138
+ const result = await this.loopBody(command.body, state, io);
2139
+ status = result.status;
2140
+ if (result.stop)
2141
+ break;
2142
+ }
2131
2143
  }
2132
2144
  }
2133
- }
2134
- finally {
2135
- if (loopRestoration)
2136
- loopRestoration.apply(() => { state.loopDepth--; });
2137
- else
2138
- state.loopDepth--;
2139
- }
2145
+ finally {
2146
+ if (loopRestoration)
2147
+ loopRestoration.apply(() => { state.loopDepth--; });
2148
+ else
2149
+ state.loopDepth--;
2150
+ }
2151
+ return status;
2152
+ };
2153
+ const status = await execute();
2154
+ await finishOutputs(status);
2140
2155
  return status;
2141
2156
  }
2142
2157
  catch (caught) {
@@ -2147,6 +2162,8 @@ export class Runtime {
2147
2162
  if (error instanceof NounsetDiagnosticFailure)
2148
2163
  diagnosticFailure = error;
2149
2164
  this.signal.throwIfAborted();
2165
+ if (error instanceof Flow)
2166
+ await finishOutputs(error.status);
2150
2167
  if (error instanceof Flow || error instanceof ShellLimitError || error instanceof ShellSyntaxError)
2151
2168
  throw error;
2152
2169
  this.clearOutcomeReport();
@@ -2197,7 +2214,7 @@ export class Runtime {
2197
2214
  }
2198
2215
  finally {
2199
2216
  await Promise.allSettled([
2200
- ...[...outputs].map(async (close) => close()),
2217
+ ...[...outputs].map(async (close) => close({ reason: new FsError("ECANCELED", { syscall: "redirect" }) })),
2201
2218
  ...[...inputs].map(async (input) => input.close()),
2202
2219
  ]).then(results => {
2203
2220
  const failures = results.filter(result => result.status === "rejected").map(result => result.reason);
@@ -2358,48 +2375,91 @@ export class Runtime {
2358
2375
  }
2359
2376
  else {
2360
2377
  const append = redirect.operator === ">>";
2378
+ const capabilities = await this.fs.capabilitiesFor?.(path, options) ?? this.fs.capabilities;
2379
+ const random = capabilities.randomAccessWrite === true;
2361
2380
  let file;
2362
2381
  await this.fileOperation(path, async () => {
2363
- await this.fs.writeFile(path, new Uint8Array(), { ...options, flag: append ? "a" : "w" });
2364
2382
  file = this.outputFiles.get(path) ?? { data: undefined, references: 0 };
2365
- if (!append)
2366
- file.data = new Uint8Array();
2383
+ if (!random && file.references)
2384
+ throw new FsError("ENOTSUP", { path, message: "Conflicting sequential output descriptors" });
2367
2385
  file.references++;
2368
2386
  this.outputFiles.set(path, file);
2369
2387
  });
2370
2388
  let closed = false;
2371
- outputs.add(() => {
2372
- closed = true;
2373
- if (--file.references === 0 && this.outputFiles.get(path) === file)
2374
- this.outputFiles.delete(path);
2375
- });
2376
2389
  let offset = 0;
2377
- const output = this.budget.sink({ write: (chunk) => {
2378
- const copy = new Uint8Array(chunk);
2379
- return this.fileOperation(path, async () => {
2380
- if (closed)
2381
- throw new Error("Output descriptor is closed");
2382
- const current = file.data;
2383
- if (append) {
2384
- await this.fs.appendFile(path, copy, options);
2385
- if (current) {
2386
- const bytes = new Uint8Array(current.length + copy.length);
2387
- bytes.set(current);
2388
- bytes.set(copy, current.length);
2390
+ const incremental = async () => {
2391
+ await this.fileOperation(path, async () => {
2392
+ if (append)
2393
+ await this.fs.appendFile(path, new Uint8Array(), options);
2394
+ else
2395
+ await this.fs.writeFile(path, new Uint8Array(), { ...options, flag: "w" });
2396
+ if (!append)
2397
+ file.data = new Uint8Array();
2398
+ });
2399
+ return { write: (chunk) => {
2400
+ const copy = new Uint8Array(chunk);
2401
+ return this.fileOperation(path, async () => {
2402
+ if (closed)
2403
+ throw new Error("Output descriptor is closed");
2404
+ const current = file.data;
2405
+ if (append) {
2406
+ await this.fs.appendFile(path, copy, options);
2407
+ if (current) {
2408
+ const bytes = new Uint8Array(current.length + copy.length);
2409
+ bytes.set(current);
2410
+ bytes.set(copy, current.length);
2411
+ file.data = bytes;
2412
+ }
2413
+ }
2414
+ else {
2415
+ const bytes = new Uint8Array(Math.max(current?.length ?? 0, offset + copy.length));
2416
+ if (current)
2417
+ bytes.set(current);
2418
+ bytes.set(copy, offset);
2419
+ await this.fs.writeFile(path, bytes, options);
2389
2420
  file.data = bytes;
2421
+ offset += copy.length;
2390
2422
  }
2423
+ });
2424
+ } };
2425
+ };
2426
+ const release = () => {
2427
+ if (closed)
2428
+ return;
2429
+ closed = true;
2430
+ if (--file.references === 0 && this.outputFiles.get(path) === file)
2431
+ this.outputFiles.delete(path);
2432
+ };
2433
+ let target;
2434
+ try {
2435
+ target = await openFileOutput({ fs: this.fs, signal: this.signal, registerCleanup: cleanup => io[invocationScope].register(cleanup) }, path, append ? "a" : "w", random ? incremental : undefined);
2436
+ }
2437
+ catch (error) {
2438
+ release();
2439
+ throw error;
2440
+ }
2441
+ outputs.add(async (completion) => {
2442
+ try {
2443
+ if (this.signal.aborted)
2444
+ await target.abort(this.signal.reason);
2445
+ else if ("reason" in completion)
2446
+ await target.abort(completion.reason);
2447
+ else {
2448
+ try {
2449
+ await target.finish();
2391
2450
  }
2392
- else {
2393
- const bytes = new Uint8Array(Math.max(current?.length ?? 0, offset + copy.length));
2394
- if (current)
2395
- bytes.set(current);
2396
- bytes.set(copy, offset);
2397
- await this.fs.writeFile(path, bytes, options);
2398
- file.data = bytes;
2399
- offset += copy.length;
2451
+ catch (error) {
2452
+ this.signal.throwIfAborted();
2453
+ if (completion.status === 0)
2454
+ throw error;
2400
2455
  }
2401
- });
2402
- } }, this.signal);
2456
+ }
2457
+ }
2458
+ finally {
2459
+ release();
2460
+ }
2461
+ });
2462
+ const output = this.budget.sink(target.sink, this.signal);
2403
2463
  descriptors.set(redirect.descriptor, { output });
2404
2464
  if (redirect.operator === "&>") {
2405
2465
  replaced.add(2);
@@ -2657,6 +2717,7 @@ export class Runtime {
2657
2717
  return invocation;
2658
2718
  },
2659
2719
  };
2720
+ bindFileOutputBudget(context, sink => this.budget.sink(sink, this.signal));
2660
2721
  if (argumentValues.values.every(value => typeof value === "string"))
2661
2722
  Reflect.deleteProperty(context, "argumentValues");
2662
2723
  const middleware = this.middleware.map((handler) => (context, next) => {
@@ -3301,6 +3362,7 @@ export class Runtime {
3301
3362
  return invocation;
3302
3363
  },
3303
3364
  };
3365
+ bindFileOutputBudget(context, sink => this.budget.sink(sink, runtime.signal));
3304
3366
  if (argumentValues.values.every(value => typeof value === "string"))
3305
3367
  Reflect.deleteProperty(context, "argumentValues");
3306
3368
  const child = await runtime.shebangState(context, state);