typescript-virtual-container 1.5.7 → 1.5.9
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 +39 -29
- package/dist/.tsbuildinfo +1 -1
- package/dist/SSHMimic/executor.js +9 -0
- package/dist/SSHMimic/prompt.d.ts +2 -1
- package/dist/SSHMimic/prompt.js +28 -6
- package/dist/SSHMimic/sftp.d.ts +1 -1
- package/dist/SSHMimic/sftp.js +1 -1
- package/dist/VirtualShell/index.d.ts +2 -3
- package/dist/VirtualShell/index.js +2 -3
- package/dist/VirtualShell/shell.js +108 -134
- package/dist/VirtualShell/shellParser.js +35 -3
- package/dist/commands/coreutils.d.ts +55 -0
- package/dist/commands/coreutils.js +275 -0
- package/dist/commands/id.js +8 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/manuals-bundle.js +237 -1
- package/dist/commands/pacman.d.ts +8 -0
- package/dist/commands/pacman.js +15 -0
- package/dist/commands/registry.js +13 -0
- package/dist/commands/rm.d.ts +1 -1
- package/dist/commands/rm.js +48 -11
- package/dist/commands/runtime.d.ts +5 -0
- package/dist/commands/runtime.js +60 -1
- package/dist/commands/sh.js +5 -3
- package/dist/modules/linuxRootfs.js +7 -3
- package/dist/modules/nanoEditor.d.ts +92 -0
- package/dist/modules/nanoEditor.js +974 -0
- package/dist/modules/pacmanGame.d.ts +59 -0
- package/dist/modules/pacmanGame.js +655 -0
- package/dist/modules/webTermRenderer.d.ts +50 -0
- package/dist/modules/webTermRenderer.js +425 -0
- package/dist/types/commands.d.ts +2 -0
- package/dist/types/pipeline.d.ts +2 -0
- package/dist/utils/shellSession.d.ts +10 -0
- package/dist/utils/shellSession.js +56 -0
- package/package.json +2 -2
package/dist/commands/rm.js
CHANGED
|
@@ -1,36 +1,73 @@
|
|
|
1
1
|
import { getArg, ifFlag } from "./command-helpers";
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
|
+
const FLAG_RECURSIVE = ["-r", "-R", "-rf", "-fr", "-rF", "-Fr"];
|
|
4
|
+
const FLAG_FORCE = ["-f", "-rf", "-fr", "-rF", "-Fr", "--force"];
|
|
3
5
|
/**
|
|
4
6
|
* Remove files or directories from the filesystem.
|
|
5
7
|
* @category files
|
|
6
|
-
* @params ["[-r|-rf] <path>"]
|
|
8
|
+
* @params ["[-r|-rf|-f] <path>"]
|
|
7
9
|
*/
|
|
8
10
|
export const rmCommand = {
|
|
9
11
|
name: "rm",
|
|
10
12
|
description: "Remove files or directories",
|
|
11
13
|
category: "files",
|
|
12
|
-
params: ["[-r|-rf] <path>"],
|
|
14
|
+
params: ["[-r|-rf|-f] <path>"],
|
|
13
15
|
run: ({ authUser, shell, cwd, args }) => {
|
|
14
16
|
if (args.length === 0) {
|
|
15
17
|
return { stderr: "rm: missing operand", exitCode: 1 };
|
|
16
18
|
}
|
|
17
|
-
const recursive = ifFlag(args,
|
|
19
|
+
const recursive = ifFlag(args, FLAG_RECURSIVE);
|
|
20
|
+
const force = ifFlag(args, FLAG_FORCE);
|
|
21
|
+
const allFlags = [...FLAG_RECURSIVE, ...FLAG_FORCE, "--force"];
|
|
18
22
|
const targets = [];
|
|
19
23
|
for (let index = 0;; index += 1) {
|
|
20
|
-
const target = getArg(args, index, { flags:
|
|
21
|
-
if (!target)
|
|
24
|
+
const target = getArg(args, index, { flags: allFlags });
|
|
25
|
+
if (!target)
|
|
22
26
|
break;
|
|
23
|
-
}
|
|
24
27
|
targets.push(target);
|
|
25
28
|
}
|
|
26
29
|
if (targets.length === 0) {
|
|
27
30
|
return { stderr: "rm: missing operand", exitCode: 1 };
|
|
28
31
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
assertPathAccess(authUser,
|
|
32
|
-
|
|
32
|
+
const resolved = targets.map((t) => resolvePath(cwd, t));
|
|
33
|
+
for (const r of resolved)
|
|
34
|
+
assertPathAccess(authUser, r, "rm");
|
|
35
|
+
for (const r of resolved) {
|
|
36
|
+
if (!shell.vfs.exists(r)) {
|
|
37
|
+
if (force)
|
|
38
|
+
continue;
|
|
39
|
+
return { stderr: `rm: cannot remove '${r}': No such file or directory`, exitCode: 1 };
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
|
-
|
|
42
|
+
const doRemove = (sh) => {
|
|
43
|
+
for (const r of resolved)
|
|
44
|
+
if (sh.vfs.exists(r))
|
|
45
|
+
sh.vfs.remove(r, { recursive });
|
|
46
|
+
return { exitCode: 0 };
|
|
47
|
+
};
|
|
48
|
+
if (force)
|
|
49
|
+
return doRemove(shell);
|
|
50
|
+
const label = targets.length === 1 ? `'${targets[0]}'` : `${targets.length} items`;
|
|
51
|
+
const prompt = recursive
|
|
52
|
+
? `rm: remove ${label} recursively? [y/N] `
|
|
53
|
+
: `rm: remove ${label}? [y/N] `;
|
|
54
|
+
return {
|
|
55
|
+
sudoChallenge: {
|
|
56
|
+
username: authUser,
|
|
57
|
+
targetUser: authUser,
|
|
58
|
+
commandLine: null,
|
|
59
|
+
loginShell: false,
|
|
60
|
+
prompt,
|
|
61
|
+
mode: "confirm",
|
|
62
|
+
onPassword: async (input, sh) => {
|
|
63
|
+
const answer = input.trim().toLowerCase();
|
|
64
|
+
if (answer !== "y" && answer !== "yes") {
|
|
65
|
+
return { result: { stdout: "rm: cancelled\n", exitCode: 1 } };
|
|
66
|
+
}
|
|
67
|
+
return { result: doRemove(sh) };
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
exitCode: 0,
|
|
71
|
+
};
|
|
35
72
|
},
|
|
36
73
|
};
|
|
@@ -2,6 +2,11 @@ import type { VirtualShell } from "../VirtualShell";
|
|
|
2
2
|
import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
|
|
3
3
|
/** Returns the home directory path for a given user. Root lives at /root. */
|
|
4
4
|
export declare function userHome(authUser: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Apply a user switch: reset PS1/USER/HOME/LOGNAME in shellEnv and re-source
|
|
7
|
+
* the new user's .bashrc. Call this after setting authUser = newUser.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyUserSwitch(newUser: string, hostname: string, cwd: string, shellEnv: ShellEnv, shell: VirtualShell): Promise<void>;
|
|
5
10
|
export declare function makeDefaultEnv(authUser: string, hostname: string): ShellEnv;
|
|
6
11
|
export declare function runCommandDirect(name: string, args: string[], authUser: string, hostname: string, mode: CommandMode, cwd: string, shell: VirtualShell, stdin: string | undefined, env: ShellEnv): Promise<CommandResult>;
|
|
7
12
|
export declare function runCommand(rawInput: string, authUser: string, hostname: string, mode: CommandMode, cwd: string, shell: VirtualShell, stdin?: string, env?: ShellEnv): Promise<CommandResult>;
|
package/dist/commands/runtime.js
CHANGED
|
@@ -18,6 +18,28 @@ const RE_OPERATORS = /[><;&]|\|\|/;
|
|
|
18
18
|
export function userHome(authUser) {
|
|
19
19
|
return authUser === "root" ? "/root" : `/home/${authUser}`;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Apply a user switch: reset PS1/USER/HOME/LOGNAME in shellEnv and re-source
|
|
23
|
+
* the new user's .bashrc. Call this after setting authUser = newUser.
|
|
24
|
+
*/
|
|
25
|
+
export async function applyUserSwitch(newUser, hostname, cwd, shellEnv, shell) {
|
|
26
|
+
shellEnv.vars.USER = newUser;
|
|
27
|
+
shellEnv.vars.LOGNAME = newUser;
|
|
28
|
+
shellEnv.vars.HOME = userHome(newUser);
|
|
29
|
+
shellEnv.vars.PS1 = makeDefaultEnv(newUser, hostname).vars.PS1 ?? "";
|
|
30
|
+
const rcPath = `${userHome(newUser)}/.bashrc`;
|
|
31
|
+
if (!shell.vfs.exists(rcPath))
|
|
32
|
+
return;
|
|
33
|
+
for (const raw of shell.vfs.readFile(rcPath).split("\n")) {
|
|
34
|
+
const l = raw.trim();
|
|
35
|
+
if (!l || l.startsWith("#"))
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
await runCommand(l, newUser, hostname, "shell", cwd, shell, undefined, shellEnv);
|
|
39
|
+
}
|
|
40
|
+
catch { /* ignore */ }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
21
43
|
export function makeDefaultEnv(authUser, hostname) {
|
|
22
44
|
return {
|
|
23
45
|
vars: {
|
|
@@ -28,7 +50,9 @@ export function makeDefaultEnv(authUser, hostname) {
|
|
|
28
50
|
SHELL: "/bin/bash",
|
|
29
51
|
TERM: "xterm-256color",
|
|
30
52
|
HOSTNAME: hostname,
|
|
31
|
-
PS1:
|
|
53
|
+
PS1: authUser === "root"
|
|
54
|
+
? "\\[\\e[37;1m\\][\\[\\e[31;1m\\]\\u\\[\\e[37;1m\\]@\\[\\e[34;1m\\]\\h\\[\\e[0m\\] \\w\\[\\e[37;1m\\]]\\[\\e[31;1m\\]\\$\\[\\e[0m\\] "
|
|
55
|
+
: "\\[\\e[37;1m\\][\\[\\e[35;1m\\]\\u\\[\\e[37;1m\\]@\\[\\e[34;1m\\]\\h\\[\\e[0m\\] \\w\\[\\e[37;1m\\]]\\[\\e[0m\\]\\$ ",
|
|
32
56
|
"0": "/bin/bash",
|
|
33
57
|
},
|
|
34
58
|
lastExitCode: 0,
|
|
@@ -156,6 +180,41 @@ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd,
|
|
|
156
180
|
}
|
|
157
181
|
}
|
|
158
182
|
}
|
|
183
|
+
// Shell function defined via sh.ts (stored as __func_<name>)
|
|
184
|
+
const funcBody = env.vars[`__func_${name}`];
|
|
185
|
+
if (funcBody) {
|
|
186
|
+
const shMod = resolveModule("sh");
|
|
187
|
+
if (!shMod)
|
|
188
|
+
return { stderr: `${name}: sh not available`, exitCode: 127 };
|
|
189
|
+
const savedPositional = {};
|
|
190
|
+
args.forEach((a, i) => {
|
|
191
|
+
savedPositional[String(i + 1)] = env.vars[String(i + 1)];
|
|
192
|
+
env.vars[String(i + 1)] = a;
|
|
193
|
+
});
|
|
194
|
+
savedPositional["0"] = env.vars["0"];
|
|
195
|
+
env.vars["0"] = name;
|
|
196
|
+
try {
|
|
197
|
+
return await shMod.run({
|
|
198
|
+
authUser, hostname,
|
|
199
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
200
|
+
rawInput: funcBody,
|
|
201
|
+
mode,
|
|
202
|
+
args: ["-c", funcBody],
|
|
203
|
+
stdin,
|
|
204
|
+
cwd,
|
|
205
|
+
shell,
|
|
206
|
+
env,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
for (const [k, v] of Object.entries(savedPositional)) {
|
|
211
|
+
if (v === undefined)
|
|
212
|
+
delete env.vars[k];
|
|
213
|
+
else
|
|
214
|
+
env.vars[k] = v;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
159
218
|
const aliasVal = env.vars[`__alias_${name}`];
|
|
160
219
|
if (aliasVal) {
|
|
161
220
|
return runCommand(`${aliasVal} ${args.join(" ")}`, authUser, hostname, mode, cwd, shell, stdin, env);
|
package/dist/commands/sh.js
CHANGED
|
@@ -20,9 +20,11 @@ function parseBlocks(lines) {
|
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
22
|
// Function definition: name() { or function name { or name() { body }
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
23
|
+
// Shell allows any non-whitespace identifier as function name (incl. ':')
|
|
24
|
+
const funcNamePat = "[^\\s(){}]+";
|
|
25
|
+
const funcMatchInline = line.match(new RegExp(`^(?:function\\s+)?(${funcNamePat})\\s*\\(\\s*\\)\\s*\\{(.+)\\}\\s*$`));
|
|
26
|
+
const funcMatch = funcMatchInline ?? (line.match(new RegExp(`^(?:function\\s+)?(${funcNamePat})\\s*\\(\\s*\\)\\s*\\{?\\s*$`)) ||
|
|
27
|
+
line.match(new RegExp(`^function\\s+(${funcNamePat})\\s*\\{?\\s*$`)));
|
|
26
28
|
if (funcMatch) {
|
|
27
29
|
const funcName = funcMatch[1];
|
|
28
30
|
const body = [];
|
|
@@ -59,7 +59,11 @@ function bootstrapEtc(vfs, hostname, props) {
|
|
|
59
59
|
ensureFile(vfs, "/etc/shells", "/bin/sh\n/bin/bash\n/usr/bin/bash\n/bin/dash\n/usr/bin/dash\n");
|
|
60
60
|
ensureFile(vfs, "/etc/profile", `${[
|
|
61
61
|
"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
62
|
-
"
|
|
62
|
+
"if [ \"$(id -u)\" -eq 0 ]; then",
|
|
63
|
+
" export PS1='\\[\\e[37;1m\\][\\[\\e[31;1m\\]\\u\\[\\e[37;1m\\]@\\[\\e[34;1m\\]\\h\\[\\e[0m\\] \\w]\\[\\e[31;1m\\]\\$\\[\\e[0m\\] '",
|
|
64
|
+
"else",
|
|
65
|
+
" export PS1='\\[\\e[37;1m\\][\\[\\e[35;1m\\]\\u\\[\\e[37;1m\\]@\\[\\e[34;1m\\]\\h\\[\\e[0m\\] \\w]\\[\\e[0m\\]\\$ '",
|
|
66
|
+
"fi",
|
|
63
67
|
].join("\n")}\n`);
|
|
64
68
|
ensureFile(vfs, "/etc/issue", "Fortune GNU/Linux 24.04 LTS \\n \\l\n");
|
|
65
69
|
ensureFile(vfs, "/etc/issue.net", "Fortune GNU/Linux 24.04 LTS\n");
|
|
@@ -1281,7 +1285,7 @@ Installed-Size: 6800
|
|
|
1281
1285
|
Maintainer: Fortune Package Team <dpkg@fortune.local>
|
|
1282
1286
|
Architecture: amd64
|
|
1283
1287
|
Version: 1.22.6nyx1
|
|
1284
|
-
Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.
|
|
1288
|
+
Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.9)
|
|
1285
1289
|
Description: Fortune package management system
|
|
1286
1290
|
This package provides the low-level infrastructure for handling the
|
|
1287
1291
|
installation and removal of Fortune software packages.
|
|
@@ -1449,7 +1453,7 @@ function bootstrapRoot(vfs) {
|
|
|
1449
1453
|
ensureDir(vfs, "/root/.local/share", 0o755);
|
|
1450
1454
|
ensureFile(vfs, "/root/.bashrc", `${[
|
|
1451
1455
|
"# root .bashrc",
|
|
1452
|
-
"export PS1='\\[\\
|
|
1456
|
+
"export PS1='\\[\\e[37;1m\\][\\[\\e[31;1m\\]\\u\\[\\e[37;1m\\]@\\[\\e[34;1m\\]\\h\\[\\e[0m\\] \\w]\\[\\e[31;1m\\]\\$\\[\\e[0m\\] '",
|
|
1453
1457
|
"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
1454
1458
|
"export LANG=en_US.UTF-8",
|
|
1455
1459
|
"alias ll='ls -la'",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ShellStream } from "../types/streams";
|
|
2
|
+
import type { TerminalSize } from "./shellRuntime";
|
|
3
|
+
export type NanoExitReason = "saved" | "aborted";
|
|
4
|
+
export interface NanoEditorOptions {
|
|
5
|
+
stream: ShellStream;
|
|
6
|
+
terminalSize: TerminalSize;
|
|
7
|
+
content: string;
|
|
8
|
+
filename: string;
|
|
9
|
+
onExit: (reason: NanoExitReason, content: string) => void;
|
|
10
|
+
/** Called on ^S / silent save — save without closing nano. Optional. */
|
|
11
|
+
onSave?: (content: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare class NanoEditor {
|
|
14
|
+
private lines;
|
|
15
|
+
private cursorRow;
|
|
16
|
+
private cursorCol;
|
|
17
|
+
private scrollTop;
|
|
18
|
+
private modified;
|
|
19
|
+
private filename;
|
|
20
|
+
private mode;
|
|
21
|
+
private inputBuffer;
|
|
22
|
+
private searchState;
|
|
23
|
+
private clipboard;
|
|
24
|
+
private undoStack;
|
|
25
|
+
private redoStack;
|
|
26
|
+
private markActive;
|
|
27
|
+
private readonly stream;
|
|
28
|
+
private terminalSize;
|
|
29
|
+
private readonly onExit;
|
|
30
|
+
private readonly onSave;
|
|
31
|
+
constructor(opts: NanoEditorOptions);
|
|
32
|
+
start(): void;
|
|
33
|
+
resize(size: TerminalSize): void;
|
|
34
|
+
handleInput(chunk: Buffer): void;
|
|
35
|
+
private consumeSequence;
|
|
36
|
+
private handleEscape;
|
|
37
|
+
private handleAlt;
|
|
38
|
+
private handleChar;
|
|
39
|
+
private handleControl;
|
|
40
|
+
private dispatch;
|
|
41
|
+
private handlePromptChar;
|
|
42
|
+
private moveCursor;
|
|
43
|
+
private moveCursorLeft;
|
|
44
|
+
private moveCursorRight;
|
|
45
|
+
private moveCursorHome;
|
|
46
|
+
private moveCursorEnd;
|
|
47
|
+
private movePage;
|
|
48
|
+
private moveWordRight;
|
|
49
|
+
private moveWordLeft;
|
|
50
|
+
private pushUndo;
|
|
51
|
+
private doInsertChar;
|
|
52
|
+
private doEnter;
|
|
53
|
+
private doBackspace;
|
|
54
|
+
private doDelete;
|
|
55
|
+
private doCutLine;
|
|
56
|
+
private doUncut;
|
|
57
|
+
private doUndo;
|
|
58
|
+
private doRedo;
|
|
59
|
+
private enterSearch;
|
|
60
|
+
private doSearch;
|
|
61
|
+
private doSearchNext;
|
|
62
|
+
private doSearchReplace;
|
|
63
|
+
private toggleMark;
|
|
64
|
+
private doExit;
|
|
65
|
+
private doSave;
|
|
66
|
+
private enterWriteout;
|
|
67
|
+
private showCursorPos;
|
|
68
|
+
private enterGotoLine;
|
|
69
|
+
private enterHelp;
|
|
70
|
+
private get cols();
|
|
71
|
+
private get rows();
|
|
72
|
+
private editAreaRows;
|
|
73
|
+
private editAreaStart;
|
|
74
|
+
private currentLine;
|
|
75
|
+
private clampScroll;
|
|
76
|
+
private getCurrentContent;
|
|
77
|
+
private pad;
|
|
78
|
+
fullRedraw(): void;
|
|
79
|
+
private renderTitleBar;
|
|
80
|
+
private renderEditArea;
|
|
81
|
+
private renderLine;
|
|
82
|
+
private renderCursor;
|
|
83
|
+
private renderStatusLine;
|
|
84
|
+
private renderStatusBar;
|
|
85
|
+
private buildTitleBar;
|
|
86
|
+
private buildEditArea;
|
|
87
|
+
private renderLineText;
|
|
88
|
+
private buildHelpBar;
|
|
89
|
+
private buildShortcutRow;
|
|
90
|
+
private buildCursorPosition;
|
|
91
|
+
private renderHelp;
|
|
92
|
+
}
|