typescript-virtual-container 1.2.9 → 1.3.0
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/.vscode/settings.json +0 -1
- package/README.md +141 -50
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +32 -16
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +192 -43
- package/dist/VirtualShell/index.d.ts +10 -4
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +18 -7
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +32 -9
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +10 -2
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +5 -0
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +34 -6
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +5 -0
- package/dist/commands/dpkg.d.ts.map +1 -1
- package/dist/commands/dpkg.js +24 -7
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +13 -4
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +5 -0
- package/dist/commands/free.d.ts.map +1 -1
- package/dist/commands/free.js +5 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +5 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -17
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -340
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +3 -1
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +8 -2
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.js +1 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +3 -1
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +4 -1
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +57 -36
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -2
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +46 -24
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +1 -1
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +4 -1
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +32 -7
- package/dist/commands/which.d.ts.map +1 -1
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -9
- package/dist/modules/linuxRootfs.d.ts +18 -1
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +160 -17
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +2 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +104 -18
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +208 -49
- package/src/VirtualShell/index.ts +35 -7
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +7 -2
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +5 -1
- package/src/commands/apt.ts +47 -17
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +13 -5
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +5 -0
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +56 -12
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +33 -11
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +22 -9
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +9 -2
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/history.ts +7 -2
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -360
- package/src/commands/ls.ts +5 -3
- package/src/commands/lsb-release.ts +8 -2
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +1 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +6 -2
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +21 -9
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +159 -55
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +15 -3
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +61 -26
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +6 -3
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +4 -1
- package/src/commands/wget.ts +51 -12
- package/src/commands/which.ts +5 -2
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +23 -24
- package/src/modules/linuxRootfs.ts +233 -30
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +253 -191
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +415 -5
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
package/src/commands/set.ts
CHANGED
|
@@ -11,9 +11,13 @@ const _globalEnv: Record<string, string> = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
/** @deprecated use env.vars from CommandContext */
|
|
14
|
-
export function getEnvVar(name: string): string | undefined {
|
|
14
|
+
export function getEnvVar(name: string): string | undefined {
|
|
15
|
+
return _globalEnv[name];
|
|
16
|
+
}
|
|
15
17
|
/** @deprecated use env.vars from CommandContext */
|
|
16
|
-
export function setEnvVar(name: string, value: string): void {
|
|
18
|
+
export function setEnvVar(name: string, value: string): void {
|
|
19
|
+
_globalEnv[name] = value;
|
|
20
|
+
}
|
|
17
21
|
/** @deprecated use env.vars from CommandContext */
|
|
18
22
|
export function getAllEnvVars(authUser: string): Record<string, string> {
|
|
19
23
|
_globalEnv.USER = authUser;
|
|
@@ -28,7 +32,9 @@ export const setCommand: ShellModule = {
|
|
|
28
32
|
params: ["[VAR=value]"],
|
|
29
33
|
run: ({ args, env }) => {
|
|
30
34
|
if (args.length === 0) {
|
|
31
|
-
const out = Object.entries(env.vars)
|
|
35
|
+
const out = Object.entries(env.vars)
|
|
36
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
37
|
+
.join("\n");
|
|
32
38
|
return { stdout: out, exitCode: 0 };
|
|
33
39
|
}
|
|
34
40
|
for (const arg of args) {
|
package/src/commands/sh.ts
CHANGED
|
@@ -1,42 +1,48 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CommandContext,
|
|
3
|
+
CommandResult,
|
|
4
|
+
ShellModule,
|
|
5
|
+
} from "../types/commands";
|
|
6
|
+
import { expandAsync } from "../utils/expand";
|
|
2
7
|
import { ifFlag } from "./command-helpers";
|
|
3
8
|
import { resolvePath } from "./helpers";
|
|
4
|
-
import { runCommand } from "./
|
|
9
|
+
import { runCommand } from "./runtime";
|
|
5
10
|
|
|
6
11
|
/** Alias for clarity inside sh.ts */
|
|
7
12
|
type ShellContext = CommandContext;
|
|
8
13
|
|
|
9
|
-
/** Expand $VAR and ${VAR:-default} in a line using the current env (sync, no $(cmd)) */
|
|
10
|
-
function expandVarsSync(line: string, env: Record<string, string>, lastExit: number): string {
|
|
11
|
-
return line
|
|
12
|
-
.replace(/\$\?/g, String(lastExit))
|
|
13
|
-
.replace(/\$\{([^}:]+):-([^}]*)\}/g, (_, n, d) => env[n] ?? d)
|
|
14
|
-
.replace(/\$\{([^}]+)\}/g, (_, n) => env[n] ?? "")
|
|
15
|
-
.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, n) => env[n] ?? "")
|
|
16
|
-
.replace(/^~(\/|$)/, `${env.HOME ?? "/home/user"}$1`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
/**
|
|
20
|
-
* Expand
|
|
21
|
-
*
|
|
15
|
+
* Expand all shell forms including $(cmd) substitution.
|
|
16
|
+
* Delegates to centralised expandAsync (single-quote-aware, depth-tracked).
|
|
22
17
|
*/
|
|
23
|
-
async function expandVars(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
18
|
+
async function expandVars(
|
|
19
|
+
line: string,
|
|
20
|
+
env: Record<string, string>,
|
|
21
|
+
lastExit: number,
|
|
22
|
+
ctx: ShellContext,
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
return expandAsync(line, env, lastExit, (sub) =>
|
|
25
|
+
runCommand(
|
|
26
|
+
sub,
|
|
27
|
+
ctx.authUser,
|
|
28
|
+
ctx.hostname,
|
|
29
|
+
ctx.mode,
|
|
30
|
+
ctx.cwd,
|
|
31
|
+
ctx.shell,
|
|
32
|
+
undefined,
|
|
33
|
+
ctx.env,
|
|
34
|
+
).then((r) => r.stdout ?? ""),
|
|
35
|
+
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
type Block =
|
|
39
|
-
| {
|
|
39
|
+
| {
|
|
40
|
+
type: "if";
|
|
41
|
+
cond: string;
|
|
42
|
+
then_: string[];
|
|
43
|
+
elif: Array<{ cond: string; body: string[] }>;
|
|
44
|
+
else_: string[];
|
|
45
|
+
}
|
|
40
46
|
| { type: "for"; var: string; list: string; body: string[] }
|
|
41
47
|
| { type: "while"; cond: string; body: string[] }
|
|
42
48
|
| { type: "cmd"; line: string };
|
|
@@ -47,10 +53,16 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
47
53
|
let i = 0;
|
|
48
54
|
while (i < lines.length) {
|
|
49
55
|
const line = lines[i]!.trim();
|
|
50
|
-
if (!line || line.startsWith("#")) {
|
|
56
|
+
if (!line || line.startsWith("#")) {
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
51
60
|
|
|
52
61
|
if (line.startsWith("if ") || line === "if") {
|
|
53
|
-
const cond = line
|
|
62
|
+
const cond = line
|
|
63
|
+
.replace(/^if\s+/, "")
|
|
64
|
+
.replace(/;\s*then\s*$/, "")
|
|
65
|
+
.trim();
|
|
54
66
|
const thenLines: string[] = [];
|
|
55
67
|
const elifBlocks: Array<{ cond: string; body: string[] }> = [];
|
|
56
68
|
const elseLines: string[] = [];
|
|
@@ -59,17 +71,30 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
59
71
|
i++;
|
|
60
72
|
while (i < lines.length && lines[i]?.trim() !== "fi") {
|
|
61
73
|
const l = lines[i]!.trim();
|
|
62
|
-
if (l.startsWith("elif ")) {
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
if (l.startsWith("elif ")) {
|
|
75
|
+
section = "elif";
|
|
76
|
+
elifCond = l
|
|
77
|
+
.replace(/^elif\s+/, "")
|
|
78
|
+
.replace(/;\s*then\s*$/, "")
|
|
79
|
+
.trim();
|
|
80
|
+
elifBlocks.push({ cond: elifCond, body: [] });
|
|
81
|
+
} else if (l === "else") {
|
|
82
|
+
section = "else";
|
|
83
|
+
} else if (l !== "then") {
|
|
65
84
|
if (section === "then") thenLines.push(l);
|
|
66
|
-
else if (section === "elif" && elifBlocks.length > 0)
|
|
85
|
+
else if (section === "elif" && elifBlocks.length > 0)
|
|
86
|
+
elifBlocks[elifBlocks.length - 1]!.body.push(l);
|
|
67
87
|
else elseLines.push(l);
|
|
68
88
|
}
|
|
69
89
|
i++;
|
|
70
90
|
}
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
blocks.push({
|
|
92
|
+
type: "if",
|
|
93
|
+
cond,
|
|
94
|
+
then_: thenLines,
|
|
95
|
+
elif: elifBlocks,
|
|
96
|
+
else_: elseLines,
|
|
97
|
+
});
|
|
73
98
|
} else if (line.startsWith("for ")) {
|
|
74
99
|
const m = line.match(/^for\s+(\w+)\s+in\s+(.+?)(?:\s*;\s*do)?$/);
|
|
75
100
|
if (m) {
|
|
@@ -81,9 +106,14 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
81
106
|
i++;
|
|
82
107
|
}
|
|
83
108
|
blocks.push({ type: "for", var: m[1]!, list: m[2]!, body });
|
|
84
|
-
} else {
|
|
109
|
+
} else {
|
|
110
|
+
blocks.push({ type: "cmd", line });
|
|
111
|
+
}
|
|
85
112
|
} else if (line.startsWith("while ")) {
|
|
86
|
-
const cond = line
|
|
113
|
+
const cond = line
|
|
114
|
+
.replace(/^while\s+/, "")
|
|
115
|
+
.replace(/;\s*do\s*$/, "")
|
|
116
|
+
.trim();
|
|
87
117
|
const body: string[] = [];
|
|
88
118
|
i++;
|
|
89
119
|
while (i < lines.length && lines[i]?.trim() !== "done") {
|
|
@@ -100,8 +130,16 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
100
130
|
return blocks;
|
|
101
131
|
}
|
|
102
132
|
|
|
103
|
-
async function evalCondition(
|
|
104
|
-
|
|
133
|
+
async function evalCondition(
|
|
134
|
+
cond: string,
|
|
135
|
+
ctx: CommandContext,
|
|
136
|
+
): Promise<boolean> {
|
|
137
|
+
const expanded = await expandVars(
|
|
138
|
+
cond,
|
|
139
|
+
ctx.env.vars,
|
|
140
|
+
ctx.env.lastExitCode,
|
|
141
|
+
ctx,
|
|
142
|
+
);
|
|
105
143
|
// test -f / test -d / [ ... ]
|
|
106
144
|
const testMatch = expanded.match(/^\[?\s*(.+?)\s*\]?$/);
|
|
107
145
|
if (testMatch) {
|
|
@@ -111,8 +149,12 @@ async function evalCondition(cond: string, ctx: CommandContext): Promise<boolean
|
|
|
111
149
|
if (fTest) {
|
|
112
150
|
const [, flag, arg] = fTest;
|
|
113
151
|
const p = resolvePath(ctx.cwd, arg!);
|
|
114
|
-
if (flag === "f")
|
|
115
|
-
|
|
152
|
+
if (flag === "f")
|
|
153
|
+
return ctx.shell.vfs.exists(p) && ctx.shell.vfs.stat(p).type === "file";
|
|
154
|
+
if (flag === "d")
|
|
155
|
+
return (
|
|
156
|
+
ctx.shell.vfs.exists(p) && ctx.shell.vfs.stat(p).type === "directory"
|
|
157
|
+
);
|
|
116
158
|
if (flag === "e") return ctx.shell.vfs.exists(p);
|
|
117
159
|
if (flag === "z") return (arg ?? "").length === 0;
|
|
118
160
|
if (flag === "n") return (arg ?? "").length > 0;
|
|
@@ -128,7 +170,8 @@ async function evalCondition(cond: string, ctx: CommandContext): Promise<boolean
|
|
|
128
170
|
const numMatch = expr.match(/^(\S+)\s+(-eq|-ne|-lt|-le|-gt|-ge)\s+(\S+)$/);
|
|
129
171
|
if (numMatch) {
|
|
130
172
|
const [, a, op, b] = numMatch;
|
|
131
|
-
const na = Number(a),
|
|
173
|
+
const na = Number(a),
|
|
174
|
+
nb = Number(b);
|
|
132
175
|
if (op === "-eq") return na === nb;
|
|
133
176
|
if (op === "-ne") return na !== nb;
|
|
134
177
|
if (op === "-lt") return na < nb;
|
|
@@ -138,18 +181,60 @@ async function evalCondition(cond: string, ctx: CommandContext): Promise<boolean
|
|
|
138
181
|
}
|
|
139
182
|
}
|
|
140
183
|
// fallback: run command and check exit code
|
|
141
|
-
const r = await runCommand(
|
|
184
|
+
const r = await runCommand(
|
|
185
|
+
expanded,
|
|
186
|
+
ctx.authUser,
|
|
187
|
+
ctx.hostname,
|
|
188
|
+
ctx.mode,
|
|
189
|
+
ctx.cwd,
|
|
190
|
+
ctx.shell,
|
|
191
|
+
undefined,
|
|
192
|
+
ctx.env,
|
|
193
|
+
);
|
|
142
194
|
return (r.exitCode ?? 0) === 0;
|
|
143
195
|
}
|
|
144
196
|
|
|
145
|
-
async function runBlocks(
|
|
197
|
+
async function runBlocks(
|
|
198
|
+
blocks: Block[],
|
|
199
|
+
ctx: CommandContext,
|
|
200
|
+
): Promise<CommandResult> {
|
|
146
201
|
let lastResult: CommandResult = { exitCode: 0 };
|
|
147
202
|
let output = "";
|
|
148
203
|
|
|
149
204
|
for (const block of blocks) {
|
|
150
205
|
if (block.type === "cmd") {
|
|
151
|
-
const expanded = await expandVars(
|
|
152
|
-
|
|
206
|
+
const expanded = await expandVars(
|
|
207
|
+
block.line,
|
|
208
|
+
ctx.env.vars,
|
|
209
|
+
ctx.env.lastExitCode,
|
|
210
|
+
ctx,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Bare VAR=val assignment(s) — handle before dispatching to runCommand
|
|
214
|
+
const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)/;
|
|
215
|
+
const tokens = expanded.trim().split(/\s+/);
|
|
216
|
+
if (tokens.length > 0 && assignRe.test(tokens[0]!)) {
|
|
217
|
+
const allAssign = tokens.every((t) => assignRe.test(t));
|
|
218
|
+
if (allAssign) {
|
|
219
|
+
for (const tok of tokens) {
|
|
220
|
+
const m = tok.match(assignRe)!;
|
|
221
|
+
ctx.env.vars[m[1]!] = m[2]!;
|
|
222
|
+
}
|
|
223
|
+
ctx.env.lastExitCode = 0;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const r = await runCommand(
|
|
229
|
+
expanded,
|
|
230
|
+
ctx.authUser,
|
|
231
|
+
ctx.hostname,
|
|
232
|
+
ctx.mode,
|
|
233
|
+
ctx.cwd,
|
|
234
|
+
ctx.shell,
|
|
235
|
+
undefined,
|
|
236
|
+
ctx.env,
|
|
237
|
+
);
|
|
153
238
|
ctx.env.lastExitCode = r.exitCode ?? 0;
|
|
154
239
|
if (r.stdout) output += `${r.stdout}\n`;
|
|
155
240
|
if (r.stderr) return { ...r, stdout: output.trim() };
|
|
@@ -157,7 +242,7 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
157
242
|
} else if (block.type === "if") {
|
|
158
243
|
let ran = false;
|
|
159
244
|
if (await evalCondition(block.cond, ctx)) {
|
|
160
|
-
const sub = await runBlocks(parseBlocks(block.
|
|
245
|
+
const sub = await runBlocks(parseBlocks(block.then_), ctx);
|
|
161
246
|
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
162
247
|
ran = true;
|
|
163
248
|
} else {
|
|
@@ -165,7 +250,8 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
165
250
|
if (await evalCondition(elif.cond, ctx)) {
|
|
166
251
|
const sub = await runBlocks(parseBlocks(elif.body), ctx);
|
|
167
252
|
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
168
|
-
ran = true;
|
|
253
|
+
ran = true;
|
|
254
|
+
break;
|
|
169
255
|
}
|
|
170
256
|
}
|
|
171
257
|
if (!ran && block.else_.length > 0) {
|
|
@@ -174,7 +260,12 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
174
260
|
}
|
|
175
261
|
}
|
|
176
262
|
} else if (block.type === "for") {
|
|
177
|
-
const listExpanded = await expandVars(
|
|
263
|
+
const listExpanded = await expandVars(
|
|
264
|
+
block.list,
|
|
265
|
+
ctx.env.vars,
|
|
266
|
+
ctx.env.lastExitCode,
|
|
267
|
+
ctx,
|
|
268
|
+
);
|
|
178
269
|
const items = listExpanded.trim().split(/\s+/);
|
|
179
270
|
for (const item of items) {
|
|
180
271
|
ctx.env.vars[block.var] = item;
|
|
@@ -184,7 +275,7 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
184
275
|
}
|
|
185
276
|
} else if (block.type === "while") {
|
|
186
277
|
let iterations = 0;
|
|
187
|
-
while (iterations < 1000 && await evalCondition(block.cond, ctx)) {
|
|
278
|
+
while (iterations < 1000 && (await evalCondition(block.cond, ctx))) {
|
|
188
279
|
const sub = await runBlocks(parseBlocks(block.body), ctx);
|
|
189
280
|
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
190
281
|
if (sub.closeSession) return sub;
|
|
@@ -208,7 +299,10 @@ export const shCommand: ShellModule = {
|
|
|
208
299
|
if (ifFlag(args, "-c")) {
|
|
209
300
|
const script = args[args.indexOf("-c") + 1] ?? "";
|
|
210
301
|
if (!script) return { stderr: "sh: -c requires a script", exitCode: 1 };
|
|
211
|
-
const lines = script
|
|
302
|
+
const lines = script
|
|
303
|
+
.split(/[;\n]/)
|
|
304
|
+
.map((l) => l.trim())
|
|
305
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
212
306
|
const blocks = parseBlocks(lines);
|
|
213
307
|
return runBlocks(blocks, ctx);
|
|
214
308
|
}
|
|
@@ -217,13 +311,23 @@ export const shCommand: ShellModule = {
|
|
|
217
311
|
const fileArg = args[0];
|
|
218
312
|
if (fileArg) {
|
|
219
313
|
const p = resolvePath(cwd, fileArg);
|
|
220
|
-
if (!shell.vfs.exists(p))
|
|
314
|
+
if (!shell.vfs.exists(p))
|
|
315
|
+
return {
|
|
316
|
+
stderr: `sh: ${fileArg}: No such file or directory`,
|
|
317
|
+
exitCode: 1,
|
|
318
|
+
};
|
|
221
319
|
const content = shell.vfs.readFile(p);
|
|
222
|
-
const lines = content
|
|
320
|
+
const lines = content
|
|
321
|
+
.split("\n")
|
|
322
|
+
.map((l) => l.trim())
|
|
323
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
223
324
|
const blocks = parseBlocks(lines);
|
|
224
325
|
return runBlocks(blocks, ctx);
|
|
225
326
|
}
|
|
226
327
|
|
|
227
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
stderr: "sh: invalid usage. Use: sh -c 'cmd' or sh <file>",
|
|
330
|
+
exitCode: 1,
|
|
331
|
+
};
|
|
228
332
|
},
|
|
229
333
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
|
|
3
|
+
export const shiftCommand: ShellModule = {
|
|
4
|
+
name: "shift",
|
|
5
|
+
description: "Shift positional parameters",
|
|
6
|
+
category: "shell",
|
|
7
|
+
params: ["[n]"],
|
|
8
|
+
// shift is meaningful only inside sh scripts where positional params exist.
|
|
9
|
+
// In the current impl, positional params ($1 $2 …) aren't tracked in env by default.
|
|
10
|
+
// We store them under env.vars.__argv and shift there if present.
|
|
11
|
+
run: ({ args, env }) => {
|
|
12
|
+
if (!env) return { exitCode: 0 };
|
|
13
|
+
const n = parseInt(args[0] ?? "1", 10) || 1;
|
|
14
|
+
const argv = env.vars.__argv?.split("\x00").filter(Boolean) ?? [];
|
|
15
|
+
env.vars.__argv = argv.slice(n).join("\x00");
|
|
16
|
+
// Update $1 $2 … in env
|
|
17
|
+
const shifted = argv.slice(n);
|
|
18
|
+
for (let i = 1; i <= 9; i++) {
|
|
19
|
+
env.vars[String(i)] = shifted[i - 1] ?? "";
|
|
20
|
+
}
|
|
21
|
+
return { exitCode: 0 };
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const trapCommand: ShellModule = {
|
|
26
|
+
name: "trap",
|
|
27
|
+
description: "Trap signals and events",
|
|
28
|
+
category: "shell",
|
|
29
|
+
params: ["[action] [signal...]"],
|
|
30
|
+
// Store trap handlers in env for EXIT signal support
|
|
31
|
+
run: ({ args, env }) => {
|
|
32
|
+
if (!env || args.length === 0) return { exitCode: 0 };
|
|
33
|
+
const action = args[0] ?? "";
|
|
34
|
+
const signals = args.slice(1);
|
|
35
|
+
for (const sig of signals) {
|
|
36
|
+
env.vars[`__trap_${sig.toUpperCase()}`] = action;
|
|
37
|
+
}
|
|
38
|
+
return { exitCode: 0 };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const returnCommand: ShellModule = {
|
|
43
|
+
name: "return",
|
|
44
|
+
description: "Return from a shell function",
|
|
45
|
+
category: "shell",
|
|
46
|
+
params: ["[n]"],
|
|
47
|
+
run: ({ args, env }) => {
|
|
48
|
+
const code = parseInt(args[0] ?? "0", 10);
|
|
49
|
+
if (env) env.lastExitCode = code;
|
|
50
|
+
// Signal the caller via exitCode; function return is handled by runBlocks
|
|
51
|
+
return { exitCode: code };
|
|
52
|
+
},
|
|
53
|
+
};
|
package/src/commands/sleep.ts
CHANGED
|
@@ -7,7 +7,8 @@ export const sleepCommand: ShellModule = {
|
|
|
7
7
|
params: ["<seconds>"],
|
|
8
8
|
run: async ({ args }) => {
|
|
9
9
|
const secs = parseFloat(args[0] ?? "1");
|
|
10
|
-
if (Number.isNaN(secs) || secs < 0)
|
|
10
|
+
if (Number.isNaN(secs) || secs < 0)
|
|
11
|
+
return { stderr: "sleep: invalid time", exitCode: 1 };
|
|
11
12
|
await new Promise((r) => setTimeout(r, secs * 1000));
|
|
12
13
|
return { exitCode: 0 };
|
|
13
14
|
},
|
package/src/commands/sort.ts
CHANGED
|
@@ -15,12 +15,16 @@ export const sortCommand: ShellModule = {
|
|
|
15
15
|
|
|
16
16
|
const getContent = (): string => {
|
|
17
17
|
if (files.length > 0) {
|
|
18
|
-
return files
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
return files
|
|
19
|
+
.map((f) => {
|
|
20
|
+
try {
|
|
21
|
+
assertPathAccess(authUser, resolvePath(cwd, f), "sort");
|
|
22
|
+
return shell.vfs.readFile(resolvePath(cwd, f));
|
|
23
|
+
} catch {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.join("\n");
|
|
24
28
|
}
|
|
25
29
|
return stdin ?? "";
|
|
26
30
|
};
|
package/src/commands/source.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { resolvePath } from "./helpers";
|
|
3
|
-
import { runCommand } from "
|
|
3
|
+
import { runCommand } from "./runtime";
|
|
4
4
|
|
|
5
5
|
export const sourceCommand: ShellModule = {
|
|
6
6
|
name: "source",
|
|
@@ -16,7 +16,10 @@ export const sourceCommand: ShellModule = {
|
|
|
16
16
|
|
|
17
17
|
const filePath = resolvePath(cwd, fileArg);
|
|
18
18
|
if (!shell.vfs.exists(filePath)) {
|
|
19
|
-
return {
|
|
19
|
+
return {
|
|
20
|
+
stderr: `source: ${fileArg}: No such file or directory`,
|
|
21
|
+
exitCode: 1,
|
|
22
|
+
};
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
const content = shell.vfs.readFile(filePath);
|
|
@@ -25,7 +28,16 @@ export const sourceCommand: ShellModule = {
|
|
|
25
28
|
for (const line of content.split("\n")) {
|
|
26
29
|
const l = line.trim();
|
|
27
30
|
if (!l || l.startsWith("#")) continue;
|
|
28
|
-
const result = await runCommand(
|
|
31
|
+
const result = await runCommand(
|
|
32
|
+
l,
|
|
33
|
+
authUser,
|
|
34
|
+
hostname,
|
|
35
|
+
"shell",
|
|
36
|
+
cwd,
|
|
37
|
+
shell,
|
|
38
|
+
undefined,
|
|
39
|
+
env,
|
|
40
|
+
);
|
|
29
41
|
lastExitCode = result.exitCode ?? 0;
|
|
30
42
|
if (result.closeSession || result.switchUser) return result;
|
|
31
43
|
}
|
package/src/commands/sudo.ts
CHANGED
package/src/commands/tar.ts
CHANGED
|
@@ -12,13 +12,22 @@ export const tarCommand: ShellModule = {
|
|
|
12
12
|
const extract = ifFlag(args, ["-x"]);
|
|
13
13
|
const list = ifFlag(args, ["-t"]);
|
|
14
14
|
const fFlag = args.findIndex((a) => a.includes("f"));
|
|
15
|
-
const archiveName =
|
|
15
|
+
const archiveName =
|
|
16
|
+
fFlag !== -1
|
|
17
|
+
? args[fFlag + 1]
|
|
18
|
+
: args.find(
|
|
19
|
+
(a) =>
|
|
20
|
+
a.endsWith(".tar") || a.endsWith(".tar.gz") || a.endsWith(".tgz"),
|
|
21
|
+
);
|
|
16
22
|
|
|
17
|
-
if (!archiveName)
|
|
23
|
+
if (!archiveName)
|
|
24
|
+
return { stderr: "tar: no archive specified", exitCode: 1 };
|
|
18
25
|
const archivePath = resolvePath(cwd, archiveName);
|
|
19
26
|
|
|
20
27
|
if (create) {
|
|
21
|
-
const fileArgs = args.filter(
|
|
28
|
+
const fileArgs = args.filter(
|
|
29
|
+
(a) => !a.startsWith("-") && a !== archiveName,
|
|
30
|
+
);
|
|
22
31
|
const entries: Record<string, string> = {};
|
|
23
32
|
for (const f of fileArgs) {
|
|
24
33
|
const p = resolvePath(cwd, f);
|
|
@@ -28,7 +37,8 @@ export const tarCommand: ShellModule = {
|
|
|
28
37
|
else {
|
|
29
38
|
const walk = (dir: string, prefix: string) => {
|
|
30
39
|
for (const e of shell.vfs.list(dir)) {
|
|
31
|
-
const full = `${dir}/${e}`,
|
|
40
|
+
const full = `${dir}/${e}`,
|
|
41
|
+
rel = `${prefix}/${e}`;
|
|
32
42
|
const s = shell.vfs.stat(full);
|
|
33
43
|
if (s.type === "file") entries[rel] = shell.vfs.readFile(full);
|
|
34
44
|
else walk(full, rel);
|
|
@@ -36,7 +46,12 @@ export const tarCommand: ShellModule = {
|
|
|
36
46
|
};
|
|
37
47
|
walk(p, f);
|
|
38
48
|
}
|
|
39
|
-
} catch {
|
|
49
|
+
} catch {
|
|
50
|
+
return {
|
|
51
|
+
stderr: `tar: ${f}: No such file or directory`,
|
|
52
|
+
exitCode: 1,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
40
55
|
}
|
|
41
56
|
shell.writeFileAsUser(authUser, archivePath, JSON.stringify(entries));
|
|
42
57
|
return { exitCode: 0 };
|
|
@@ -44,8 +59,14 @@ export const tarCommand: ShellModule = {
|
|
|
44
59
|
|
|
45
60
|
if (list || extract) {
|
|
46
61
|
let entries: Record<string, string>;
|
|
47
|
-
try {
|
|
48
|
-
|
|
62
|
+
try {
|
|
63
|
+
entries = JSON.parse(shell.vfs.readFile(archivePath));
|
|
64
|
+
} catch {
|
|
65
|
+
return {
|
|
66
|
+
stderr: `tar: ${archiveName}: cannot open archive`,
|
|
67
|
+
exitCode: 1,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
49
70
|
if (list) return { stdout: Object.keys(entries).join("\n"), exitCode: 0 };
|
|
50
71
|
for (const [name, content] of Object.entries(entries)) {
|
|
51
72
|
shell.writeFileAsUser(authUser, resolvePath(cwd, name), content);
|
package/src/commands/tee.ts
CHANGED
|
@@ -14,7 +14,13 @@ export const teeCommand: ShellModule = {
|
|
|
14
14
|
for (const f of files) {
|
|
15
15
|
const p = resolvePath(cwd, f);
|
|
16
16
|
if (append) {
|
|
17
|
-
const existing = (() => {
|
|
17
|
+
const existing = (() => {
|
|
18
|
+
try {
|
|
19
|
+
return shell.vfs.readFile(p);
|
|
20
|
+
} catch {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
18
24
|
shell.writeFileAsUser(authUser, p, existing + input);
|
|
19
25
|
} else {
|
|
20
26
|
shell.writeFileAsUser(authUser, p, input);
|