@poe-platform/safe-bash 0.1.601 → 0.1.603
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/safe-bash/chunks/{chunk-IYFB6VED.js → chunk-4BYUV5PA.js} +6 -4
- package/dist/safe-bash/chunks/{chunk-IYFB6VED.js.map → chunk-4BYUV5PA.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-PE5NZIA2.js → chunk-6LSSEOG7.js} +3 -3
- package/dist/safe-bash/chunks/{chunk-3Y5PKSHX.js → chunk-AD7UAP5B.js} +10 -8
- package/dist/safe-bash/chunks/{chunk-3Y5PKSHX.js.map → chunk-AD7UAP5B.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-XBLVOMEJ.js → chunk-AFH57PPL.js} +2 -2
- package/dist/safe-bash/chunks/{chunk-FMTBJ7SG.js → chunk-CEPNDODE.js} +3 -3
- package/dist/safe-bash/chunks/chunk-CXMIIXJA.js +178 -0
- package/dist/safe-bash/chunks/chunk-CXMIIXJA.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-E3NZKSTA.js → chunk-F5TCC4UG.js} +12 -10
- package/dist/safe-bash/chunks/{chunk-E3NZKSTA.js.map → chunk-F5TCC4UG.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-TJVKGNKA.js → chunk-FWDMJTQO.js} +9 -7
- package/dist/safe-bash/chunks/{chunk-TJVKGNKA.js.map → chunk-FWDMJTQO.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-LHKAA57T.js → chunk-GFZKQIPL.js} +8 -6
- package/dist/safe-bash/chunks/{chunk-LHKAA57T.js.map → chunk-GFZKQIPL.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-W3TLNFDB.js → chunk-HHJSJWUD.js} +9 -7
- package/dist/safe-bash/chunks/{chunk-W3TLNFDB.js.map → chunk-HHJSJWUD.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-XYVCXUGM.js → chunk-KZ7QCYNT.js} +10 -8
- package/dist/safe-bash/chunks/{chunk-XYVCXUGM.js.map → chunk-KZ7QCYNT.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-HQCBEAFK.js → chunk-N5CYTGBN.js} +10 -8
- package/dist/safe-bash/chunks/{chunk-HQCBEAFK.js.map → chunk-N5CYTGBN.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-H6E6F5KJ.js → chunk-PCLOK32J.js} +9 -7
- package/dist/safe-bash/chunks/{chunk-H6E6F5KJ.js.map → chunk-PCLOK32J.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-BXVGCJXW.js → chunk-PFMSKAFQ.js} +12 -10
- package/dist/safe-bash/chunks/{chunk-BXVGCJXW.js.map → chunk-PFMSKAFQ.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-PMBBUNNN.js → chunk-PW4WQSCS.js} +11 -9
- package/dist/safe-bash/chunks/{chunk-PMBBUNNN.js.map → chunk-PW4WQSCS.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-OECKXHVJ.js → chunk-RNKTWFEM.js} +2 -2
- package/dist/safe-bash/chunks/{chunk-2PQ3XOCQ.js → chunk-UYX72YHY.js} +4 -172
- package/dist/safe-bash/chunks/chunk-UYX72YHY.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-QAZN7SAN.js → chunk-WPEMUXV4.js} +7 -5
- package/dist/safe-bash/chunks/{chunk-QAZN7SAN.js.map → chunk-WPEMUXV4.js.map} +1 -1
- package/dist/safe-bash/commands/csplit/index.browser.js +5 -4
- package/dist/safe-bash/commands/factor/index.browser.js +4 -3
- package/dist/safe-bash/commands/getopt/index.browser.js +4 -3
- package/dist/safe-bash/commands/hexdump/index.browser.js +4 -3
- package/dist/safe-bash/commands/iconv/index.browser.js +4 -3
- package/dist/safe-bash/commands/line-endings/index.browser.js +5 -4
- package/dist/safe-bash/commands/llm/index.browser.js +5 -4
- package/dist/safe-bash/commands/network/index.browser.js +6 -5
- package/dist/safe-bash/commands/op/index.browser.js +5664 -0
- package/dist/safe-bash/commands/op/index.browser.js.map +7 -0
- package/dist/safe-bash/commands/op/index.d.ts +14 -0
- package/dist/safe-bash/commands/op/index.d.ts.map +1 -0
- package/dist/safe-bash/commands/op/index.js +4684 -0
- package/dist/safe-bash/commands/op/index.js.map +7 -0
- package/dist/safe-bash/commands/op/op.test.d.ts +2 -0
- package/dist/safe-bash/commands/op/op.test.d.ts.map +1 -0
- package/dist/safe-bash/commands/op/op.test.js +159 -0
- package/dist/safe-bash/commands/op/op.test.js.map +1 -0
- package/dist/safe-bash/commands/pr/index.browser.js +4 -3
- package/dist/safe-bash/commands/tsort/index.browser.js +4 -3
- package/dist/safe-bash/commands/xml/index.browser.js +5 -4
- package/dist/safe-bash/commands/yq/index.browser.js +6 -5
- package/dist/safe-bash/core.browser.js +455 -101
- package/dist/safe-bash/core.browser.js.map +4 -4
- package/dist/safe-bash/index.d.ts +1 -0
- package/dist/safe-bash/index.d.ts.map +1 -1
- package/dist/safe-bash/index.js +1 -0
- package/dist/safe-bash/index.js.map +1 -1
- package/dist/safe-bash/internal/op/admin.d.ts +31 -0
- package/dist/safe-bash/internal/op/backend.d.ts +8 -0
- package/dist/safe-bash/internal/op/cli.d.ts +80 -0
- package/dist/safe-bash/internal/op/documents.d.ts +6 -0
- package/dist/safe-bash/internal/op/encoding.d.ts +15 -0
- package/dist/safe-bash/internal/op/environment-commands.d.ts +11 -0
- package/dist/safe-bash/internal/op/environment.d.ts +9 -0
- package/dist/safe-bash/internal/op/file-mode.d.ts +1 -0
- package/dist/safe-bash/internal/op/host-contracts.d.ts +12 -0
- package/dist/safe-bash/internal/op/index.d.ts +19 -0
- package/dist/safe-bash/internal/op/items.d.ts +3 -0
- package/dist/safe-bash/internal/op/references.d.ts +4 -0
- package/dist/safe-bash/internal/op/secrets.d.ts +13 -0
- package/dist/safe-bash/internal/op/templates.d.ts +10 -0
- package/dist/safe-bash/internal/op/types.d.ts +202 -0
- package/dist/safe-bash/shell/arrays/state.js +1 -1
- package/dist/safe-bash/shell/arrays/state.js.map +1 -1
- package/dist/safe-bash/shell/background-jobs.d.ts +39 -0
- package/dist/safe-bash/shell/background-jobs.d.ts.map +1 -0
- package/dist/safe-bash/shell/background-jobs.js +85 -0
- package/dist/safe-bash/shell/background-jobs.js.map +1 -0
- package/dist/safe-bash/shell/display.js +7 -7
- package/dist/safe-bash/shell/display.js.map +1 -1
- package/dist/safe-bash/shell/parser.d.ts +1 -0
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +8 -8
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +7 -1
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +295 -46
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/dist/safe-bash/shell/shell.d.ts.map +1 -1
- package/dist/safe-bash/shell/shell.js +11 -2
- package/dist/safe-bash/shell/shell.js.map +1 -1
- package/package.json +9 -2
- package/dist/safe-bash/chunks/chunk-2PQ3XOCQ.js.map +0 -7
- /package/dist/safe-bash/chunks/{chunk-PE5NZIA2.js.map → chunk-6LSSEOG7.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-XBLVOMEJ.js.map → chunk-AFH57PPL.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-FMTBJ7SG.js.map → chunk-CEPNDODE.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-OECKXHVJ.js.map → chunk-RNKTWFEM.js.map} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BackgroundJobs, BackgroundResources, backgroundResources, backgroundPrune, backgroundDestination, forwardBackgroundDestination } from "./background-jobs.js";
|
|
1
2
|
import { PublicDiagnostic, publicDiagnosticMessage } from "../diagnostics.js";
|
|
2
3
|
import { writeDiagnostic } from "../escaping.js";
|
|
3
4
|
import { cancelTurn, monotonicNow, registerYieldCheckpoint, scheduleTurn, yieldTurn } from "../contracts/yield.js";
|
|
@@ -62,7 +63,7 @@ export const defaultLimits = {
|
|
|
62
63
|
};
|
|
63
64
|
const shellBuiltinNames = new Set([
|
|
64
65
|
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read", "declare", "mapfile", "readarray",
|
|
65
|
-
"exit", "return", "break", "continue", "command", "builtin", "type", "readonly", "echo", "printf", "test", "[", ".", "source", "eval", "getopts", "let", "pushd", "dirs", "popd", "shopt",
|
|
66
|
+
"wait", "exit", "return", "break", "continue", "command", "builtin", "type", "readonly", "echo", "printf", "test", "[", ".", "source", "eval", "getopts", "let", "pushd", "dirs", "popd", "shopt",
|
|
66
67
|
]);
|
|
67
68
|
const implementedBuiltins = new Set([...shellBuiltinNames].filter(name => !["echo", "printf", "test", "["].includes(name)));
|
|
68
69
|
const specialBuiltinNames = new Set([":", ".", "break", "continue", "eval", "exit", "export", "readonly", "return", "set", "shift", "unset"]);
|
|
@@ -81,6 +82,8 @@ export function resolveLimits(...limits) {
|
|
|
81
82
|
}
|
|
82
83
|
return result;
|
|
83
84
|
}
|
|
85
|
+
const runtimeFileSystems = new WeakMap();
|
|
86
|
+
const inputScopes = new WeakMap();
|
|
84
87
|
const budgetedSinks = new WeakMap();
|
|
85
88
|
async function sortExpansionStrings(values, work) {
|
|
86
89
|
const compare = async (left, right) => {
|
|
@@ -249,6 +252,7 @@ export class Budget {
|
|
|
249
252
|
await interruptible(sink.write(chunk), signal);
|
|
250
253
|
},
|
|
251
254
|
};
|
|
255
|
+
forwardBackgroundDestination(output, sink);
|
|
252
256
|
budgetedSinks.set(output, { budget: this, write: output.write, ...(ownership?.write === sink.write && ownership.file ? { file: ownership.file } : {}) });
|
|
253
257
|
return output;
|
|
254
258
|
}
|
|
@@ -425,6 +429,7 @@ function signalSink(sink, signal) {
|
|
|
425
429
|
} } : {}),
|
|
426
430
|
async write(chunk) { signal.throwIfAborted(); await interruptible(write(chunk), signal); },
|
|
427
431
|
};
|
|
432
|
+
forwardBackgroundDestination(output, sink);
|
|
428
433
|
if (owned)
|
|
429
434
|
budgetedSinks.set(output, { ...owned, write: output.write });
|
|
430
435
|
return output;
|
|
@@ -441,6 +446,7 @@ function bindCommandIO(context) {
|
|
|
441
446
|
async function cloneState(state, signal, scope, inheritLocals = true) {
|
|
442
447
|
const destination = await snapshotState(state, () => ({
|
|
443
448
|
...state,
|
|
449
|
+
...(state.backgroundJobs ? { backgroundJobs: new BackgroundJobs(state.backgroundJobs.execution) } : {}),
|
|
444
450
|
variables: Object.assign(Object.create(null), state.variables),
|
|
445
451
|
exported: new Set(state.exported), functions: new Map(state.functions), positional: [...state.positional],
|
|
446
452
|
readonlyVariables: new Set(state.readonlyVariables),
|
|
@@ -1147,6 +1153,7 @@ export class Runtime {
|
|
|
1147
1153
|
this.outcomeFrame = outcomeFrame;
|
|
1148
1154
|
this.inputProfile = inputProfile;
|
|
1149
1155
|
this.fs = scopeFileSystem(fs, () => budget.fileSystemOperation(), signal, () => budget.fileSystemCleanupOperation());
|
|
1156
|
+
runtimeFileSystems.set(this.fs, runtimeFileSystems.get(fs) ?? fs);
|
|
1150
1157
|
const checkpoint = () => budget.cpuCheckpoint();
|
|
1151
1158
|
registerYieldCheckpoint(signal, checkpoint);
|
|
1152
1159
|
registerYieldCheckpoint(commandSignal, checkpoint);
|
|
@@ -1934,6 +1941,68 @@ export class Runtime {
|
|
|
1934
1941
|
}
|
|
1935
1942
|
async script(script, state, io) {
|
|
1936
1943
|
for (const list of script.lists) {
|
|
1944
|
+
if (list.background) {
|
|
1945
|
+
const jobs = state.backgroundJobs;
|
|
1946
|
+
const id = jobs.execution.identifier();
|
|
1947
|
+
const scope = jobs.execution.scope.child();
|
|
1948
|
+
const inheritedDestinations = new Set([io.stdout, io.stderr, ...[...io.descriptors?.values() ?? []].flatMap(descriptor => descriptor.output && !descriptor.closed ? [descriptor.output] : [])].map(backgroundDestination));
|
|
1949
|
+
const leases = (io[backgroundResources] ?? []).filter(resource => !resource.destination || inheritedDestinations.has(resource.destination)).map(resource => ({ resource, release: resource.retain() }));
|
|
1950
|
+
const releases = leases.map(lease => lease.release);
|
|
1951
|
+
let child;
|
|
1952
|
+
try {
|
|
1953
|
+
child = await cloneState(state, this.signal, scope);
|
|
1954
|
+
}
|
|
1955
|
+
catch (error) {
|
|
1956
|
+
releases.forEach(release => release());
|
|
1957
|
+
await scope.close();
|
|
1958
|
+
throw error;
|
|
1959
|
+
}
|
|
1960
|
+
child.isolated = true;
|
|
1961
|
+
child.loopDepth = 0;
|
|
1962
|
+
const inherited = isolateIO(activeIO(io));
|
|
1963
|
+
const redirectsInput = list.pipelines[0].commands[0].redirects.some(redirect => redirect.descriptor === 0);
|
|
1964
|
+
const stdin = redirectsInput ? inherited.stdin : toByteSource("");
|
|
1965
|
+
const descriptors = new Map(inherited.descriptors);
|
|
1966
|
+
descriptors.set(0, { input: stdin, stdinIsDefault: false });
|
|
1967
|
+
let initialRedirection = true;
|
|
1968
|
+
const backgroundIO = { ...inherited, [invocationScope]: scope, stdin, stdinIsDefault: false, descriptors,
|
|
1969
|
+
[backgroundPrune]: list.pipelines.length === 1 ? descriptors => {
|
|
1970
|
+
if (!initialRedirection)
|
|
1971
|
+
return;
|
|
1972
|
+
initialRedirection = false;
|
|
1973
|
+
const destinations = new Set([...descriptors.values()].flatMap(descriptor => descriptor.output && !descriptor.closed ? [backgroundDestination(descriptor.output)] : []));
|
|
1974
|
+
for (const lease of leases)
|
|
1975
|
+
if (lease.resource.destination && !destinations.has(lease.resource.destination))
|
|
1976
|
+
lease.release();
|
|
1977
|
+
} : undefined,
|
|
1978
|
+
};
|
|
1979
|
+
const runtime = new Runtime(runtimeFileSystems.get(this.fs) ?? this.fs, this.commands, this.middleware, this.budget, AbortSignal.any([this.commandSignal, scope.signal]), this.fileWrites, this.outputFiles, this.commandSignal, this.cancellation, this.cancellationState, this.cancellationOwner, this.cancellationDepth, this.cancellationMaxDepth, undefined, this.inputProfile);
|
|
1980
|
+
try {
|
|
1981
|
+
state.lastBackgroundJob = jobs.launch(id, async () => {
|
|
1982
|
+
try {
|
|
1983
|
+
try {
|
|
1984
|
+
return (await runtime.runUnit({ lists: [{ ...list, background: false }] }, child, backgroundIO)).exitCode;
|
|
1985
|
+
}
|
|
1986
|
+
catch (error) {
|
|
1987
|
+
if (error instanceof Flow && error.kind === "return")
|
|
1988
|
+
return error.status;
|
|
1989
|
+
throw error;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
finally {
|
|
1993
|
+
releases.forEach(release => release());
|
|
1994
|
+
await scope.close();
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
catch (error) {
|
|
1999
|
+
releases.forEach(release => release());
|
|
2000
|
+
await scope.close();
|
|
2001
|
+
throw error;
|
|
2002
|
+
}
|
|
2003
|
+
state.status = 0;
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
1937
2006
|
for (let index = 0; index < list.pipelines.length; index++) {
|
|
1938
2007
|
const operator = list.operators[index - 1];
|
|
1939
2008
|
if ((operator === "&&" && state.status !== 0) || (operator === "||" && state.status === 0))
|
|
@@ -1979,6 +2048,7 @@ export class Runtime {
|
|
|
1979
2048
|
const completed = new Set();
|
|
1980
2049
|
const closing = new Set();
|
|
1981
2050
|
let statuses;
|
|
2051
|
+
const stageLeases = [];
|
|
1982
2052
|
try {
|
|
1983
2053
|
for (let index = 1; index < pipeline.commands.length; index++)
|
|
1984
2054
|
pipes.push(createBytePipe({
|
|
@@ -1986,7 +2056,11 @@ export class Runtime {
|
|
|
1986
2056
|
}));
|
|
1987
2057
|
for (let index = 0; index < pipeline.commands.length; index++)
|
|
1988
2058
|
controllers.push(new AbortController());
|
|
2059
|
+
stageLeases.push(...pipeline.commands.map(() => (io[backgroundResources] ?? []).filter(resource => resource.destination).map(resource => ({ resource, release: resource.retain() }))));
|
|
2060
|
+
io[backgroundPrune]?.(new Map());
|
|
1989
2061
|
const tasks = pipeline.commands.map(async (command, index) => {
|
|
2062
|
+
let initialRedirection = true;
|
|
2063
|
+
const inheritedLeases = stageLeases[index];
|
|
1990
2064
|
const incoming = pipes[index - 1];
|
|
1991
2065
|
const outgoing = pipes[index];
|
|
1992
2066
|
const childDepth = this.cancellationDepth + 1;
|
|
@@ -2022,6 +2096,8 @@ export class Runtime {
|
|
|
2022
2096
|
throw error;
|
|
2023
2097
|
}
|
|
2024
2098
|
} };
|
|
2099
|
+
const resources = new BackgroundResources();
|
|
2100
|
+
resources.destination = backgroundDestination(pipeOutput ?? io.stdout);
|
|
2025
2101
|
const executeStage = async () => {
|
|
2026
2102
|
try {
|
|
2027
2103
|
let exitCode;
|
|
@@ -2030,6 +2106,16 @@ export class Runtime {
|
|
|
2030
2106
|
child.isolated = true;
|
|
2031
2107
|
const work = runtime.runCommandIsolated(command, child, {
|
|
2032
2108
|
...isolateIO(io),
|
|
2109
|
+
[backgroundResources]: [...io[backgroundResources] ?? [], resources],
|
|
2110
|
+
[backgroundPrune]: descriptors => {
|
|
2111
|
+
if (!initialRedirection)
|
|
2112
|
+
return;
|
|
2113
|
+
initialRedirection = false;
|
|
2114
|
+
const destinations = new Set([...descriptors.values()].flatMap(descriptor => descriptor.output && !descriptor.closed ? [backgroundDestination(descriptor.output)] : []));
|
|
2115
|
+
for (const lease of inheritedLeases)
|
|
2116
|
+
if (!destinations.has(lease.resource.destination))
|
|
2117
|
+
lease.release();
|
|
2118
|
+
},
|
|
2033
2119
|
stdin: input,
|
|
2034
2120
|
...(incoming ? { stdinIsDefault: false } : {}),
|
|
2035
2121
|
stdout: pipeOutput ? this.budget.sink(pipeOutput, signal) : signalSink(io.stdout, signal),
|
|
@@ -2037,6 +2123,8 @@ export class Runtime {
|
|
|
2037
2123
|
}).finally(() => stateMonitor(child)?.closeValues());
|
|
2038
2124
|
retain(work);
|
|
2039
2125
|
exitCode = await interruptible(work, signal);
|
|
2126
|
+
if (resources.busy)
|
|
2127
|
+
await interruptible(resources.drain(), signal);
|
|
2040
2128
|
}
|
|
2041
2129
|
catch (error) {
|
|
2042
2130
|
if (!(error instanceof PipelineClosed))
|
|
@@ -2046,6 +2134,7 @@ export class Runtime {
|
|
|
2046
2134
|
return { exitCode };
|
|
2047
2135
|
}
|
|
2048
2136
|
finally {
|
|
2137
|
+
inheritedLeases.forEach(lease => lease.release());
|
|
2049
2138
|
completed.add(index);
|
|
2050
2139
|
if (incoming) {
|
|
2051
2140
|
const upstream = index - 1;
|
|
@@ -2083,6 +2172,9 @@ export class Runtime {
|
|
|
2083
2172
|
}
|
|
2084
2173
|
finally {
|
|
2085
2174
|
try {
|
|
2175
|
+
for (const leases of stageLeases)
|
|
2176
|
+
for (const lease of leases)
|
|
2177
|
+
lease.release();
|
|
2086
2178
|
for (const close of closing)
|
|
2087
2179
|
cancelTurn(close);
|
|
2088
2180
|
for (const [index, controller] of controllers.entries())
|
|
@@ -2171,13 +2263,22 @@ export class Runtime {
|
|
|
2171
2263
|
if (this.budget.commands % 128 === 0)
|
|
2172
2264
|
await yieldTurn(this.signal);
|
|
2173
2265
|
this.signal.throwIfAborted();
|
|
2266
|
+
const resources = new BackgroundResources();
|
|
2267
|
+
originalIO = { ...originalIO, [backgroundResources]: [...originalIO[backgroundResources] ?? [], resources] };
|
|
2174
2268
|
const inputs = new Set();
|
|
2175
2269
|
const outputs = new Set();
|
|
2176
2270
|
const finishOutputs = async (status) => {
|
|
2177
2271
|
const pending = [...outputs];
|
|
2178
2272
|
outputs.clear();
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2273
|
+
const finish = async () => {
|
|
2274
|
+
await resources.drain();
|
|
2275
|
+
const settled = await Promise.allSettled(pending.map(close => close({ status })));
|
|
2276
|
+
throwCleanupFailures(settled.filter(result => result.status === "rejected").map(result => result.reason));
|
|
2277
|
+
};
|
|
2278
|
+
if (resources.busy)
|
|
2279
|
+
state.backgroundJobs.execution.track(finish());
|
|
2280
|
+
else
|
|
2281
|
+
await finish();
|
|
2181
2282
|
};
|
|
2182
2283
|
const allocation = this.budget.values.scope();
|
|
2183
2284
|
originalIO = { ...originalIO, [valueScope]: allocation };
|
|
@@ -2518,16 +2619,28 @@ export class Runtime {
|
|
|
2518
2619
|
}
|
|
2519
2620
|
finally {
|
|
2520
2621
|
try {
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2622
|
+
const close = async () => {
|
|
2623
|
+
await resources.drain();
|
|
2624
|
+
await Promise.allSettled([
|
|
2625
|
+
...[...outputs].map(async (close) => close({ reason: new FsError("ECANCELED", { syscall: "redirect" }) })),
|
|
2626
|
+
...[...inputs].map(async (input) => { try {
|
|
2627
|
+
await input.close();
|
|
2628
|
+
}
|
|
2629
|
+
finally {
|
|
2630
|
+
await inputScopes.get(input)?.close();
|
|
2631
|
+
} }),
|
|
2632
|
+
]).then(results => {
|
|
2633
|
+
const failures = results.filter(result => result.status === "rejected").map(result => result.reason);
|
|
2634
|
+
if (diagnosticFailure)
|
|
2635
|
+
io[invocationScope].failures.push(...failures);
|
|
2636
|
+
else
|
|
2637
|
+
throwCleanupFailures(failures);
|
|
2638
|
+
}).finally(() => allocation.close());
|
|
2639
|
+
};
|
|
2640
|
+
if (resources.busy)
|
|
2641
|
+
state.backgroundJobs.execution.track(close());
|
|
2642
|
+
else
|
|
2643
|
+
await close();
|
|
2531
2644
|
}
|
|
2532
2645
|
finally {
|
|
2533
2646
|
finishSnapshot?.();
|
|
@@ -2614,6 +2727,8 @@ export class Runtime {
|
|
|
2614
2727
|
const stdinIsDefault = descriptor?.input ? descriptor.stdinIsDefault : false;
|
|
2615
2728
|
return {
|
|
2616
2729
|
[invocationScope]: io[invocationScope],
|
|
2730
|
+
[backgroundResources]: io[backgroundResources],
|
|
2731
|
+
[backgroundPrune]: io[backgroundPrune],
|
|
2617
2732
|
...(io[valueScope] === undefined ? {} : { [valueScope]: io[valueScope] }),
|
|
2618
2733
|
...(io.execution === undefined ? {} : { execution: io.execution }),
|
|
2619
2734
|
...(io.diagnosticLine === undefined ? {} : { diagnosticLine: io.diagnosticLine }),
|
|
@@ -2692,15 +2807,29 @@ export class Runtime {
|
|
|
2692
2807
|
const stat = await interruptible(this.fs.stat(path, options), this.signal);
|
|
2693
2808
|
if (stat.type === "directory" && !fileShortcut)
|
|
2694
2809
|
throw new PublicDiagnostic(`${target}: Is a directory`);
|
|
2695
|
-
const
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2810
|
+
const inputScope = (state.backgroundJobs?.execution.scope ?? io[invocationScope]).child();
|
|
2811
|
+
let input;
|
|
2812
|
+
try {
|
|
2813
|
+
const inputSignal = this.commandSignal;
|
|
2814
|
+
const inputFs = scopeFileSystem(runtimeFileSystems.get(this.fs) ?? this.fs, () => this.budget.fileSystemOperation(), inputSignal, () => this.budget.fileSystemCleanupOperation());
|
|
2815
|
+
const source = stat.type === "directory" ? toByteSource("")
|
|
2816
|
+
: await fileInput(inputFs, path, this.budget.limits.maxInputBytes, inputSignal, this.inputProfile, { stat, registerCleanup: cleanup => { inputScope.register(cleanup); } });
|
|
2817
|
+
input = new ShellInput(source, this.budget, inputSignal, { stat, ...source });
|
|
2818
|
+
inputScopes.set(input, inputScope);
|
|
2819
|
+
inputs.add(input);
|
|
2820
|
+
}
|
|
2821
|
+
catch (error) {
|
|
2822
|
+
await inputScope.close();
|
|
2823
|
+
throw error;
|
|
2824
|
+
}
|
|
2699
2825
|
descriptors.set(redirect.descriptor, { input, stdinIsDefault: false });
|
|
2700
2826
|
}
|
|
2701
2827
|
else {
|
|
2828
|
+
const outputSignal = this.commandSignal;
|
|
2829
|
+
const outputFs = scopeFileSystem(runtimeFileSystems.get(this.fs) ?? this.fs, () => this.budget.fileSystemOperation(), outputSignal, () => this.budget.fileSystemCleanupOperation());
|
|
2830
|
+
const options = { signal: outputSignal };
|
|
2702
2831
|
const append = redirect.operator === ">>";
|
|
2703
|
-
const capabilities = await
|
|
2832
|
+
const capabilities = await outputFs.capabilitiesFor?.(path, options) ?? outputFs.capabilities;
|
|
2704
2833
|
const random = capabilities.randomAccessWrite === true;
|
|
2705
2834
|
const key = resolvePath(state.cwd, path);
|
|
2706
2835
|
let file;
|
|
@@ -2710,18 +2839,18 @@ export class Runtime {
|
|
|
2710
2839
|
throw new FsError("ENOTSUP", { path, message: "Conflicting sequential output descriptors" });
|
|
2711
2840
|
file.references++;
|
|
2712
2841
|
this.outputFiles.set(key, file);
|
|
2713
|
-
});
|
|
2842
|
+
}, outputSignal);
|
|
2714
2843
|
let closed = false;
|
|
2715
2844
|
let offset = 0;
|
|
2716
2845
|
const incremental = async () => {
|
|
2717
2846
|
await this.fileOperation(key, async () => {
|
|
2718
2847
|
if (append)
|
|
2719
|
-
await
|
|
2848
|
+
await outputFs.appendFile(path, new Uint8Array(), options);
|
|
2720
2849
|
else
|
|
2721
|
-
await
|
|
2850
|
+
await outputFs.writeFile(path, new Uint8Array(), { ...options, flag: "w" });
|
|
2722
2851
|
if (!append)
|
|
2723
2852
|
file.data = new Uint8Array();
|
|
2724
|
-
});
|
|
2853
|
+
}, outputSignal);
|
|
2725
2854
|
return { write: (chunk) => {
|
|
2726
2855
|
const copy = new Uint8Array(chunk);
|
|
2727
2856
|
return this.fileOperation(key, async () => {
|
|
@@ -2731,18 +2860,18 @@ export class Runtime {
|
|
|
2731
2860
|
let atEOF = false;
|
|
2732
2861
|
if (!append && current && offset === current.length && capabilities.append === true && capabilities.stat !== false) {
|
|
2733
2862
|
try {
|
|
2734
|
-
atEOF = (await interruptible(
|
|
2863
|
+
atEOF = (await interruptible(outputFs.stat(path, options), outputSignal)).size === offset;
|
|
2735
2864
|
}
|
|
2736
2865
|
catch {
|
|
2737
2866
|
// Metadata is optional for this optimization; writes need no read access.
|
|
2738
|
-
|
|
2867
|
+
outputSignal.throwIfAborted();
|
|
2739
2868
|
}
|
|
2740
|
-
|
|
2869
|
+
outputSignal.throwIfAborted();
|
|
2741
2870
|
}
|
|
2742
2871
|
if (append || atEOF) {
|
|
2743
2872
|
// Preparing a larger view only touches the unpublished tail of current.
|
|
2744
2873
|
const bytes = current ? appendOutputBytes(current, copy) : undefined;
|
|
2745
|
-
await
|
|
2874
|
+
await outputFs.appendFile(path, copy, options);
|
|
2746
2875
|
file.data = bytes;
|
|
2747
2876
|
}
|
|
2748
2877
|
else {
|
|
@@ -2750,12 +2879,12 @@ export class Runtime {
|
|
|
2750
2879
|
if (current)
|
|
2751
2880
|
bytes.set(current);
|
|
2752
2881
|
bytes.set(copy, offset);
|
|
2753
|
-
await
|
|
2882
|
+
await outputFs.writeFile(path, bytes, options);
|
|
2754
2883
|
file.data = bytes;
|
|
2755
2884
|
}
|
|
2756
2885
|
if (!append)
|
|
2757
2886
|
offset += copy.length;
|
|
2758
|
-
});
|
|
2887
|
+
}, outputSignal);
|
|
2759
2888
|
} };
|
|
2760
2889
|
};
|
|
2761
2890
|
const resumePathCache = this.budget.pathLookup.suspend();
|
|
@@ -2770,8 +2899,8 @@ export class Runtime {
|
|
|
2770
2899
|
let target;
|
|
2771
2900
|
let outputScope;
|
|
2772
2901
|
try {
|
|
2773
|
-
outputScope = io[invocationScope].child();
|
|
2774
|
-
target = await openFileOutput({ fs:
|
|
2902
|
+
outputScope = (state.backgroundJobs?.execution.scope ?? io[invocationScope]).child();
|
|
2903
|
+
target = await openFileOutput({ fs: outputFs, signal: outputSignal, registerCleanup: cleanup => { outputScope.register(cleanup); } }, path, append ? "a" : "w", random ? incremental : undefined);
|
|
2775
2904
|
}
|
|
2776
2905
|
catch (error) {
|
|
2777
2906
|
try {
|
|
@@ -2784,8 +2913,8 @@ export class Runtime {
|
|
|
2784
2913
|
}
|
|
2785
2914
|
outputs.add(async (completion) => {
|
|
2786
2915
|
try {
|
|
2787
|
-
if (
|
|
2788
|
-
await target.abort(
|
|
2916
|
+
if (outputSignal.aborted)
|
|
2917
|
+
await target.abort(outputSignal.reason);
|
|
2789
2918
|
else if ("reason" in completion)
|
|
2790
2919
|
await target.abort(completion.reason);
|
|
2791
2920
|
else {
|
|
@@ -2793,7 +2922,7 @@ export class Runtime {
|
|
|
2793
2922
|
await target.finish();
|
|
2794
2923
|
}
|
|
2795
2924
|
catch (error) {
|
|
2796
|
-
|
|
2925
|
+
outputSignal.throwIfAborted();
|
|
2797
2926
|
if (completion.status === 0)
|
|
2798
2927
|
throw error;
|
|
2799
2928
|
}
|
|
@@ -2808,7 +2937,7 @@ export class Runtime {
|
|
|
2808
2937
|
}
|
|
2809
2938
|
}
|
|
2810
2939
|
});
|
|
2811
|
-
const output = this.budget.sink(target.sink,
|
|
2940
|
+
const output = this.budget.sink(target.sink, outputSignal);
|
|
2812
2941
|
budgetedSinks.get(output).file = Object.freeze({ path });
|
|
2813
2942
|
descriptors.set(redirect.descriptor, { output });
|
|
2814
2943
|
if (redirect.operator === "&>") {
|
|
@@ -2823,14 +2952,15 @@ export class Runtime {
|
|
|
2823
2952
|
const diagnostic = errorTarget === undefined ? undefined : filesystemDiagnostic(error, errorTarget);
|
|
2824
2953
|
throw new ExecutionFailure(error, currentIO(), diagnostic);
|
|
2825
2954
|
}
|
|
2955
|
+
io[backgroundPrune]?.(descriptors);
|
|
2826
2956
|
return currentIO();
|
|
2827
2957
|
}
|
|
2828
|
-
async fileOperation(path, operation) {
|
|
2958
|
+
async fileOperation(path, operation, signal = this.signal) {
|
|
2829
2959
|
const previous = this.fileWrites.get(path) ?? Promise.resolve();
|
|
2830
|
-
const pending = previous.catch(() => undefined).then(() => {
|
|
2960
|
+
const pending = previous.catch(() => undefined).then(() => { signal.throwIfAborted(); return operation(); });
|
|
2831
2961
|
this.fileWrites.set(path, pending);
|
|
2832
2962
|
try {
|
|
2833
|
-
await interruptible(pending,
|
|
2963
|
+
await interruptible(pending, signal);
|
|
2834
2964
|
}
|
|
2835
2965
|
finally {
|
|
2836
2966
|
if (this.fileWrites.get(path) === pending)
|
|
@@ -2911,8 +3041,11 @@ export class Runtime {
|
|
|
2911
3041
|
};
|
|
2912
3042
|
let overlayOpen = false;
|
|
2913
3043
|
try {
|
|
2914
|
-
if (snapshotScope)
|
|
3044
|
+
if (snapshotScope) {
|
|
3045
|
+
const jobs = state.backgroundJobs;
|
|
2915
3046
|
state = await cloneState(state, this.signal, snapshotScope);
|
|
3047
|
+
state.backgroundJobs = jobs;
|
|
3048
|
+
}
|
|
2916
3049
|
if (words.length) {
|
|
2917
3050
|
stateMonitor(state)?.openOverlay(previous);
|
|
2918
3051
|
overlayOpen = true;
|
|
@@ -3049,6 +3182,8 @@ export class Runtime {
|
|
|
3049
3182
|
async dispatchScoped(name, values, state, io, assignments, bypassFunctions) {
|
|
3050
3183
|
const { [invocationScope]: scope, ...publicIO } = io;
|
|
3051
3184
|
Reflect.deleteProperty(publicIO, valueScope);
|
|
3185
|
+
Reflect.deleteProperty(publicIO, backgroundResources);
|
|
3186
|
+
Reflect.deleteProperty(publicIO, backgroundPrune);
|
|
3052
3187
|
const allocation = this.budget.values.scope();
|
|
3053
3188
|
scope.register(() => allocation.close());
|
|
3054
3189
|
const argumentValues = this.admitArguments(values, allocation);
|
|
@@ -3101,6 +3236,7 @@ export class Runtime {
|
|
|
3101
3236
|
const forwardedValues = getCommandArguments(forwarded);
|
|
3102
3237
|
const admitted = forwardedValues === argumentValues ? argumentValues : this.admitArguments(forwardedValues.values, allocation);
|
|
3103
3238
|
const context = { ...forwarded, args: admitted.args, argumentValues: admitted, [invocationScope]: scope,
|
|
3239
|
+
[backgroundResources]: io[backgroundResources], [backgroundPrune]: io[backgroundPrune],
|
|
3104
3240
|
...(io[valueScope] === undefined ? {} : { [valueScope]: io[valueScope] }) };
|
|
3105
3241
|
const previous = new Map();
|
|
3106
3242
|
const cwd = state.cwd;
|
|
@@ -3552,6 +3688,7 @@ export class Runtime {
|
|
|
3552
3688
|
directoryStack: { entries: [], bytes: 0 },
|
|
3553
3689
|
dotglob: false,
|
|
3554
3690
|
globstar: false,
|
|
3691
|
+
backgroundJobs: new BackgroundJobs(state.backgroundJobs.execution),
|
|
3555
3692
|
positional: [], arg0: shellValueText(arg0), profile: context.command === "sh" ? "sh" : "bash", status: 0, substitutionStatus: 0, depth: state.depth + 1,
|
|
3556
3693
|
loopDepth: 0, functionDepth: 0, locals: [], pipefail: false, isolated: true,
|
|
3557
3694
|
errexit: false,
|
|
@@ -3758,6 +3895,8 @@ export class Runtime {
|
|
|
3758
3895
|
};
|
|
3759
3896
|
Reflect.deleteProperty(context, invocationScope);
|
|
3760
3897
|
Reflect.deleteProperty(context, valueScope);
|
|
3898
|
+
Reflect.deleteProperty(context, backgroundResources);
|
|
3899
|
+
Reflect.deleteProperty(context, backgroundPrune);
|
|
3761
3900
|
bindCommandIO(context);
|
|
3762
3901
|
bindFileOutputBudget(context, sink => this.budget.sink(sink, runtime.signal));
|
|
3763
3902
|
if (argumentValues.values.every(value => typeof value === "string"))
|
|
@@ -5024,12 +5163,118 @@ export class Runtime {
|
|
|
5024
5163
|
}
|
|
5025
5164
|
}
|
|
5026
5165
|
}
|
|
5166
|
+
async waitBuiltin(context, state) {
|
|
5167
|
+
let next = false;
|
|
5168
|
+
let variable;
|
|
5169
|
+
let index = 0;
|
|
5170
|
+
for (; index < context.args.length; index++) {
|
|
5171
|
+
const option = context.args[index];
|
|
5172
|
+
if (option === "--") {
|
|
5173
|
+
index++;
|
|
5174
|
+
break;
|
|
5175
|
+
}
|
|
5176
|
+
if (!option.startsWith("-") || option === "-")
|
|
5177
|
+
break;
|
|
5178
|
+
for (let flag = 1; flag < option.length; flag++) {
|
|
5179
|
+
const letter = option[flag];
|
|
5180
|
+
if (letter === "n")
|
|
5181
|
+
next = true;
|
|
5182
|
+
else if (letter === "f") { /* Processes do not stop in the virtual shell. */ }
|
|
5183
|
+
else if (letter === "p") {
|
|
5184
|
+
variable = option.slice(flag + 1) || context.args[++index];
|
|
5185
|
+
if (variable === undefined) {
|
|
5186
|
+
await writeDiagnostic(context.stderr, "wait: -p: option requires an argument\n");
|
|
5187
|
+
return 2;
|
|
5188
|
+
}
|
|
5189
|
+
if (!/^[a-zA-Z_][a-zA-Z_0-9]*(?:\[.+\])?$/su.test(variable)) {
|
|
5190
|
+
await writeDiagnostic(context.stderr, `wait: ${variable}: not a valid identifier\n`);
|
|
5191
|
+
return 1;
|
|
5192
|
+
}
|
|
5193
|
+
break;
|
|
5194
|
+
}
|
|
5195
|
+
else {
|
|
5196
|
+
await writeDiagnostic(context.stderr, `wait: -${letter}: invalid option\n`);
|
|
5197
|
+
return 2;
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
const target = variable === undefined ? undefined : /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[(.+)\])?$/su.exec(variable);
|
|
5202
|
+
if (target) {
|
|
5203
|
+
const name = target[1];
|
|
5204
|
+
if (state.readonlyVariables?.has(name)) {
|
|
5205
|
+
await writeDiagnostic(context.stderr, `wait: ${name}: readonly variable\n`);
|
|
5206
|
+
return 1;
|
|
5207
|
+
}
|
|
5208
|
+
if (target[2] === undefined) {
|
|
5209
|
+
if (arrayStore(state)?.get(name))
|
|
5210
|
+
await this.unsetIndexed(state, name);
|
|
5211
|
+
else
|
|
5212
|
+
this.unsetVariable(state, name);
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
const publish = async (id) => {
|
|
5216
|
+
if (!target)
|
|
5217
|
+
return;
|
|
5218
|
+
if (target[2] === undefined)
|
|
5219
|
+
await this.assignVariable(state, target[1], id);
|
|
5220
|
+
else
|
|
5221
|
+
await this.arrayAssignment({ kind: "element", name: target[1], index: { decimal: target[2], source: target[2] }, append: false, value: { offset: 0, parts: [{ kind: "text", value: id, quoted: true }] } }, state, context);
|
|
5222
|
+
};
|
|
5223
|
+
const table = state.backgroundJobs;
|
|
5224
|
+
const ids = context.args.slice(index);
|
|
5225
|
+
const selected = [];
|
|
5226
|
+
let invalid = false;
|
|
5227
|
+
for (const id of ids) {
|
|
5228
|
+
if (!id.startsWith("%") && !/^[0-9]+$/u.test(id)) {
|
|
5229
|
+
await writeDiagnostic(context.stderr, `wait: ${id}: not a pid or valid job spec\n`);
|
|
5230
|
+
if (next)
|
|
5231
|
+
continue;
|
|
5232
|
+
return 1;
|
|
5233
|
+
}
|
|
5234
|
+
const job = table.resolve(id);
|
|
5235
|
+
if (!job) {
|
|
5236
|
+
await writeDiagnostic(context.stderr, `wait: ${id}: not a child of this shell\n`);
|
|
5237
|
+
invalid = true;
|
|
5238
|
+
}
|
|
5239
|
+
else
|
|
5240
|
+
selected.push(job);
|
|
5241
|
+
}
|
|
5242
|
+
if (next) {
|
|
5243
|
+
const eligible = ids.length ? selected.filter(job => !job.waited) : [...table.jobs.values()].filter(job => !job.done && !job.consumed);
|
|
5244
|
+
if (!eligible.length)
|
|
5245
|
+
return 127;
|
|
5246
|
+
const result = await interruptible(Promise.race(eligible.map(async (job) => ({ job, status: await job.promise }))), this.signal);
|
|
5247
|
+
result.job.consumed = true;
|
|
5248
|
+
await publish(result.job.id);
|
|
5249
|
+
return result.status;
|
|
5250
|
+
}
|
|
5251
|
+
if (!ids.length) {
|
|
5252
|
+
await interruptible(Promise.all([...table.jobs.values()].map(job => job.promise)), this.signal);
|
|
5253
|
+
table.jobs.clear();
|
|
5254
|
+
return 0;
|
|
5255
|
+
}
|
|
5256
|
+
let status = invalid ? 127 : 0;
|
|
5257
|
+
for (const id of ids) {
|
|
5258
|
+
const job = table.resolve(id);
|
|
5259
|
+
if (!job) {
|
|
5260
|
+
status = 127;
|
|
5261
|
+
continue;
|
|
5262
|
+
}
|
|
5263
|
+
status = await interruptible(job.promise, this.signal);
|
|
5264
|
+
job.consumed = true;
|
|
5265
|
+
job.waited = true;
|
|
5266
|
+
await publish(job.id);
|
|
5267
|
+
}
|
|
5268
|
+
return status;
|
|
5269
|
+
}
|
|
5027
5270
|
async builtin(context, state, assignments, diagnose, suppressSpecial = false) {
|
|
5028
5271
|
const { command, args, stdout, stderr } = context;
|
|
5029
5272
|
if (command === ":" || command === "true")
|
|
5030
5273
|
return 0;
|
|
5031
5274
|
if (command === "false")
|
|
5032
5275
|
return 1;
|
|
5276
|
+
if (command === "wait")
|
|
5277
|
+
return this.waitBuiltin(context, state);
|
|
5033
5278
|
if (command === "shopt")
|
|
5034
5279
|
return this.shoptBuiltin(context, state);
|
|
5035
5280
|
if (command === "let")
|
|
@@ -5829,11 +6074,14 @@ export class Runtime {
|
|
|
5829
6074
|
const substitutionDiagnosticLines = new Map();
|
|
5830
6075
|
for (const [command, line] of part.script.printedLines ?? [])
|
|
5831
6076
|
substitutionDiagnosticLines.set(command, part.sourceLine === undefined ? warningLine + (command.line ?? part.line) - part.line : warningLine + line - 1);
|
|
5832
|
-
const
|
|
6077
|
+
const resources = new BackgroundResources();
|
|
6078
|
+
const captureIO = { ...isolateIO(io), [backgroundResources]: [...io[backgroundResources] ?? [], resources], substitutionDiagnosticLines, diagnosticOffset: (io.diagnosticLine ?? part.line) - (part.sourceLine ?? part.line), stdout: this.budget.sink(capture, this.signal) };
|
|
6079
|
+
resources.destination = backgroundDestination(captureIO.stdout);
|
|
5833
6080
|
try {
|
|
5834
6081
|
state.substitutionStatus = fileShortcut ? await this.runCommandIsolated(command, child, captureIO, true) : await this.run(part.script, child, captureIO);
|
|
5835
6082
|
}
|
|
5836
6083
|
finally {
|
|
6084
|
+
await interruptible(resources.drain(), this.signal);
|
|
5837
6085
|
stateMonitor(child)?.closeValues();
|
|
5838
6086
|
}
|
|
5839
6087
|
state.status = state.substitutionStatus;
|
|
@@ -5879,13 +6127,14 @@ export class Runtime {
|
|
|
5879
6127
|
io[valueScope]?.reserve(values.length * 32 + 64, 0);
|
|
5880
6128
|
return concatShellValues(values.flatMap((value, index) => index ? [separator, value] : [value]), io[valueScope]);
|
|
5881
6129
|
}
|
|
5882
|
-
let value = part.name === "
|
|
5883
|
-
: part.name === "
|
|
5884
|
-
: part.name === "
|
|
5885
|
-
: part.name === "
|
|
5886
|
-
:
|
|
5887
|
-
:
|
|
5888
|
-
:
|
|
6130
|
+
let value = part.name === "!" ? state.lastBackgroundJob
|
|
6131
|
+
: part.name === "?" ? String(state.status)
|
|
6132
|
+
: part.name === "-" ? `${state.errexit ? "e" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}`
|
|
6133
|
+
: part.name === "#" ? String(state.positional.length)
|
|
6134
|
+
: part.name === "@" || part.name === "*" ? state.positional.join(hereString && (part.name === "@" || !part.quoted) ? " " : Array.from(state.variables.IFS ?? " ")[0] ?? "")
|
|
6135
|
+
: /^0+$/u.test(part.name) ? state.arg0 ?? "virtual-bash"
|
|
6136
|
+
: /^\d+$/u.test(part.name) ? state.positional[Number(part.name) - 1]
|
|
6137
|
+
: this.variable(state, part.name);
|
|
5889
6138
|
let retained = value;
|
|
5890
6139
|
if (!part.length && value !== undefined) {
|
|
5891
6140
|
if (/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name) && !arrayStore(state)?.get(part.name))
|