typescript-virtual-container 1.5.6 → 1.5.8
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 +28 -20
- package/dist/.tsbuildinfo +1 -1
- package/dist/SSHMimic/index.d.ts +5 -1
- package/dist/SSHMimic/index.js +27 -3
- package/dist/SSHMimic/prompt.d.ts +2 -1
- package/dist/SSHMimic/prompt.js +27 -5
- package/dist/SSHMimic/scp.d.ts +34 -0
- package/dist/SSHMimic/scp.js +285 -0
- package/dist/SSHMimic/sftp.d.ts +53 -3
- package/dist/SSHMimic/sftp.js +9 -3
- package/dist/VirtualFileSystem/binaryPack.d.ts +7 -0
- package/dist/VirtualFileSystem/binaryPack.js +37 -1
- package/dist/VirtualFileSystem/index.d.ts +7 -0
- package/dist/VirtualFileSystem/index.js +67 -27
- package/dist/VirtualFileSystem/internalTypes.d.ts +2 -0
- package/dist/VirtualFileSystem/path.d.ts +5 -0
- package/dist/VirtualFileSystem/path.js +24 -11
- package/dist/VirtualPackageManager/index.d.ts +4 -2
- package/dist/VirtualPackageManager/index.js +24 -4
- package/dist/VirtualShell/index.d.ts +6 -3
- package/dist/VirtualShell/index.js +3 -10
- package/dist/VirtualShell/shell.js +114 -140
- package/dist/VirtualShell/shellParser.js +1 -22
- package/dist/commands/exit.js +1 -1
- package/dist/commands/find.js +1 -4
- package/dist/commands/helpers.d.ts +0 -20
- package/dist/commands/helpers.js +0 -97
- 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 +10 -1
- package/dist/commands/perl.js +1 -1
- package/dist/commands/python.js +5 -2
- package/dist/commands/registry.js +6 -1
- 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 +90 -88
- package/dist/commands/strace.js +1 -1
- package/dist/commands/tar.js +2 -2
- package/dist/commands/test.js +2 -2
- package/dist/modules/linuxRootfs.js +7 -6
- package/dist/modules/nanoEditor.d.ts +92 -0
- package/dist/modules/nanoEditor.js +956 -0
- package/dist/modules/neofetch.js +2 -2
- package/dist/modules/webTermRenderer.d.ts +42 -0
- package/dist/modules/webTermRenderer.js +291 -0
- package/dist/types/commands.d.ts +4 -0
- package/dist/utils/argv.d.ts +6 -0
- package/dist/utils/argv.js +32 -0
- package/dist/utils/expand.d.ts +5 -2
- package/dist/utils/expand.js +70 -67
- package/dist/utils/glob.d.ts +6 -0
- package/dist/utils/glob.js +34 -0
- package/dist/utils/shellSession.d.ts +10 -0
- package/dist/utils/shellSession.js +56 -0
- package/dist/utils/tokenize.js +13 -13
- package/package.json +7 -6
|
@@ -255,7 +255,12 @@ export function registerCommand(module) {
|
|
|
255
255
|
throw new Error("Command names must be non-empty and contain no spaces");
|
|
256
256
|
}
|
|
257
257
|
customCommands.push(normalized);
|
|
258
|
-
|
|
258
|
+
// Incremental insert — avoids full Map rebuild for every registerCommand call
|
|
259
|
+
commandRegistry.set(normalized.name, normalized);
|
|
260
|
+
for (const alias of normalized.aliases ?? [])
|
|
261
|
+
commandRegistry.set(alias, normalized);
|
|
262
|
+
// Invalidate sorted names cache; rebuilt lazily on next getCommandNames()
|
|
263
|
+
cachedCommandNames = null;
|
|
259
264
|
}
|
|
260
265
|
export function createCustomCommand(name, params, run) {
|
|
261
266
|
return { name, params, run };
|
package/dist/commands/rm.d.ts
CHANGED
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
|
@@ -4,10 +4,42 @@ import { parseScript } from "../VirtualShell/shellParser";
|
|
|
4
4
|
import { expandAsync, expandBraces, expandGlob } from "../utils/expand";
|
|
5
5
|
import { tokenizeCommand } from "../utils/tokenize";
|
|
6
6
|
import { resolveModule } from "./registry";
|
|
7
|
+
// Module-level compiled regexes — avoids recompilation on every runCommand call
|
|
8
|
+
const ASSIGN_RE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
9
|
+
const RE_FOR = /\bfor\s+\w+\s+in\b/;
|
|
10
|
+
const RE_WHILE = /\bwhile\s+/;
|
|
11
|
+
const RE_IF = /\bif\s+/;
|
|
12
|
+
const RE_FUNC_BRACE = /\w+\s*\(\s*\)\s*\{/;
|
|
13
|
+
const RE_FUNC_KW = /\bfunction\s+\w+/;
|
|
14
|
+
const RE_ARITH = /\(\(\s*.+\s*\)\)/;
|
|
15
|
+
const RE_PIPE = /(?<![|&])[|](?![|])/;
|
|
16
|
+
const RE_OPERATORS = /[><;&]|\|\|/;
|
|
7
17
|
/** Returns the home directory path for a given user. Root lives at /root. */
|
|
8
18
|
export function userHome(authUser) {
|
|
9
19
|
return authUser === "root" ? "/root" : `/home/${authUser}`;
|
|
10
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
|
+
}
|
|
11
43
|
export function makeDefaultEnv(authUser, hostname) {
|
|
12
44
|
return {
|
|
13
45
|
vars: {
|
|
@@ -18,7 +50,9 @@ export function makeDefaultEnv(authUser, hostname) {
|
|
|
18
50
|
SHELL: "/bin/bash",
|
|
19
51
|
TERM: "xterm-256color",
|
|
20
52
|
HOSTNAME: hostname,
|
|
21
|
-
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\\]\\$ ",
|
|
22
56
|
"0": "/bin/bash",
|
|
23
57
|
},
|
|
24
58
|
lastExitCode: 0,
|
|
@@ -43,7 +77,13 @@ function resolveVfsBinary(name, env, shell, authUser) {
|
|
|
43
77
|
return null;
|
|
44
78
|
}
|
|
45
79
|
}
|
|
46
|
-
const
|
|
80
|
+
const rawPath = env.vars.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
81
|
+
// Cache split PATH on the env object to avoid re-splitting on every binary lookup
|
|
82
|
+
if (!env._pathDirs || env._pathRaw !== rawPath) {
|
|
83
|
+
env._pathRaw = rawPath;
|
|
84
|
+
env._pathDirs = rawPath.split(":");
|
|
85
|
+
}
|
|
86
|
+
const pathDirs = env._pathDirs;
|
|
47
87
|
for (const dir of pathDirs) {
|
|
48
88
|
if ((dir === "/sbin" || dir === "/usr/sbin") && authUser !== "root")
|
|
49
89
|
continue;
|
|
@@ -63,6 +103,35 @@ function resolveVfsBinary(name, env, shell, authUser) {
|
|
|
63
103
|
return null;
|
|
64
104
|
}
|
|
65
105
|
const MAX_CALL_DEPTH = 8;
|
|
106
|
+
/** Run a VFS stub file as a command, handling `exec builtin <name>` and `sh -c` stubs. */
|
|
107
|
+
async function runVfsStub(vfsBinary, cmdName, args, rawInput, authUser, hostname, mode, cwd, shell, env, stdin) {
|
|
108
|
+
const stubContent = shell.vfs.readFile(vfsBinary);
|
|
109
|
+
const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
|
|
110
|
+
if (builtinMatch) {
|
|
111
|
+
const builtinMod = resolveModule(builtinMatch[1]);
|
|
112
|
+
if (builtinMod) {
|
|
113
|
+
return builtinMod.run({
|
|
114
|
+
authUser, hostname,
|
|
115
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
116
|
+
rawInput, mode, args, stdin, cwd, shell, env,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Guard: missing builtin — stop here to avoid sh -c infinite loop
|
|
120
|
+
return { stderr: `${cmdName}: exec builtin '${builtinMatch[1]}' not found`, exitCode: 127 };
|
|
121
|
+
}
|
|
122
|
+
const shMod = resolveModule("sh");
|
|
123
|
+
if (shMod) {
|
|
124
|
+
return shMod.run({
|
|
125
|
+
authUser, hostname,
|
|
126
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
127
|
+
rawInput: `sh -c ${JSON.stringify(stubContent)}`,
|
|
128
|
+
mode,
|
|
129
|
+
args: ["-c", stubContent, "--", ...args],
|
|
130
|
+
stdin, cwd, shell, env,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return { stderr: `${cmdName}: command not found`, exitCode: 127 };
|
|
134
|
+
}
|
|
66
135
|
let _callDepth = 0;
|
|
67
136
|
export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
|
|
68
137
|
// Anti-loop guard: track call depth via env to avoid infinite recursion
|
|
@@ -81,7 +150,7 @@ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd
|
|
|
81
150
|
}
|
|
82
151
|
}
|
|
83
152
|
async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
|
|
84
|
-
const assignRe =
|
|
153
|
+
const assignRe = ASSIGN_RE;
|
|
85
154
|
const invocation = [name, ...args];
|
|
86
155
|
let assignCount = 0;
|
|
87
156
|
while (assignCount < invocation.length && assignRe.test(invocation[assignCount])) {
|
|
@@ -119,42 +188,7 @@ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd,
|
|
|
119
188
|
if (!mod) {
|
|
120
189
|
const vfsBinary = resolveVfsBinary(name, env, shell, authUser);
|
|
121
190
|
if (vfsBinary) {
|
|
122
|
-
|
|
123
|
-
const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
|
|
124
|
-
if (builtinMatch) {
|
|
125
|
-
const builtinMod = resolveModule(builtinMatch[1]);
|
|
126
|
-
if (builtinMod) {
|
|
127
|
-
return await builtinMod.run({
|
|
128
|
-
authUser,
|
|
129
|
-
hostname,
|
|
130
|
-
activeSessions: shell.users.listActiveSessions(),
|
|
131
|
-
rawInput: [name, ...args].join(" "),
|
|
132
|
-
mode,
|
|
133
|
-
args,
|
|
134
|
-
stdin,
|
|
135
|
-
cwd,
|
|
136
|
-
shell,
|
|
137
|
-
env,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
// builtin not found — stop here, don't fall through to sh -c (avoids infinite loop)
|
|
141
|
-
return { stderr: `${name}: exec builtin '${builtinMatch[1]}' not found`, exitCode: 127 };
|
|
142
|
-
}
|
|
143
|
-
const shMod = resolveModule("sh");
|
|
144
|
-
if (shMod) {
|
|
145
|
-
return await shMod.run({
|
|
146
|
-
authUser,
|
|
147
|
-
hostname,
|
|
148
|
-
activeSessions: shell.users.listActiveSessions(),
|
|
149
|
-
rawInput: `sh -c ${JSON.stringify(stubContent)}`,
|
|
150
|
-
mode,
|
|
151
|
-
args: ["-c", stubContent, "--", ...args],
|
|
152
|
-
stdin,
|
|
153
|
-
cwd,
|
|
154
|
-
shell,
|
|
155
|
-
env,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
191
|
+
return runVfsStub(vfsBinary, name, args, [name, ...args].join(" "), authUser, hostname, mode, cwd, shell, env, stdin);
|
|
158
192
|
}
|
|
159
193
|
return { stderr: `${name}: command not found`, exitCode: 127 };
|
|
160
194
|
}
|
|
@@ -220,18 +254,13 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
|
|
|
220
254
|
? trimmed.replace(rawFirstWord, aliasVal)
|
|
221
255
|
: trimmed;
|
|
222
256
|
// Detect sh-syntax constructs that must be handled by the sh interpreter
|
|
223
|
-
const isShScript =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const hasOperators =
|
|
230
|
-
aliasExpanded.includes(">") ||
|
|
231
|
-
aliasExpanded.includes("<") ||
|
|
232
|
-
aliasExpanded.includes("&&") ||
|
|
233
|
-
aliasExpanded.includes("||") ||
|
|
234
|
-
aliasExpanded.includes(";");
|
|
257
|
+
const isShScript = RE_FOR.test(aliasExpanded) ||
|
|
258
|
+
RE_WHILE.test(aliasExpanded) ||
|
|
259
|
+
RE_IF.test(aliasExpanded) ||
|
|
260
|
+
RE_FUNC_BRACE.test(aliasExpanded) ||
|
|
261
|
+
RE_FUNC_KW.test(aliasExpanded) ||
|
|
262
|
+
RE_ARITH.test(aliasExpanded);
|
|
263
|
+
const hasOperators = RE_PIPE.test(aliasExpanded) || RE_OPERATORS.test(aliasExpanded);
|
|
235
264
|
if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
|
|
236
265
|
// sh-syntax: route through sh interpreter to handle for/while/functions
|
|
237
266
|
if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
|
|
@@ -267,52 +296,25 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
|
|
|
267
296
|
const parts = tokenizeCommand(expanded.trim());
|
|
268
297
|
if (parts.length === 0)
|
|
269
298
|
return { exitCode: 0 };
|
|
270
|
-
const assignRe =
|
|
299
|
+
const assignRe = ASSIGN_RE;
|
|
271
300
|
if (assignRe.test(parts[0])) {
|
|
272
301
|
return runCommandDirect(parts[0], parts.slice(1), authUser, hostname, mode, cwd, shell, stdin, shellEnv);
|
|
273
302
|
}
|
|
274
303
|
const commandName = parts[0]?.toLowerCase() ?? "";
|
|
275
304
|
// Apply brace expansion to each arg token
|
|
276
|
-
const
|
|
305
|
+
const rawArgs = parts.slice(1);
|
|
306
|
+
const args = [];
|
|
307
|
+
for (const token of rawArgs) {
|
|
308
|
+
for (const brace of expandBraces(token)) {
|
|
309
|
+
for (const glob of expandGlob(brace, cwd, shell.vfs))
|
|
310
|
+
args.push(glob);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
277
313
|
const mod = resolveModule(commandName);
|
|
278
314
|
if (!mod) {
|
|
279
315
|
const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
|
|
280
316
|
if (vfsBinary) {
|
|
281
|
-
|
|
282
|
-
const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
|
|
283
|
-
if (builtinMatch) {
|
|
284
|
-
const builtinName = builtinMatch[1];
|
|
285
|
-
const builtinMod = resolveModule(builtinName);
|
|
286
|
-
if (builtinMod) {
|
|
287
|
-
return await builtinMod.run({
|
|
288
|
-
authUser,
|
|
289
|
-
hostname,
|
|
290
|
-
activeSessions: shell.users.listActiveSessions(),
|
|
291
|
-
rawInput: [commandName, ...args].join(" "),
|
|
292
|
-
mode,
|
|
293
|
-
args,
|
|
294
|
-
stdin,
|
|
295
|
-
cwd,
|
|
296
|
-
shell,
|
|
297
|
-
env: shellEnv,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
const shMod = resolveModule("sh");
|
|
302
|
-
if (shMod) {
|
|
303
|
-
return await shMod.run({
|
|
304
|
-
authUser,
|
|
305
|
-
hostname,
|
|
306
|
-
activeSessions: shell.users.listActiveSessions(),
|
|
307
|
-
rawInput: `sh -c ${JSON.stringify(stubContent)}`,
|
|
308
|
-
mode,
|
|
309
|
-
args: ["-c", stubContent, "--", ...args],
|
|
310
|
-
stdin,
|
|
311
|
-
cwd,
|
|
312
|
-
shell,
|
|
313
|
-
env: shellEnv,
|
|
314
|
-
});
|
|
315
|
-
}
|
|
317
|
+
return runVfsStub(vfsBinary, commandName, args, expanded, authUser, hostname, mode, cwd, shell, shellEnv, stdin);
|
|
316
318
|
}
|
|
317
319
|
return { stderr: `${commandName}: command not found`, exitCode: 127 };
|
|
318
320
|
}
|
package/dist/commands/strace.js
CHANGED
|
@@ -11,7 +11,7 @@ export const straceCommand = {
|
|
|
11
11
|
const cmd = args.find((a) => !a.startsWith("-"));
|
|
12
12
|
if (!cmd)
|
|
13
13
|
return { stderr: "strace: must have PROG [ARGS] or -p PID", exitCode: 1 };
|
|
14
|
-
const
|
|
14
|
+
const _pid = Math.floor(Math.random() * 30000) + 1000;
|
|
15
15
|
const lines = [
|
|
16
16
|
`execve("/usr/bin/${cmd}", ["${cmd}"${args.slice(1).map((a) => `, "${a}"`).join("")}], 0x... /* ... vars */) = 0`,
|
|
17
17
|
`brk(NULL) = 0x${(Math.random() * 0xfffff | 0).toString(16)}000`,
|
package/dist/commands/tar.js
CHANGED
|
@@ -11,8 +11,8 @@ function makeTarHeader(name, size, isDir) {
|
|
|
11
11
|
enc(isDir ? "0000755\0" : "0000644\0", 100, 8);
|
|
12
12
|
enc("0000000\0", 108, 8);
|
|
13
13
|
enc("0000000\0", 116, 8);
|
|
14
|
-
enc(size.toString(8).padStart(11, "0")
|
|
15
|
-
enc(Math.floor(Date.now() / 1000).toString(8).padStart(11, "0")
|
|
14
|
+
enc(`${size.toString(8).padStart(11, "0")}\0`, 124, 12);
|
|
15
|
+
enc(`${Math.floor(Date.now() / 1000).toString(8).padStart(11, "0")}\0`, 136, 12);
|
|
16
16
|
hdr[156] = isDir ? 0x35 : 0x30; // '5' dir, '0' file
|
|
17
17
|
enc("ustar\0", 257, 6);
|
|
18
18
|
enc("00", 263, 2);
|
package/dist/commands/test.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolvePath } from "./helpers";
|
|
1
2
|
/**
|
|
2
3
|
* Evaluate a POSIX test expression.
|
|
3
4
|
* Supports: -f, -d, -e, -r, -w, -x, -s, -z, -n,
|
|
@@ -33,8 +34,7 @@ function evalTest(tokens, shell, cwd) {
|
|
|
33
34
|
// Unary file tests
|
|
34
35
|
if (tokens.length === 2) {
|
|
35
36
|
const [flag, operand = ""] = tokens;
|
|
36
|
-
const
|
|
37
|
-
const path = resolvePath(operand);
|
|
37
|
+
const path = resolvePath(cwd, operand);
|
|
38
38
|
switch (flag) {
|
|
39
39
|
case "-e":
|
|
40
40
|
return shell.vfs.exists(path);
|
|
@@ -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[37;1m\\]]\\[\\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[37;1m\\]]\\[\\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.8)
|
|
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[37;1m\\]]\\[\\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'",
|
|
@@ -1547,12 +1551,9 @@ export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime
|
|
|
1547
1551
|
const snapshot = getStaticRootfsSnapshot(hostname, props);
|
|
1548
1552
|
const hasRestoredData = vfs.getMode() === "fs" && vfs.exists("/home");
|
|
1549
1553
|
if (hasRestoredData) {
|
|
1550
|
-
// Snapshot was already restored — merge static rootfs without
|
|
1551
|
-
// clobbering user files and directories.
|
|
1552
1554
|
vfs.mergeRootTree(decodeVfs(snapshot));
|
|
1553
1555
|
}
|
|
1554
1556
|
else {
|
|
1555
|
-
// Fresh start — replace the empty tree with the full static rootfs.
|
|
1556
1557
|
vfs.importRootTree(decodeVfs(snapshot));
|
|
1557
1558
|
}
|
|
1558
1559
|
bootstrapRoot(vfs);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { TerminalSize } from "./shellRuntime";
|
|
2
|
+
import type { ShellStream } from "../types/streams";
|
|
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
|
+
}
|