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.
Files changed (37) hide show
  1. package/README.md +39 -29
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/SSHMimic/executor.js +9 -0
  4. package/dist/SSHMimic/prompt.d.ts +2 -1
  5. package/dist/SSHMimic/prompt.js +28 -6
  6. package/dist/SSHMimic/sftp.d.ts +1 -1
  7. package/dist/SSHMimic/sftp.js +1 -1
  8. package/dist/VirtualShell/index.d.ts +2 -3
  9. package/dist/VirtualShell/index.js +2 -3
  10. package/dist/VirtualShell/shell.js +108 -134
  11. package/dist/VirtualShell/shellParser.js +35 -3
  12. package/dist/commands/coreutils.d.ts +55 -0
  13. package/dist/commands/coreutils.js +275 -0
  14. package/dist/commands/id.js +8 -1
  15. package/dist/commands/index.d.ts +1 -1
  16. package/dist/commands/index.js +1 -1
  17. package/dist/commands/manuals-bundle.js +237 -1
  18. package/dist/commands/pacman.d.ts +8 -0
  19. package/dist/commands/pacman.js +15 -0
  20. package/dist/commands/registry.js +13 -0
  21. package/dist/commands/rm.d.ts +1 -1
  22. package/dist/commands/rm.js +48 -11
  23. package/dist/commands/runtime.d.ts +5 -0
  24. package/dist/commands/runtime.js +60 -1
  25. package/dist/commands/sh.js +5 -3
  26. package/dist/modules/linuxRootfs.js +7 -3
  27. package/dist/modules/nanoEditor.d.ts +92 -0
  28. package/dist/modules/nanoEditor.js +974 -0
  29. package/dist/modules/pacmanGame.d.ts +59 -0
  30. package/dist/modules/pacmanGame.js +655 -0
  31. package/dist/modules/webTermRenderer.d.ts +50 -0
  32. package/dist/modules/webTermRenderer.js +425 -0
  33. package/dist/types/commands.d.ts +2 -0
  34. package/dist/types/pipeline.d.ts +2 -0
  35. package/dist/utils/shellSession.d.ts +10 -0
  36. package/dist/utils/shellSession.js +56 -0
  37. package/package.json +2 -2
@@ -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, ["-r", "-rf", "-fr"]);
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: ["-r", "-rf", "-fr"] });
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
- for (const target of targets) {
30
- const resolvedTarget = resolvePath(cwd, target);
31
- assertPathAccess(authUser, resolvedTarget, "rm");
32
- shell.vfs.remove(resolvedTarget, { recursive });
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
- return { exitCode: 0 };
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>;
@@ -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: "\\u@\\h:\\w\\$ ",
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);
@@ -20,9 +20,11 @@ function parseBlocks(lines) {
20
20
  continue;
21
21
  }
22
22
  // Function definition: name() { or function name { or name() { body }
23
- const funcMatchInline = line.match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{(.+)\}\s*$/);
24
- const funcMatch = funcMatchInline ?? (line.match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{?\s*$/) ||
25
- line.match(/^function\s+(\w+)\s*\{?\s*$/));
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
- "export PS1='\\u@\\h:\\w\\$ '",
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.7)
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='\\[\\033[0;31m\\]\\u@\\h\\[\\033[0m\\]:\\[\\033[0;34m\\]\\w\\[\\033[0m\\]# '",
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
+ }