typescript-virtual-container 1.2.8 → 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 +462 -44
- 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 +35 -21
- 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 +202 -0
- package/dist/VirtualPackageManager/index.d.ts.map +1 -0
- package/dist/VirtualPackageManager/index.js +825 -0
- package/dist/VirtualShell/index.d.ts +93 -12
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +95 -13
- 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 +52 -20
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +54 -20
- 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 +9 -0
- package/dist/commands/alias.d.ts.map +1 -0
- package/dist/commands/alias.js +63 -0
- package/dist/commands/apt.d.ts +9 -0
- package/dist/commands/apt.d.ts.map +1 -0
- package/dist/commands/apt.js +205 -0
- 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 +35 -8
- 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 +57 -3
- package/dist/commands/command-helpers.d.ts +78 -4
- package/dist/commands/command-helpers.d.ts.map +1 -1
- package/dist/commands/command-helpers.js +78 -4
- 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 +106 -26
- 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 +9 -0
- package/dist/commands/dpkg.d.ts.map +1 -0
- package/dist/commands/dpkg.js +161 -0
- 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 +33 -12
- 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 +8 -0
- package/dist/commands/free.d.ts.map +1 -0
- package/dist/commands/free.js +43 -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/helpers.d.ts +3 -0
- package/dist/commands/helpers.d.ts.map +1 -1
- package/dist/commands/helpers.js +3 -0
- package/dist/commands/history.d.ts +8 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +26 -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 -10
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -231
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +6 -3
- package/dist/commands/lsb-release.d.ts +3 -0
- package/dist/commands/lsb-release.d.ts.map +1 -0
- package/dist/commands/lsb-release.js +56 -0
- package/dist/commands/man.d.ts +3 -0
- package/dist/commands/man.d.ts.map +1 -0
- package/dist/commands/man.js +155 -0
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +6 -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 +7 -2
- 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 +30 -6
- 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 +69 -30
- 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 +3 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +34 -0
- 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 +3 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +114 -0
- 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 +3 -0
- package/dist/commands/type.d.ts.map +1 -0
- package/dist/commands/type.js +34 -0
- 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 +3 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +43 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +92 -96
- package/dist/commands/which.d.ts +3 -0
- package/dist/commands/which.d.ts.map +1 -0
- package/dist/commands/which.js +32 -0
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/modules/linuxRootfs.d.ts +41 -0
- package/dist/modules/linuxRootfs.d.ts.map +1 -0
- package/dist/modules/linuxRootfs.js +440 -0
- package/dist/modules/neofetch.d.ts.map +1 -1
- package/dist/modules/neofetch.js +1 -0
- 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 +3 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +105 -21
- 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 +979 -0
- package/src/VirtualShell/index.ts +133 -14
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +62 -22
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +64 -0
- package/src/commands/apt.ts +228 -0
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +40 -8
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +53 -3
- package/src/commands/command-helpers.ts +78 -4
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +118 -33
- 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 +180 -0
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +41 -12
- 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 +47 -0
- 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/helpers.ts +8 -0
- package/src/commands/history.ts +34 -0
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -255
- package/src/commands/ls.ts +6 -3
- package/src/commands/lsb-release.ts +58 -0
- package/src/commands/man.ts +166 -0
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +6 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +10 -3
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +40 -6
- 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 +170 -44
- 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 +47 -0
- 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 +135 -0
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +43 -0
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +49 -0
- package/src/commands/wget.ts +105 -119
- package/src/commands/which.ts +37 -0
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +27 -18
- package/src/modules/linuxRootfs.ts +642 -0
- package/src/modules/neofetch.ts +1 -0
- 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 +486 -109
- 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 +1036 -0
- 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,20 +1,48 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
CommandContext,
|
|
3
|
+
CommandResult,
|
|
4
|
+
ShellModule,
|
|
5
|
+
} from "../types/commands";
|
|
6
|
+
import { expandAsync } from "../utils/expand";
|
|
7
|
+
import { ifFlag } from "./command-helpers";
|
|
3
8
|
import { resolvePath } from "./helpers";
|
|
4
|
-
import { runCommand } from "./
|
|
9
|
+
import { runCommand } from "./runtime";
|
|
5
10
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/** Alias for clarity inside sh.ts */
|
|
12
|
+
type ShellContext = CommandContext;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Expand all shell forms including $(cmd) substitution.
|
|
16
|
+
* Delegates to centralised expandAsync (single-quote-aware, depth-tracked).
|
|
17
|
+
*/
|
|
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
|
+
);
|
|
14
36
|
}
|
|
15
37
|
|
|
16
38
|
type Block =
|
|
17
|
-
| {
|
|
39
|
+
| {
|
|
40
|
+
type: "if";
|
|
41
|
+
cond: string;
|
|
42
|
+
then_: string[];
|
|
43
|
+
elif: Array<{ cond: string; body: string[] }>;
|
|
44
|
+
else_: string[];
|
|
45
|
+
}
|
|
18
46
|
| { type: "for"; var: string; list: string; body: string[] }
|
|
19
47
|
| { type: "while"; cond: string; body: string[] }
|
|
20
48
|
| { type: "cmd"; line: string };
|
|
@@ -25,10 +53,16 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
25
53
|
let i = 0;
|
|
26
54
|
while (i < lines.length) {
|
|
27
55
|
const line = lines[i]!.trim();
|
|
28
|
-
if (!line || line.startsWith("#")) {
|
|
56
|
+
if (!line || line.startsWith("#")) {
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
29
60
|
|
|
30
61
|
if (line.startsWith("if ") || line === "if") {
|
|
31
|
-
const cond = line
|
|
62
|
+
const cond = line
|
|
63
|
+
.replace(/^if\s+/, "")
|
|
64
|
+
.replace(/;\s*then\s*$/, "")
|
|
65
|
+
.trim();
|
|
32
66
|
const thenLines: string[] = [];
|
|
33
67
|
const elifBlocks: Array<{ cond: string; body: string[] }> = [];
|
|
34
68
|
const elseLines: string[] = [];
|
|
@@ -37,36 +71,54 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
37
71
|
i++;
|
|
38
72
|
while (i < lines.length && lines[i]?.trim() !== "fi") {
|
|
39
73
|
const l = lines[i]!.trim();
|
|
40
|
-
if (l.startsWith("elif ")) {
|
|
41
|
-
|
|
42
|
-
|
|
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") {
|
|
43
84
|
if (section === "then") thenLines.push(l);
|
|
44
|
-
else if (section === "elif" && elifBlocks.length > 0)
|
|
85
|
+
else if (section === "elif" && elifBlocks.length > 0)
|
|
86
|
+
elifBlocks[elifBlocks.length - 1]!.body.push(l);
|
|
45
87
|
else elseLines.push(l);
|
|
46
88
|
}
|
|
47
89
|
i++;
|
|
48
90
|
}
|
|
49
|
-
|
|
50
|
-
|
|
91
|
+
blocks.push({
|
|
92
|
+
type: "if",
|
|
93
|
+
cond,
|
|
94
|
+
then_: thenLines,
|
|
95
|
+
elif: elifBlocks,
|
|
96
|
+
else_: elseLines,
|
|
97
|
+
});
|
|
51
98
|
} else if (line.startsWith("for ")) {
|
|
52
99
|
const m = line.match(/^for\s+(\w+)\s+in\s+(.+?)(?:\s*;\s*do)?$/);
|
|
53
100
|
if (m) {
|
|
54
101
|
const body: string[] = [];
|
|
55
102
|
i++;
|
|
56
103
|
while (i < lines.length && lines[i]?.trim() !== "done") {
|
|
57
|
-
const l = lines[i]!.trim();
|
|
58
|
-
if (l !== "do") body.push(l);
|
|
104
|
+
const l = lines[i]!.trim().replace(/^do\s+/, "");
|
|
105
|
+
if (l && l !== "do") body.push(l);
|
|
59
106
|
i++;
|
|
60
107
|
}
|
|
61
108
|
blocks.push({ type: "for", var: m[1]!, list: m[2]!, body });
|
|
62
|
-
} else {
|
|
109
|
+
} else {
|
|
110
|
+
blocks.push({ type: "cmd", line });
|
|
111
|
+
}
|
|
63
112
|
} else if (line.startsWith("while ")) {
|
|
64
|
-
const cond = line
|
|
113
|
+
const cond = line
|
|
114
|
+
.replace(/^while\s+/, "")
|
|
115
|
+
.replace(/;\s*do\s*$/, "")
|
|
116
|
+
.trim();
|
|
65
117
|
const body: string[] = [];
|
|
66
118
|
i++;
|
|
67
119
|
while (i < lines.length && lines[i]?.trim() !== "done") {
|
|
68
|
-
const l = lines[i]!.trim();
|
|
69
|
-
if (l !== "do") body.push(l);
|
|
120
|
+
const l = lines[i]!.trim().replace(/^do\s+/, "");
|
|
121
|
+
if (l && l !== "do") body.push(l);
|
|
70
122
|
i++;
|
|
71
123
|
}
|
|
72
124
|
blocks.push({ type: "while", cond, body });
|
|
@@ -78,8 +130,16 @@ function parseBlocks(lines: string[]): Block[] {
|
|
|
78
130
|
return blocks;
|
|
79
131
|
}
|
|
80
132
|
|
|
81
|
-
async function evalCondition(
|
|
82
|
-
|
|
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
|
+
);
|
|
83
143
|
// test -f / test -d / [ ... ]
|
|
84
144
|
const testMatch = expanded.match(/^\[?\s*(.+?)\s*\]?$/);
|
|
85
145
|
if (testMatch) {
|
|
@@ -89,8 +149,12 @@ async function evalCondition(cond: string, ctx: CommandContext): Promise<boolean
|
|
|
89
149
|
if (fTest) {
|
|
90
150
|
const [, flag, arg] = fTest;
|
|
91
151
|
const p = resolvePath(ctx.cwd, arg!);
|
|
92
|
-
if (flag === "f")
|
|
93
|
-
|
|
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
|
+
);
|
|
94
158
|
if (flag === "e") return ctx.shell.vfs.exists(p);
|
|
95
159
|
if (flag === "z") return (arg ?? "").length === 0;
|
|
96
160
|
if (flag === "n") return (arg ?? "").length > 0;
|
|
@@ -106,7 +170,8 @@ async function evalCondition(cond: string, ctx: CommandContext): Promise<boolean
|
|
|
106
170
|
const numMatch = expr.match(/^(\S+)\s+(-eq|-ne|-lt|-le|-gt|-ge)\s+(\S+)$/);
|
|
107
171
|
if (numMatch) {
|
|
108
172
|
const [, a, op, b] = numMatch;
|
|
109
|
-
const na = Number(a),
|
|
173
|
+
const na = Number(a),
|
|
174
|
+
nb = Number(b);
|
|
110
175
|
if (op === "-eq") return na === nb;
|
|
111
176
|
if (op === "-ne") return na !== nb;
|
|
112
177
|
if (op === "-lt") return na < nb;
|
|
@@ -116,18 +181,60 @@ async function evalCondition(cond: string, ctx: CommandContext): Promise<boolean
|
|
|
116
181
|
}
|
|
117
182
|
}
|
|
118
183
|
// fallback: run command and check exit code
|
|
119
|
-
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
|
+
);
|
|
120
194
|
return (r.exitCode ?? 0) === 0;
|
|
121
195
|
}
|
|
122
196
|
|
|
123
|
-
async function runBlocks(
|
|
197
|
+
async function runBlocks(
|
|
198
|
+
blocks: Block[],
|
|
199
|
+
ctx: CommandContext,
|
|
200
|
+
): Promise<CommandResult> {
|
|
124
201
|
let lastResult: CommandResult = { exitCode: 0 };
|
|
125
202
|
let output = "";
|
|
126
203
|
|
|
127
204
|
for (const block of blocks) {
|
|
128
205
|
if (block.type === "cmd") {
|
|
129
|
-
const expanded = expandVars(
|
|
130
|
-
|
|
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
|
+
);
|
|
131
238
|
ctx.env.lastExitCode = r.exitCode ?? 0;
|
|
132
239
|
if (r.stdout) output += `${r.stdout}\n`;
|
|
133
240
|
if (r.stderr) return { ...r, stdout: output.trim() };
|
|
@@ -135,7 +242,7 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
135
242
|
} else if (block.type === "if") {
|
|
136
243
|
let ran = false;
|
|
137
244
|
if (await evalCondition(block.cond, ctx)) {
|
|
138
|
-
const sub = await runBlocks(parseBlocks(block.
|
|
245
|
+
const sub = await runBlocks(parseBlocks(block.then_), ctx);
|
|
139
246
|
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
140
247
|
ran = true;
|
|
141
248
|
} else {
|
|
@@ -143,7 +250,8 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
143
250
|
if (await evalCondition(elif.cond, ctx)) {
|
|
144
251
|
const sub = await runBlocks(parseBlocks(elif.body), ctx);
|
|
145
252
|
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
146
|
-
ran = true;
|
|
253
|
+
ran = true;
|
|
254
|
+
break;
|
|
147
255
|
}
|
|
148
256
|
}
|
|
149
257
|
if (!ran && block.else_.length > 0) {
|
|
@@ -152,7 +260,12 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
152
260
|
}
|
|
153
261
|
}
|
|
154
262
|
} else if (block.type === "for") {
|
|
155
|
-
const listExpanded = expandVars(
|
|
263
|
+
const listExpanded = await expandVars(
|
|
264
|
+
block.list,
|
|
265
|
+
ctx.env.vars,
|
|
266
|
+
ctx.env.lastExitCode,
|
|
267
|
+
ctx,
|
|
268
|
+
);
|
|
156
269
|
const items = listExpanded.trim().split(/\s+/);
|
|
157
270
|
for (const item of items) {
|
|
158
271
|
ctx.env.vars[block.var] = item;
|
|
@@ -162,7 +275,7 @@ async function runBlocks(blocks: Block[], ctx: CommandContext): Promise<CommandR
|
|
|
162
275
|
}
|
|
163
276
|
} else if (block.type === "while") {
|
|
164
277
|
let iterations = 0;
|
|
165
|
-
while (iterations < 1000 && await evalCondition(block.cond, ctx)) {
|
|
278
|
+
while (iterations < 1000 && (await evalCondition(block.cond, ctx))) {
|
|
166
279
|
const sub = await runBlocks(parseBlocks(block.body), ctx);
|
|
167
280
|
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
168
281
|
if (sub.closeSession) return sub;
|
|
@@ -184,9 +297,12 @@ export const shCommand: ShellModule = {
|
|
|
184
297
|
|
|
185
298
|
// sh -c "inline script"
|
|
186
299
|
if (ifFlag(args, "-c")) {
|
|
187
|
-
const script =
|
|
300
|
+
const script = args[args.indexOf("-c") + 1] ?? "";
|
|
188
301
|
if (!script) return { stderr: "sh: -c requires a script", exitCode: 1 };
|
|
189
|
-
const lines = script
|
|
302
|
+
const lines = script
|
|
303
|
+
.split(/[;\n]/)
|
|
304
|
+
.map((l) => l.trim())
|
|
305
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
190
306
|
const blocks = parseBlocks(lines);
|
|
191
307
|
return runBlocks(blocks, ctx);
|
|
192
308
|
}
|
|
@@ -195,13 +311,23 @@ export const shCommand: ShellModule = {
|
|
|
195
311
|
const fileArg = args[0];
|
|
196
312
|
if (fileArg) {
|
|
197
313
|
const p = resolvePath(cwd, fileArg);
|
|
198
|
-
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
|
+
};
|
|
199
319
|
const content = shell.vfs.readFile(p);
|
|
200
|
-
const lines = content
|
|
320
|
+
const lines = content
|
|
321
|
+
.split("\n")
|
|
322
|
+
.map((l) => l.trim())
|
|
323
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
201
324
|
const blocks = parseBlocks(lines);
|
|
202
325
|
return runBlocks(blocks, ctx);
|
|
203
326
|
}
|
|
204
327
|
|
|
205
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
stderr: "sh: invalid usage. Use: sh -c 'cmd' or sh <file>",
|
|
330
|
+
exitCode: 1,
|
|
331
|
+
};
|
|
206
332
|
},
|
|
207
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
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
import { resolvePath } from "./helpers";
|
|
3
|
+
import { runCommand } from "./runtime";
|
|
4
|
+
|
|
5
|
+
export const sourceCommand: ShellModule = {
|
|
6
|
+
name: "source",
|
|
7
|
+
aliases: ["."],
|
|
8
|
+
description: "Execute commands from a file in the current shell environment",
|
|
9
|
+
category: "shell",
|
|
10
|
+
params: ["<file> [args...]"],
|
|
11
|
+
run: async ({ args, authUser, hostname, cwd, shell, env }) => {
|
|
12
|
+
const fileArg = args[0];
|
|
13
|
+
if (!fileArg) {
|
|
14
|
+
return { stderr: "source: missing filename", exitCode: 1 };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const filePath = resolvePath(cwd, fileArg);
|
|
18
|
+
if (!shell.vfs.exists(filePath)) {
|
|
19
|
+
return {
|
|
20
|
+
stderr: `source: ${fileArg}: No such file or directory`,
|
|
21
|
+
exitCode: 1,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const content = shell.vfs.readFile(filePath);
|
|
26
|
+
let lastExitCode = 0;
|
|
27
|
+
|
|
28
|
+
for (const line of content.split("\n")) {
|
|
29
|
+
const l = line.trim();
|
|
30
|
+
if (!l || l.startsWith("#")) continue;
|
|
31
|
+
const result = await runCommand(
|
|
32
|
+
l,
|
|
33
|
+
authUser,
|
|
34
|
+
hostname,
|
|
35
|
+
"shell",
|
|
36
|
+
cwd,
|
|
37
|
+
shell,
|
|
38
|
+
undefined,
|
|
39
|
+
env,
|
|
40
|
+
);
|
|
41
|
+
lastExitCode = result.exitCode ?? 0;
|
|
42
|
+
if (result.closeSession || result.switchUser) return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { exitCode: lastExitCode };
|
|
46
|
+
},
|
|
47
|
+
};
|
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);
|