shell-dsl 0.0.38 → 0.0.40
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/README.md +118 -3
- package/dist/cjs/index.cjs +2 -1
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/commands/exit/exit.cjs +84 -0
- package/dist/cjs/src/commands/exit/exit.cjs.map +10 -0
- package/dist/cjs/src/commands/index.cjs +22 -2
- package/dist/cjs/src/commands/index.cjs.map +3 -3
- package/dist/cjs/src/commands/printf/printf.cjs +416 -0
- package/dist/cjs/src/commands/printf/printf.cjs.map +10 -0
- package/dist/cjs/src/commands/sh/sh.cjs +134 -0
- package/dist/cjs/src/commands/sh/sh.cjs.map +10 -0
- package/dist/cjs/src/index.cjs +2 -1
- package/dist/cjs/src/index.cjs.map +3 -3
- package/dist/cjs/src/interpreter/context.cjs +4 -1
- package/dist/cjs/src/interpreter/context.cjs.map +3 -3
- package/dist/cjs/src/interpreter/index.cjs +2 -1
- package/dist/cjs/src/interpreter/index.cjs.map +3 -3
- package/dist/cjs/src/interpreter/interpreter.cjs +301 -76
- package/dist/cjs/src/interpreter/interpreter.cjs.map +3 -3
- package/dist/cjs/src/lexer/lexer.cjs +13 -1
- package/dist/cjs/src/lexer/lexer.cjs.map +3 -3
- package/dist/cjs/src/parser/parser.cjs +11 -1
- package/dist/cjs/src/parser/parser.cjs.map +3 -3
- package/dist/cjs/src/types.cjs.map +2 -2
- package/dist/mjs/index.mjs +3 -1
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/commands/exit/exit.mjs +44 -0
- package/dist/mjs/src/commands/exit/exit.mjs.map +10 -0
- package/dist/mjs/src/commands/index.mjs +22 -2
- package/dist/mjs/src/commands/index.mjs.map +3 -3
- package/dist/mjs/src/commands/printf/printf.mjs +376 -0
- package/dist/mjs/src/commands/printf/printf.mjs.map +10 -0
- package/dist/mjs/src/commands/sh/sh.mjs +94 -0
- package/dist/mjs/src/commands/sh/sh.mjs.map +10 -0
- package/dist/mjs/src/index.mjs +3 -2
- package/dist/mjs/src/index.mjs.map +3 -3
- package/dist/mjs/src/interpreter/context.mjs +4 -1
- package/dist/mjs/src/interpreter/context.mjs.map +3 -3
- package/dist/mjs/src/interpreter/index.mjs +3 -2
- package/dist/mjs/src/interpreter/index.mjs.map +2 -2
- package/dist/mjs/src/interpreter/interpreter.mjs +301 -76
- package/dist/mjs/src/interpreter/interpreter.mjs.map +3 -3
- package/dist/mjs/src/lexer/lexer.mjs +13 -1
- package/dist/mjs/src/lexer/lexer.mjs.map +3 -3
- package/dist/mjs/src/parser/parser.mjs +11 -1
- package/dist/mjs/src/parser/parser.mjs.map +3 -3
- package/dist/mjs/src/types.mjs.map +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/src/commands/exit/exit.d.ts +2 -0
- package/dist/types/src/commands/index.d.ts +3 -0
- package/dist/types/src/commands/printf/printf.d.ts +2 -0
- package/dist/types/src/commands/sh/sh.d.ts +5 -0
- package/dist/types/src/index.d.ts +2 -2
- package/dist/types/src/interpreter/context.d.ts +2 -1
- package/dist/types/src/interpreter/index.d.ts +1 -1
- package/dist/types/src/interpreter/interpreter.d.ts +24 -0
- package/dist/types/src/types.d.ts +13 -0
- package/package.json +1 -1
|
@@ -56,7 +56,10 @@ function createCommandContext(options) {
|
|
|
56
56
|
if (options.exec) {
|
|
57
57
|
ctx.exec = options.exec;
|
|
58
58
|
}
|
|
59
|
+
if (options.shell) {
|
|
60
|
+
ctx.shell = options.shell;
|
|
61
|
+
}
|
|
59
62
|
return ctx;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
//# debugId=
|
|
65
|
+
//# debugId=A1213B6F49E1441764756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/interpreter/context.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { CommandContext, VirtualFS, Stdin, Stdout, Stderr, ExecResult } from \"../types.cjs\";\n\nexport interface ContextOptions {\n args: string[];\n stdin: Stdin;\n stdout: Stdout;\n stderr: Stderr;\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n setCwd: (path: string) => void;\n exec?: (name: string, args: string[]) => Promise<ExecResult>;\n}\n\nexport function createCommandContext(options: ContextOptions): CommandContext {\n const ctx: CommandContext = {\n args: options.args,\n stdin: options.stdin,\n stdout: options.stdout,\n stderr: options.stderr,\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n setCwd: options.setCwd,\n };\n if (options.exec) {\n ctx.exec = options.exec;\n }\n return ctx;\n}\n"
|
|
5
|
+
"import type { CommandContext, VirtualFS, Stdin, Stdout, Stderr, ExecResult, ShellCommandApi } from \"../types.cjs\";\n\nexport interface ContextOptions {\n args: string[];\n stdin: Stdin;\n stdout: Stdout;\n stderr: Stderr;\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n setCwd: (path: string) => void;\n exec?: (name: string, args: string[]) => Promise<ExecResult>;\n shell?: ShellCommandApi;\n}\n\nexport function createCommandContext(options: ContextOptions): CommandContext {\n const ctx: CommandContext = {\n args: options.args,\n stdin: options.stdin,\n stdout: options.stdout,\n stderr: options.stderr,\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n setCwd: options.setCwd,\n };\n if (options.exec) {\n ctx.exec = options.exec;\n }\n if (options.shell) {\n ctx.shell = options.shell;\n }\n return ctx;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeO,SAAS,oBAAoB,CAAC,SAAyC;AAAA,EAC5E,MAAM,MAAsB;AAAA,IAC1B,MAAM,QAAQ;AAAA,IACd,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,IAAI,QAAQ;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,IACb,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,IAAI,QAAQ,MAAM;AAAA,IAChB,IAAI,OAAO,QAAQ;AAAA,EACrB;AAAA,EACA,IAAI,QAAQ,OAAO;AAAA,IACjB,IAAI,QAAQ,QAAQ;AAAA,EACtB;AAAA,EACA,OAAO;AAAA;",
|
|
8
|
+
"debugId": "A1213B6F49E1441764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -41,6 +41,7 @@ var exports_interpreter = {};
|
|
|
41
41
|
__export(exports_interpreter, {
|
|
42
42
|
createCommandContext: () => import_context.createCommandContext,
|
|
43
43
|
Interpreter: () => import_interpreter.Interpreter,
|
|
44
|
+
ExitException: () => import_interpreter.ExitException,
|
|
44
45
|
ContinueException: () => import_interpreter.ContinueException,
|
|
45
46
|
BreakException: () => import_interpreter.BreakException
|
|
46
47
|
});
|
|
@@ -48,4 +49,4 @@ module.exports = __toCommonJS(exports_interpreter);
|
|
|
48
49
|
var import_interpreter = require("./interpreter.cjs");
|
|
49
50
|
var import_context = require("./context.cjs");
|
|
50
51
|
|
|
51
|
-
//# debugId=
|
|
52
|
+
//# debugId=478C5E50B11BB65664756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/interpreter/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter.cjs\";\nexport { createCommandContext, type ContextOptions } from \"./context.cjs\";\n"
|
|
5
|
+
"export { Interpreter, type InterpreterOptions, BreakException, ContinueException, ExitException } from \"./interpreter.cjs\";\nexport { createCommandContext, type ContextOptions } from \"./context.cjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAuG,IAAvG;AAC0D,IAA1D;",
|
|
8
|
+
"debugId": "478C5E50B11BB65664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -40,11 +40,14 @@ var __export = (target, all) => {
|
|
|
40
40
|
var exports_interpreter = {};
|
|
41
41
|
__export(exports_interpreter, {
|
|
42
42
|
Interpreter: () => Interpreter,
|
|
43
|
+
ExitException: () => ExitException,
|
|
43
44
|
ContinueException: () => ContinueException,
|
|
44
45
|
BreakException: () => BreakException
|
|
45
46
|
});
|
|
46
47
|
module.exports = __toCommonJS(exports_interpreter);
|
|
47
48
|
var import_context = require("./context.cjs");
|
|
49
|
+
var import_lexer = require("../lexer/lexer.cjs");
|
|
50
|
+
var import_parser = require("../parser/parser.cjs");
|
|
48
51
|
var import_stdin = require("../io/stdin.cjs");
|
|
49
52
|
var import_stdout = require("../io/stdout.cjs");
|
|
50
53
|
var import_special_files = require("../fs/special-files.cjs");
|
|
@@ -68,6 +71,14 @@ class ContinueException extends Error {
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
class ExitException extends Error {
|
|
75
|
+
exitCode;
|
|
76
|
+
constructor(exitCode) {
|
|
77
|
+
super("exit");
|
|
78
|
+
this.exitCode = exitCode;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
class Interpreter {
|
|
72
83
|
fs;
|
|
73
84
|
cwd;
|
|
@@ -76,6 +87,9 @@ class Interpreter {
|
|
|
76
87
|
redirectObjects;
|
|
77
88
|
loopDepth = 0;
|
|
78
89
|
isTTY;
|
|
90
|
+
argv0;
|
|
91
|
+
positionalParameters;
|
|
92
|
+
lastExitCode;
|
|
79
93
|
constructor(options) {
|
|
80
94
|
this.fs = options.fs;
|
|
81
95
|
this.cwd = options.cwd;
|
|
@@ -83,6 +97,9 @@ class Interpreter {
|
|
|
83
97
|
this.commands = options.commands;
|
|
84
98
|
this.redirectObjects = options.redirectObjects ?? {};
|
|
85
99
|
this.isTTY = options.isTTY ?? false;
|
|
100
|
+
this.argv0 = options.argv0 ?? "sh";
|
|
101
|
+
this.positionalParameters = [...options.positionalParameters ?? []];
|
|
102
|
+
this.lastExitCode = options.lastExitCode ?? 0;
|
|
86
103
|
}
|
|
87
104
|
getLoopDepth() {
|
|
88
105
|
return this.loopDepth;
|
|
@@ -90,7 +107,16 @@ class Interpreter {
|
|
|
90
107
|
async execute(ast) {
|
|
91
108
|
const stdout = import_stdout.createStdout(this.isTTY);
|
|
92
109
|
const stderr = import_stdout.createStderr(this.isTTY);
|
|
93
|
-
|
|
110
|
+
let exitCode;
|
|
111
|
+
try {
|
|
112
|
+
exitCode = await this.executeNode(ast, null, stdout, stderr);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (!(err instanceof ExitException)) {
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
exitCode = err.exitCode;
|
|
118
|
+
this.lastExitCode = exitCode;
|
|
119
|
+
}
|
|
94
120
|
stdout.close();
|
|
95
121
|
stderr.close();
|
|
96
122
|
return {
|
|
@@ -100,30 +126,43 @@ class Interpreter {
|
|
|
100
126
|
};
|
|
101
127
|
}
|
|
102
128
|
async executeNode(node, stdinSource, stdout, stderr) {
|
|
129
|
+
let exitCode;
|
|
103
130
|
switch (node.type) {
|
|
104
131
|
case "command":
|
|
105
|
-
|
|
132
|
+
exitCode = await this.executeCommand(node, stdinSource, stdout, stderr);
|
|
133
|
+
break;
|
|
106
134
|
case "pipeline":
|
|
107
|
-
|
|
135
|
+
exitCode = await this.executePipeline(node.commands, stdinSource, stdout, stderr);
|
|
136
|
+
break;
|
|
108
137
|
case "sequence":
|
|
109
|
-
|
|
138
|
+
exitCode = await this.executeSequence(node.commands, stdinSource, stdout, stderr);
|
|
139
|
+
break;
|
|
110
140
|
case "and":
|
|
111
|
-
|
|
141
|
+
exitCode = await this.executeAnd(node.left, node.right, stdinSource, stdout, stderr);
|
|
142
|
+
break;
|
|
112
143
|
case "or":
|
|
113
|
-
|
|
144
|
+
exitCode = await this.executeOr(node.left, node.right, stdinSource, stdout, stderr);
|
|
145
|
+
break;
|
|
114
146
|
case "if":
|
|
115
|
-
|
|
147
|
+
exitCode = await this.executeIf(node, stdinSource, stdout, stderr);
|
|
148
|
+
break;
|
|
116
149
|
case "for":
|
|
117
|
-
|
|
150
|
+
exitCode = await this.executeFor(node, stdinSource, stdout, stderr);
|
|
151
|
+
break;
|
|
118
152
|
case "while":
|
|
119
|
-
|
|
153
|
+
exitCode = await this.executeWhile(node, stdinSource, stdout, stderr);
|
|
154
|
+
break;
|
|
120
155
|
case "until":
|
|
121
|
-
|
|
156
|
+
exitCode = await this.executeUntil(node, stdinSource, stdout, stderr);
|
|
157
|
+
break;
|
|
122
158
|
case "case":
|
|
123
|
-
|
|
159
|
+
exitCode = await this.executeCase(node, stdinSource, stdout, stderr);
|
|
160
|
+
break;
|
|
124
161
|
default:
|
|
125
162
|
throw new Error("Cannot execute unknown node type");
|
|
126
163
|
}
|
|
164
|
+
this.lastExitCode = exitCode;
|
|
165
|
+
return exitCode;
|
|
127
166
|
}
|
|
128
167
|
async executeCommand(node, stdinSource, stdout, stderr) {
|
|
129
168
|
const assignmentEnv = { ...this.env };
|
|
@@ -171,95 +210,239 @@ class Interpreter {
|
|
|
171
210
|
if (stdoutToStderr) {
|
|
172
211
|
actualStdout = actualStderr;
|
|
173
212
|
}
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
213
|
+
let exitCode = await this.invokeCommand(name, args, actualStdin, actualStdout, actualStderr, assignmentEnv);
|
|
214
|
+
if (actualStdout !== stdout) {
|
|
215
|
+
actualStdout.close();
|
|
216
|
+
}
|
|
217
|
+
if (actualStderr !== stderr && actualStderr !== actualStdout) {
|
|
218
|
+
actualStderr.close();
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
await Promise.all(fileWritePromises);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
224
|
+
const writeRedirects = node.redirects.filter((r) => r.mode !== "<" && r.mode !== "2>&1" && r.mode !== "1>&2");
|
|
225
|
+
const target = writeRedirects.length > 0 ? await this.expandWordScalar(writeRedirects[writeRedirects.length - 1].target, this.env) : "unknown";
|
|
226
|
+
await stderr.writeText(`sh: ${target}: ${message}
|
|
177
227
|
`);
|
|
178
|
-
|
|
228
|
+
exitCode = 1;
|
|
179
229
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
230
|
+
return exitCode;
|
|
231
|
+
}
|
|
232
|
+
async invokeCommand(name, args, stdinSource, stdout, stderr, env) {
|
|
233
|
+
const command = this.commands[name];
|
|
234
|
+
if (command) {
|
|
235
|
+
return this.invokeRegisteredCommand(name, command, args, stdinSource, stdout, stderr, env);
|
|
236
|
+
}
|
|
237
|
+
if (name.includes("/")) {
|
|
238
|
+
return this.executeExecutableFile(name, args, stdinSource, stdout, stderr, env);
|
|
239
|
+
}
|
|
240
|
+
await stderr.writeText(`${name}: command not found
|
|
241
|
+
`);
|
|
242
|
+
return 127;
|
|
243
|
+
}
|
|
244
|
+
async invokeRegisteredCommand(name, command, args, stdinSource, stdout, stderr, env) {
|
|
245
|
+
const exec = this.createExec(env);
|
|
246
|
+
const shell = this.createShellApi(stdinSource, stdout, stderr, env);
|
|
247
|
+
const ctx = import_context.createCommandContext({
|
|
248
|
+
args,
|
|
249
|
+
stdin: import_stdin.createStdin(stdinSource),
|
|
250
|
+
stdout,
|
|
251
|
+
stderr,
|
|
252
|
+
fs: this.fs,
|
|
253
|
+
cwd: this.cwd,
|
|
254
|
+
env,
|
|
255
|
+
setCwd: (path) => this.setCwd(path),
|
|
256
|
+
exec,
|
|
257
|
+
shell
|
|
258
|
+
});
|
|
259
|
+
try {
|
|
260
|
+
return await command(ctx);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
if (err instanceof BreakException || err instanceof ContinueException || err instanceof ExitException) {
|
|
263
|
+
throw err;
|
|
189
264
|
}
|
|
265
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
266
|
+
await stderr.writeText(`${name}: ${message}
|
|
267
|
+
`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
createExec(env) {
|
|
272
|
+
return async (name, args) => {
|
|
190
273
|
const subStdout = import_stdout.createStdout();
|
|
191
274
|
const subStderr = import_stdout.createStderr();
|
|
192
|
-
const
|
|
193
|
-
args: cmdArgs,
|
|
194
|
-
stdin: import_stdin.createStdin(null),
|
|
195
|
-
stdout: subStdout,
|
|
196
|
-
stderr: subStderr,
|
|
197
|
-
fs: this.fs,
|
|
198
|
-
cwd: this.cwd,
|
|
199
|
-
env: { ...assignmentEnv },
|
|
200
|
-
setCwd: (path) => this.setCwd(path),
|
|
201
|
-
exec
|
|
202
|
-
});
|
|
203
|
-
let exitCode2;
|
|
204
|
-
try {
|
|
205
|
-
exitCode2 = await cmd(subCtx);
|
|
206
|
-
} catch (err) {
|
|
207
|
-
if (err instanceof BreakException || err instanceof ContinueException) {
|
|
208
|
-
throw err;
|
|
209
|
-
}
|
|
210
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
211
|
-
await subStderr.writeText(`${cmdName}: ${message}
|
|
212
|
-
`);
|
|
213
|
-
exitCode2 = 1;
|
|
214
|
-
}
|
|
275
|
+
const exitCode = await this.invokeCommand(name, args, null, subStdout, subStderr, { ...env });
|
|
215
276
|
subStdout.close();
|
|
216
277
|
subStderr.close();
|
|
217
278
|
return {
|
|
218
279
|
stdout: await subStdout.collect(),
|
|
219
280
|
stderr: await subStderr.collect(),
|
|
220
|
-
exitCode
|
|
281
|
+
exitCode
|
|
221
282
|
};
|
|
222
283
|
};
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
284
|
+
}
|
|
285
|
+
createShellApi(stdinSource, stdout, stderr, env) {
|
|
286
|
+
return {
|
|
287
|
+
eval: (source) => this.executeSourceInCurrentFrame(source, stdinSource, stdout, stderr, "eval"),
|
|
288
|
+
source: (path, args = []) => this.sourceFile(path, args, stdinSource, stdout, stderr),
|
|
289
|
+
runScript: (path, args = []) => this.executeExecutableFile(path, args, stdinSource, stdout, stderr, { ...env }),
|
|
290
|
+
runShell: (source, options = {}) => this.executeIsolatedShellSource(source, options.argv0 ?? "sh", options.args ?? [], stdinSource, stdout, stderr, env),
|
|
291
|
+
getLastExitCode: () => this.lastExitCode,
|
|
292
|
+
exit: (exitCode = this.lastExitCode) => {
|
|
293
|
+
throw new ExitException(this.normalizeExitCode(exitCode));
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async executeExecutableFile(pathName, args, stdinSource, stdout, stderr, env) {
|
|
298
|
+
const loaded = await this.loadScriptSource(pathName, stderr, 127);
|
|
299
|
+
if (!loaded.ok) {
|
|
300
|
+
return loaded.exitCode;
|
|
301
|
+
}
|
|
302
|
+
const shebang = this.parseShebang(loaded.source);
|
|
303
|
+
if (!shebang || shebang.command === "sh") {
|
|
304
|
+
return this.executeIsolatedShellSource(loaded.source, pathName, args, stdinSource, stdout, stderr, env);
|
|
305
|
+
}
|
|
306
|
+
const command = this.commands[shebang.command];
|
|
307
|
+
if (!command) {
|
|
308
|
+
await stderr.writeText(`${pathName}: unsupported interpreter: ${shebang.display}
|
|
309
|
+
`);
|
|
310
|
+
return 126;
|
|
311
|
+
}
|
|
312
|
+
const child = new Interpreter({
|
|
228
313
|
fs: this.fs,
|
|
229
314
|
cwd: this.cwd,
|
|
230
|
-
env:
|
|
231
|
-
|
|
232
|
-
|
|
315
|
+
env: { ...env },
|
|
316
|
+
commands: this.commands,
|
|
317
|
+
redirectObjects: this.redirectObjects,
|
|
318
|
+
isTTY: this.isTTY,
|
|
319
|
+
argv0: shebang.command,
|
|
320
|
+
positionalParameters: []
|
|
321
|
+
});
|
|
322
|
+
return child.invokeRegisteredCommand(shebang.command, command, [...shebang.args, pathName, ...args], stdinSource, stdout, stderr, { ...env });
|
|
323
|
+
}
|
|
324
|
+
async sourceFile(pathName, args, stdinSource, stdout, stderr) {
|
|
325
|
+
const loaded = await this.loadScriptSource(pathName, stderr, 1);
|
|
326
|
+
if (!loaded.ok) {
|
|
327
|
+
return loaded.exitCode;
|
|
328
|
+
}
|
|
329
|
+
return this.executeSourceInCurrentFrame(loaded.source, stdinSource, stdout, stderr, pathName, args.length > 0 ? { args } : undefined);
|
|
330
|
+
}
|
|
331
|
+
async executeIsolatedShellSource(source, argv0, args, stdinSource, stdout, stderr, env) {
|
|
332
|
+
const interpreter = new Interpreter({
|
|
333
|
+
fs: this.fs,
|
|
334
|
+
cwd: this.cwd,
|
|
335
|
+
env: { ...env },
|
|
336
|
+
commands: this.commands,
|
|
337
|
+
redirectObjects: this.redirectObjects,
|
|
338
|
+
isTTY: this.isTTY,
|
|
339
|
+
argv0,
|
|
340
|
+
positionalParameters: args
|
|
233
341
|
});
|
|
234
|
-
let exitCode;
|
|
235
342
|
try {
|
|
236
|
-
|
|
343
|
+
return await interpreter.executeSourceInCurrentFrame(source, stdinSource, stdout, stderr, argv0);
|
|
237
344
|
} catch (err) {
|
|
238
|
-
if (err instanceof
|
|
345
|
+
if (err instanceof ExitException) {
|
|
346
|
+
return err.exitCode;
|
|
347
|
+
}
|
|
348
|
+
throw err;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async executeSourceInCurrentFrame(source, stdinSource, stdout, stderr, errorName, positionalOverride) {
|
|
352
|
+
const previousArgv0 = this.argv0;
|
|
353
|
+
const previousPositionals = this.positionalParameters;
|
|
354
|
+
if (positionalOverride?.argv0 !== undefined) {
|
|
355
|
+
this.argv0 = positionalOverride.argv0;
|
|
356
|
+
}
|
|
357
|
+
if (positionalOverride?.args !== undefined) {
|
|
358
|
+
this.positionalParameters = [...positionalOverride.args];
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const ast = this.parseSource(source);
|
|
362
|
+
if (!ast) {
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
return await this.executeNode(ast, stdinSource, stdout, stderr);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
if (err instanceof BreakException || err instanceof ContinueException || err instanceof ExitException) {
|
|
239
368
|
throw err;
|
|
240
369
|
}
|
|
241
370
|
const message = err instanceof Error ? err.message : String(err);
|
|
242
|
-
await stderr.writeText(`${
|
|
371
|
+
await stderr.writeText(`${errorName}: ${message}
|
|
243
372
|
`);
|
|
244
|
-
|
|
373
|
+
return 2;
|
|
374
|
+
} finally {
|
|
375
|
+
if (positionalOverride?.argv0 !== undefined) {
|
|
376
|
+
this.argv0 = previousArgv0;
|
|
377
|
+
}
|
|
378
|
+
if (positionalOverride?.args !== undefined) {
|
|
379
|
+
this.positionalParameters = previousPositionals;
|
|
380
|
+
}
|
|
245
381
|
}
|
|
246
|
-
|
|
247
|
-
|
|
382
|
+
}
|
|
383
|
+
parseSource(source) {
|
|
384
|
+
const tokens = new import_lexer.Lexer(source, { preserveNewlines: true }).tokenize();
|
|
385
|
+
if (tokens.every((token) => token.type === "newline" || token.type === "eof")) {
|
|
386
|
+
return null;
|
|
248
387
|
}
|
|
249
|
-
|
|
250
|
-
|
|
388
|
+
return new import_parser.Parser(tokens).parse();
|
|
389
|
+
}
|
|
390
|
+
async loadScriptSource(pathName, stderr, missingExitCode) {
|
|
391
|
+
const path = this.fs.resolve(this.cwd, pathName);
|
|
392
|
+
if (!await this.fs.exists(path)) {
|
|
393
|
+
await stderr.writeText(`${pathName}: No such file or directory
|
|
394
|
+
`);
|
|
395
|
+
return { ok: false, exitCode: missingExitCode };
|
|
396
|
+
}
|
|
397
|
+
const stat = await this.fs.stat(path);
|
|
398
|
+
if (stat.isDirectory()) {
|
|
399
|
+
await stderr.writeText(`${pathName}: is a directory
|
|
400
|
+
`);
|
|
401
|
+
return { ok: false, exitCode: 126 };
|
|
402
|
+
}
|
|
403
|
+
if (!stat.isFile()) {
|
|
404
|
+
await stderr.writeText(`${pathName}: not a file
|
|
405
|
+
`);
|
|
406
|
+
return { ok: false, exitCode: 126 };
|
|
251
407
|
}
|
|
252
408
|
try {
|
|
253
|
-
await
|
|
409
|
+
return { ok: true, path, source: await this.fs.readFile(path, "utf-8") };
|
|
254
410
|
} catch (err) {
|
|
255
411
|
const message = err instanceof Error ? err.message : String(err);
|
|
256
|
-
|
|
257
|
-
const target = writeRedirects.length > 0 ? await this.expandWordScalar(writeRedirects[writeRedirects.length - 1].target, this.env) : "unknown";
|
|
258
|
-
await stderr.writeText(`sh: ${target}: ${message}
|
|
412
|
+
await stderr.writeText(`${pathName}: ${message}
|
|
259
413
|
`);
|
|
260
|
-
exitCode
|
|
414
|
+
return { ok: false, exitCode: 126 };
|
|
261
415
|
}
|
|
262
|
-
|
|
416
|
+
}
|
|
417
|
+
parseShebang(source) {
|
|
418
|
+
if (!source.startsWith("#!")) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const lineEnd = source.indexOf(`
|
|
422
|
+
`);
|
|
423
|
+
const line = source.slice(2, lineEnd === -1 ? undefined : lineEnd).trim();
|
|
424
|
+
if (line === "") {
|
|
425
|
+
return { command: "sh", args: [], display: "sh" };
|
|
426
|
+
}
|
|
427
|
+
const parts = line.split(/\s+/);
|
|
428
|
+
const executable = parts[0];
|
|
429
|
+
const executableName = this.fs.basename(executable);
|
|
430
|
+
if (executableName === "env") {
|
|
431
|
+
const envCommand = parts[1];
|
|
432
|
+
if (!envCommand) {
|
|
433
|
+
return { command: "env", args: [], display: line };
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
command: this.fs.basename(envCommand),
|
|
437
|
+
args: parts.slice(2),
|
|
438
|
+
display: line
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
command: executableName,
|
|
443
|
+
args: parts.slice(1),
|
|
444
|
+
display: line
|
|
445
|
+
};
|
|
263
446
|
}
|
|
264
447
|
async handleRedirect(redirect, stdin, stdout, stderr, env) {
|
|
265
448
|
const target = await this.expandWordScalar(redirect.target, env);
|
|
@@ -672,6 +855,10 @@ class Interpreter {
|
|
|
672
855
|
this.appendSegment(fields[fields.length - 1], part.value, part.quoted);
|
|
673
856
|
continue;
|
|
674
857
|
}
|
|
858
|
+
if (part.type === "variable" && part.name === "@" && part.quoted) {
|
|
859
|
+
this.appendQuotedPositionalParameters(fields);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
675
862
|
const value = await this.expandWordPart(part, env);
|
|
676
863
|
if (part.quoted) {
|
|
677
864
|
this.appendSegment(fields[fields.length - 1], value, true);
|
|
@@ -695,7 +882,7 @@ class Interpreter {
|
|
|
695
882
|
case "text":
|
|
696
883
|
return part.value;
|
|
697
884
|
case "variable":
|
|
698
|
-
return
|
|
885
|
+
return this.getVariableValue(part.name, env);
|
|
699
886
|
case "substitution":
|
|
700
887
|
return this.executeSubstitution(part.command, env);
|
|
701
888
|
case "arithmetic":
|
|
@@ -704,6 +891,35 @@ class Interpreter {
|
|
|
704
891
|
throw new Error("Cannot expand unknown word part");
|
|
705
892
|
}
|
|
706
893
|
}
|
|
894
|
+
getVariableValue(name, env) {
|
|
895
|
+
if (name === "0") {
|
|
896
|
+
return this.argv0;
|
|
897
|
+
}
|
|
898
|
+
if (name === "#") {
|
|
899
|
+
return String(this.positionalParameters.length);
|
|
900
|
+
}
|
|
901
|
+
if (name === "?") {
|
|
902
|
+
return String(this.lastExitCode);
|
|
903
|
+
}
|
|
904
|
+
if (name === "*" || name === "@") {
|
|
905
|
+
return this.positionalParameters.join(" ");
|
|
906
|
+
}
|
|
907
|
+
if (/^[1-9]$/.test(name)) {
|
|
908
|
+
return this.positionalParameters[Number(name) - 1] ?? "";
|
|
909
|
+
}
|
|
910
|
+
return env[name] ?? "";
|
|
911
|
+
}
|
|
912
|
+
appendQuotedPositionalParameters(fields) {
|
|
913
|
+
if (this.positionalParameters.length === 0) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
this.appendSegment(fields[fields.length - 1], this.positionalParameters[0], true);
|
|
917
|
+
for (let i = 1;i < this.positionalParameters.length; i++) {
|
|
918
|
+
const field = this.createExpandedField();
|
|
919
|
+
this.appendSegment(field, this.positionalParameters[i], true);
|
|
920
|
+
fields.push(field);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
707
923
|
async executeSubstitution(command, env) {
|
|
708
924
|
const interpreter = new Interpreter({
|
|
709
925
|
fs: this.fs,
|
|
@@ -711,7 +927,10 @@ class Interpreter {
|
|
|
711
927
|
env,
|
|
712
928
|
commands: this.commands,
|
|
713
929
|
redirectObjects: this.redirectObjects,
|
|
714
|
-
isTTY: false
|
|
930
|
+
isTTY: false,
|
|
931
|
+
argv0: this.argv0,
|
|
932
|
+
positionalParameters: this.positionalParameters,
|
|
933
|
+
lastExitCode: this.lastExitCode
|
|
715
934
|
});
|
|
716
935
|
const result = await interpreter.execute(command);
|
|
717
936
|
return result.stdout.toString("utf-8").replace(/\n+$/, "");
|
|
@@ -812,8 +1031,8 @@ class Interpreter {
|
|
|
812
1031
|
expandedExpr = expandedExpr.replace(/\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_, name) => {
|
|
813
1032
|
return env[name] ?? "0";
|
|
814
1033
|
});
|
|
815
|
-
expandedExpr = expandedExpr.replace(/\$([a-zA-Z_][a-zA-Z0-9_]
|
|
816
|
-
return
|
|
1034
|
+
expandedExpr = expandedExpr.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*|[0-9#*@?])/g, (_, name) => {
|
|
1035
|
+
return this.getVariableValue(name, env) || "0";
|
|
817
1036
|
});
|
|
818
1037
|
expandedExpr = expandedExpr.replace(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g, (match) => {
|
|
819
1038
|
if (/^\d+$/.test(match))
|
|
@@ -970,6 +1189,12 @@ class Interpreter {
|
|
|
970
1189
|
};
|
|
971
1190
|
return parseOr();
|
|
972
1191
|
}
|
|
1192
|
+
normalizeExitCode(exitCode) {
|
|
1193
|
+
if (!Number.isFinite(exitCode)) {
|
|
1194
|
+
return 2;
|
|
1195
|
+
}
|
|
1196
|
+
return (Math.trunc(exitCode) % 256 + 256) % 256;
|
|
1197
|
+
}
|
|
973
1198
|
setCwd(cwd) {
|
|
974
1199
|
this.env.OLDPWD = this.cwd;
|
|
975
1200
|
this.cwd = cwd;
|
|
@@ -985,4 +1210,4 @@ class Interpreter {
|
|
|
985
1210
|
}
|
|
986
1211
|
}
|
|
987
1212
|
|
|
988
|
-
//# debugId=
|
|
1213
|
+
//# debugId=48778DC29A4E811364756E2164756E21
|