typescript-virtual-container 1.5.6 → 1.5.7
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/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 +4 -0
- package/dist/VirtualShell/index.js +1 -7
- package/dist/VirtualShell/shell.js +40 -10
- 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/perl.js +1 -1
- package/dist/commands/python.js +5 -2
- package/dist/commands/registry.js +6 -1
- package/dist/commands/runtime.js +65 -87
- 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 +1 -4
- package/dist/modules/neofetch.js +2 -2
- 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/tokenize.js +13 -13
- package/package.json +7 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { globToRegex } from "../utils/glob";
|
|
1
2
|
import { tokenizeCommand } from "../utils/tokenize";
|
|
2
3
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
3
4
|
/**
|
|
@@ -42,28 +43,6 @@ export function expandGlob(pattern, entries) {
|
|
|
42
43
|
const matches = entries.filter((e) => regex.test(e));
|
|
43
44
|
return matches.length > 0 ? matches.sort() : [pattern];
|
|
44
45
|
}
|
|
45
|
-
function globToRegex(pattern) {
|
|
46
|
-
let re = "^";
|
|
47
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
48
|
-
const c = pattern[i];
|
|
49
|
-
if (c === "*")
|
|
50
|
-
re += ".*";
|
|
51
|
-
else if (c === "?")
|
|
52
|
-
re += ".";
|
|
53
|
-
else if (c === "[") {
|
|
54
|
-
const close = pattern.indexOf("]", i + 1);
|
|
55
|
-
if (close === -1)
|
|
56
|
-
re += "\\[";
|
|
57
|
-
else {
|
|
58
|
-
re += `[${pattern.slice(i + 1, close)}]`;
|
|
59
|
-
i = close;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else
|
|
63
|
-
re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
64
|
-
}
|
|
65
|
-
return new RegExp(`${re}$`);
|
|
66
|
-
}
|
|
67
46
|
// ── Internal parser ───────────────────────────────────────────────────────────
|
|
68
47
|
function parseStatements(input) {
|
|
69
48
|
// Split by ;, &&, || — respecting quotes and parens
|
package/dist/commands/exit.js
CHANGED
package/dist/commands/find.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { globToRegex } from "../utils/glob";
|
|
1
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
2
3
|
import { runCommand } from "./runtime";
|
|
3
4
|
/**
|
|
@@ -109,10 +110,6 @@ export const findCommand = {
|
|
|
109
110
|
return [{ type: "true" }, pos + 1];
|
|
110
111
|
}
|
|
111
112
|
const pred = exprArgs.length > 0 ? parseExpr(exprArgs, 0)[0] : { type: "true" };
|
|
112
|
-
function globToRegex(pat, flags = "") {
|
|
113
|
-
const esc = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
114
|
-
return new RegExp(`^${esc}$`, flags);
|
|
115
|
-
}
|
|
116
113
|
function matchPred(p, fullPath, depth) {
|
|
117
114
|
switch (p.type) {
|
|
118
115
|
case "true": return true;
|
|
@@ -1,28 +1,8 @@
|
|
|
1
1
|
import type VirtualFileSystem from "../VirtualFileSystem";
|
|
2
2
|
import type { VirtualPackageManager } from "../VirtualPackageManager";
|
|
3
3
|
import type { VirtualShell } from "../VirtualShell";
|
|
4
|
-
export declare function normalizeTerminalOutput(text: string): string;
|
|
5
4
|
export declare function resolvePath(cwd: string, inputPath: string, homeDir?: string): string;
|
|
6
5
|
export declare function assertPathAccess(authUser: string, targetPath: string, operation: string): void;
|
|
7
6
|
export declare function stripUrlFilename(url: string): string;
|
|
8
|
-
export declare function fetchResource(url: string): Promise<{
|
|
9
|
-
text: string;
|
|
10
|
-
status: number;
|
|
11
|
-
contentType: string | null;
|
|
12
|
-
}>;
|
|
13
|
-
/**
|
|
14
|
-
* Run a host command like curl or wget and capture its output.
|
|
15
|
-
* @param binary - The binary to execute (e.g., "curl", "wget").
|
|
16
|
-
* @param args - Arguments to pass to the binary.
|
|
17
|
-
* @returns Promise resolving with stdout, stderr, and exit code.
|
|
18
|
-
*/
|
|
19
|
-
export declare function runHostCommand(binary: string, args: string[]): Promise<{
|
|
20
|
-
stdout: string;
|
|
21
|
-
stderr: string;
|
|
22
|
-
exitCode: number;
|
|
23
|
-
}>;
|
|
24
7
|
export declare function resolveReadablePath(vfs: VirtualFileSystem, cwd: string, inputPath: string): string;
|
|
25
|
-
export declare function joinListWithType(cwd: string, items: string[], statAt: (p: string) => {
|
|
26
|
-
type: "file" | "directory";
|
|
27
|
-
}): string;
|
|
28
8
|
export declare function getPackageManager(shell: VirtualShell): VirtualPackageManager | undefined;
|
package/dist/commands/helpers.js
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
const PROTECTED_PREFIXES = ["/.virtual-env-js/.auth", "/etc/htpasswd"];
|
|
4
|
-
function normalizeFetchUrl(input) {
|
|
5
|
-
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(input)) {
|
|
6
|
-
return input;
|
|
7
|
-
}
|
|
8
|
-
return `http://${input}`;
|
|
9
|
-
}
|
|
10
|
-
export function normalizeTerminalOutput(text) {
|
|
11
|
-
return text
|
|
12
|
-
.replace(/\r\n/g, "\n")
|
|
13
|
-
.replace(/\r/g, "\n")
|
|
14
|
-
.replace(/\t/g, " ")
|
|
15
|
-
.split("\n")
|
|
16
|
-
.map((line) => line.replace(/^[ \u00A0]{8,}/, " ").replace(/[ \u00A0]{3,}/g, " "))
|
|
17
|
-
.join("\n")
|
|
18
|
-
.replace(/\n{3,}/g, "\n\n")
|
|
19
|
-
.trimEnd();
|
|
20
|
-
}
|
|
21
3
|
export function resolvePath(cwd, inputPath, homeDir) {
|
|
22
4
|
if (!inputPath || inputPath.trim() === "") {
|
|
23
5
|
return cwd;
|
|
@@ -49,76 +31,6 @@ export function stripUrlFilename(url) {
|
|
|
49
31
|
const lastPart = cleaned.split("/").filter(Boolean).pop();
|
|
50
32
|
return lastPart && lastPart.length > 0 ? lastPart : "index.html";
|
|
51
33
|
}
|
|
52
|
-
export async function fetchResource(url) {
|
|
53
|
-
const response = await fetch(normalizeFetchUrl(url));
|
|
54
|
-
const contentType = response.headers.get("content-type");
|
|
55
|
-
return {
|
|
56
|
-
text: await response.text(),
|
|
57
|
-
status: response.status,
|
|
58
|
-
contentType,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Run a host command like curl or wget and capture its output.
|
|
63
|
-
* @param binary - The binary to execute (e.g., "curl", "wget").
|
|
64
|
-
* @param args - Arguments to pass to the binary.
|
|
65
|
-
* @returns Promise resolving with stdout, stderr, and exit code.
|
|
66
|
-
*/
|
|
67
|
-
export function runHostCommand(binary, args) {
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
let childProcess;
|
|
70
|
-
try {
|
|
71
|
-
childProcess = spawn(binary, args, {
|
|
72
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
resolve({
|
|
77
|
-
stdout: "",
|
|
78
|
-
stderr: `${binary}: ${error instanceof Error ? error.message : String(error)}`,
|
|
79
|
-
exitCode: 1,
|
|
80
|
-
});
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
let stdout = "";
|
|
84
|
-
let stderr = "";
|
|
85
|
-
const stdoutStream = childProcess.stdout;
|
|
86
|
-
const stderrStream = childProcess.stderr;
|
|
87
|
-
if (!stdoutStream || !stderrStream) {
|
|
88
|
-
resolve({
|
|
89
|
-
stdout: "",
|
|
90
|
-
stderr: `${binary}: failed to capture process output`,
|
|
91
|
-
exitCode: 1,
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
stdoutStream.setEncoding("utf8");
|
|
96
|
-
stderrStream.setEncoding("utf8");
|
|
97
|
-
stdoutStream.on("data", (chunk) => {
|
|
98
|
-
stdout += chunk;
|
|
99
|
-
});
|
|
100
|
-
stderrStream.on("data", (chunk) => {
|
|
101
|
-
stderr += chunk;
|
|
102
|
-
});
|
|
103
|
-
childProcess.on("error", (error) => {
|
|
104
|
-
const errorCode = error instanceof Error && "code" in error
|
|
105
|
-
? String(error.code ?? "")
|
|
106
|
-
: "";
|
|
107
|
-
resolve({
|
|
108
|
-
stdout: "",
|
|
109
|
-
stderr: `${binary}: ${error.message}`,
|
|
110
|
-
exitCode: errorCode === "ENOENT" ? 127 : 1,
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
childProcess.on("close", (code) => {
|
|
114
|
-
resolve({
|
|
115
|
-
stdout,
|
|
116
|
-
stderr,
|
|
117
|
-
exitCode: code ?? 1,
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
34
|
function levenshtein(a, b) {
|
|
123
35
|
const dp = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
|
|
124
36
|
for (let i = 0; i <= a.length; i += 1) {
|
|
@@ -153,15 +65,6 @@ export function resolveReadablePath(vfs, cwd, inputPath) {
|
|
|
153
65
|
}
|
|
154
66
|
return exactPath;
|
|
155
67
|
}
|
|
156
|
-
export function joinListWithType(cwd, items, statAt) {
|
|
157
|
-
return items
|
|
158
|
-
.map((name) => {
|
|
159
|
-
const childPath = resolvePath(cwd, name);
|
|
160
|
-
const stats = statAt(childPath);
|
|
161
|
-
return stats.type === "directory" ? `${name}/` : name;
|
|
162
|
-
})
|
|
163
|
-
.join(" ");
|
|
164
|
-
}
|
|
165
68
|
export function getPackageManager(shell) {
|
|
166
69
|
return shell.packageManager;
|
|
167
70
|
}
|
package/dist/commands/perl.js
CHANGED
|
@@ -25,7 +25,7 @@ export const perlCommand = {
|
|
|
25
25
|
for (let li = 0; li < lines.length; li++) {
|
|
26
26
|
let line = lines[li];
|
|
27
27
|
// $_ = line, $. = line number
|
|
28
|
-
|
|
28
|
+
const processed = code
|
|
29
29
|
.replace(/\$_/g, JSON.stringify(line))
|
|
30
30
|
.replace(/\$\./g, String(li + 1));
|
|
31
31
|
// s/pat/rep/[g] substitution on $_
|
package/dist/commands/python.js
CHANGED
|
@@ -694,8 +694,11 @@ class Interpreter {
|
|
|
694
694
|
return left.repeat(right);
|
|
695
695
|
if (Array.isArray(left) && typeof right === "number") {
|
|
696
696
|
const arr = [];
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
const n = right | 0;
|
|
698
|
+
for (let i = 0; i < n; i++) {
|
|
699
|
+
for (let j = 0; j < left.length; j++)
|
|
700
|
+
arr.push(left[j]);
|
|
701
|
+
}
|
|
699
702
|
return arr;
|
|
700
703
|
}
|
|
701
704
|
return left * right;
|
|
@@ -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/runtime.js
CHANGED
|
@@ -4,6 +4,16 @@ 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}`;
|
|
@@ -43,7 +53,13 @@ function resolveVfsBinary(name, env, shell, authUser) {
|
|
|
43
53
|
return null;
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
|
-
const
|
|
56
|
+
const rawPath = env.vars.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
57
|
+
// Cache split PATH on the env object to avoid re-splitting on every binary lookup
|
|
58
|
+
if (!env._pathDirs || env._pathRaw !== rawPath) {
|
|
59
|
+
env._pathRaw = rawPath;
|
|
60
|
+
env._pathDirs = rawPath.split(":");
|
|
61
|
+
}
|
|
62
|
+
const pathDirs = env._pathDirs;
|
|
47
63
|
for (const dir of pathDirs) {
|
|
48
64
|
if ((dir === "/sbin" || dir === "/usr/sbin") && authUser !== "root")
|
|
49
65
|
continue;
|
|
@@ -63,6 +79,35 @@ function resolveVfsBinary(name, env, shell, authUser) {
|
|
|
63
79
|
return null;
|
|
64
80
|
}
|
|
65
81
|
const MAX_CALL_DEPTH = 8;
|
|
82
|
+
/** Run a VFS stub file as a command, handling `exec builtin <name>` and `sh -c` stubs. */
|
|
83
|
+
async function runVfsStub(vfsBinary, cmdName, args, rawInput, authUser, hostname, mode, cwd, shell, env, stdin) {
|
|
84
|
+
const stubContent = shell.vfs.readFile(vfsBinary);
|
|
85
|
+
const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
|
|
86
|
+
if (builtinMatch) {
|
|
87
|
+
const builtinMod = resolveModule(builtinMatch[1]);
|
|
88
|
+
if (builtinMod) {
|
|
89
|
+
return builtinMod.run({
|
|
90
|
+
authUser, hostname,
|
|
91
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
92
|
+
rawInput, mode, args, stdin, cwd, shell, env,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Guard: missing builtin — stop here to avoid sh -c infinite loop
|
|
96
|
+
return { stderr: `${cmdName}: exec builtin '${builtinMatch[1]}' not found`, exitCode: 127 };
|
|
97
|
+
}
|
|
98
|
+
const shMod = resolveModule("sh");
|
|
99
|
+
if (shMod) {
|
|
100
|
+
return shMod.run({
|
|
101
|
+
authUser, hostname,
|
|
102
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
103
|
+
rawInput: `sh -c ${JSON.stringify(stubContent)}`,
|
|
104
|
+
mode,
|
|
105
|
+
args: ["-c", stubContent, "--", ...args],
|
|
106
|
+
stdin, cwd, shell, env,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return { stderr: `${cmdName}: command not found`, exitCode: 127 };
|
|
110
|
+
}
|
|
66
111
|
let _callDepth = 0;
|
|
67
112
|
export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
|
|
68
113
|
// Anti-loop guard: track call depth via env to avoid infinite recursion
|
|
@@ -81,7 +126,7 @@ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd
|
|
|
81
126
|
}
|
|
82
127
|
}
|
|
83
128
|
async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
|
|
84
|
-
const assignRe =
|
|
129
|
+
const assignRe = ASSIGN_RE;
|
|
85
130
|
const invocation = [name, ...args];
|
|
86
131
|
let assignCount = 0;
|
|
87
132
|
while (assignCount < invocation.length && assignRe.test(invocation[assignCount])) {
|
|
@@ -119,42 +164,7 @@ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd,
|
|
|
119
164
|
if (!mod) {
|
|
120
165
|
const vfsBinary = resolveVfsBinary(name, env, shell, authUser);
|
|
121
166
|
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
|
-
}
|
|
167
|
+
return runVfsStub(vfsBinary, name, args, [name, ...args].join(" "), authUser, hostname, mode, cwd, shell, env, stdin);
|
|
158
168
|
}
|
|
159
169
|
return { stderr: `${name}: command not found`, exitCode: 127 };
|
|
160
170
|
}
|
|
@@ -220,18 +230,13 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
|
|
|
220
230
|
? trimmed.replace(rawFirstWord, aliasVal)
|
|
221
231
|
: trimmed;
|
|
222
232
|
// 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(";");
|
|
233
|
+
const isShScript = RE_FOR.test(aliasExpanded) ||
|
|
234
|
+
RE_WHILE.test(aliasExpanded) ||
|
|
235
|
+
RE_IF.test(aliasExpanded) ||
|
|
236
|
+
RE_FUNC_BRACE.test(aliasExpanded) ||
|
|
237
|
+
RE_FUNC_KW.test(aliasExpanded) ||
|
|
238
|
+
RE_ARITH.test(aliasExpanded);
|
|
239
|
+
const hasOperators = RE_PIPE.test(aliasExpanded) || RE_OPERATORS.test(aliasExpanded);
|
|
235
240
|
if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
|
|
236
241
|
// sh-syntax: route through sh interpreter to handle for/while/functions
|
|
237
242
|
if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
|
|
@@ -267,52 +272,25 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
|
|
|
267
272
|
const parts = tokenizeCommand(expanded.trim());
|
|
268
273
|
if (parts.length === 0)
|
|
269
274
|
return { exitCode: 0 };
|
|
270
|
-
const assignRe =
|
|
275
|
+
const assignRe = ASSIGN_RE;
|
|
271
276
|
if (assignRe.test(parts[0])) {
|
|
272
277
|
return runCommandDirect(parts[0], parts.slice(1), authUser, hostname, mode, cwd, shell, stdin, shellEnv);
|
|
273
278
|
}
|
|
274
279
|
const commandName = parts[0]?.toLowerCase() ?? "";
|
|
275
280
|
// Apply brace expansion to each arg token
|
|
276
|
-
const
|
|
281
|
+
const rawArgs = parts.slice(1);
|
|
282
|
+
const args = [];
|
|
283
|
+
for (const token of rawArgs) {
|
|
284
|
+
for (const brace of expandBraces(token)) {
|
|
285
|
+
for (const glob of expandGlob(brace, cwd, shell.vfs))
|
|
286
|
+
args.push(glob);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
277
289
|
const mod = resolveModule(commandName);
|
|
278
290
|
if (!mod) {
|
|
279
291
|
const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
|
|
280
292
|
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
|
-
}
|
|
293
|
+
return runVfsStub(vfsBinary, commandName, args, expanded, authUser, hostname, mode, cwd, shell, shellEnv, stdin);
|
|
316
294
|
}
|
|
317
295
|
return { stderr: `${commandName}: command not found`, exitCode: 127 };
|
|
318
296
|
}
|
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);
|
|
@@ -1281,7 +1281,7 @@ Installed-Size: 6800
|
|
|
1281
1281
|
Maintainer: Fortune Package Team <dpkg@fortune.local>
|
|
1282
1282
|
Architecture: amd64
|
|
1283
1283
|
Version: 1.22.6nyx1
|
|
1284
|
-
Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.
|
|
1284
|
+
Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.7)
|
|
1285
1285
|
Description: Fortune package management system
|
|
1286
1286
|
This package provides the low-level infrastructure for handling the
|
|
1287
1287
|
installation and removal of Fortune software packages.
|
|
@@ -1547,12 +1547,9 @@ export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime
|
|
|
1547
1547
|
const snapshot = getStaticRootfsSnapshot(hostname, props);
|
|
1548
1548
|
const hasRestoredData = vfs.getMode() === "fs" && vfs.exists("/home");
|
|
1549
1549
|
if (hasRestoredData) {
|
|
1550
|
-
// Snapshot was already restored — merge static rootfs without
|
|
1551
|
-
// clobbering user files and directories.
|
|
1552
1550
|
vfs.mergeRootTree(decodeVfs(snapshot));
|
|
1553
1551
|
}
|
|
1554
1552
|
else {
|
|
1555
|
-
// Fresh start — replace the empty tree with the full static rootfs.
|
|
1556
1553
|
vfs.importRootTree(decodeVfs(snapshot));
|
|
1557
1554
|
}
|
|
1558
1555
|
bootstrapRoot(vfs);
|
package/dist/modules/neofetch.js
CHANGED
|
@@ -208,10 +208,10 @@ function resolveDefaults(info) {
|
|
|
208
208
|
os: info.osName ?? `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
|
|
209
209
|
arch: os.arch(),
|
|
210
210
|
},
|
|
211
|
-
resolution: info.resolution ?? "n/a (ssh)",
|
|
211
|
+
resolution: info.resolution ?? shellProps?.resolution ?? "n/a (ssh)",
|
|
212
212
|
terminal: info.terminal ?? "unknown",
|
|
213
213
|
cpu: info.cpu ?? resolveCpuLabel(),
|
|
214
|
-
gpu: info.gpu ?? "n/a",
|
|
214
|
+
gpu: info.gpu ?? shellProps?.gpu ?? "n/a",
|
|
215
215
|
memoryUsedMiB: info.memoryUsedMiB ?? toMiB(usedMem),
|
|
216
216
|
memoryTotalMiB: info.memoryTotalMiB ?? toMiB(totalMem),
|
|
217
217
|
};
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -94,6 +94,10 @@ export interface ShellEnv {
|
|
|
94
94
|
vars: Record<string, string>;
|
|
95
95
|
/** Exit status of the last executed command. */
|
|
96
96
|
lastExitCode: number;
|
|
97
|
+
/** @internal Cached split of vars.PATH — invalidated when _pathRaw !== vars.PATH. */
|
|
98
|
+
_pathRaw?: string;
|
|
99
|
+
/** @internal Pre-split PATH directories for resolveVfsBinary hot-path. */
|
|
100
|
+
_pathDirs?: string[];
|
|
97
101
|
}
|
|
98
102
|
/** Runtime context object passed to each command module. */
|
|
99
103
|
export interface CommandContext {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Returns true if `name` appears in `argv`. */
|
|
2
|
+
export declare function getFlag(argv: string[], name: string): boolean;
|
|
3
|
+
/** Returns the string value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
4
|
+
export declare function getOptionString(argv: string[], name: string, fallback: string): string;
|
|
5
|
+
/** Returns the integer value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
6
|
+
export declare function getOptionInt(argv: string[], name: string, fallback: number): number;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Returns true if `name` appears in `argv`. */
|
|
2
|
+
export function getFlag(argv, name) {
|
|
3
|
+
return argv.includes(name);
|
|
4
|
+
}
|
|
5
|
+
/** Returns the string value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
6
|
+
export function getOptionString(argv, name, fallback) {
|
|
7
|
+
const prefix = `${name}=`;
|
|
8
|
+
for (let i = 0; i < argv.length; i++) {
|
|
9
|
+
const a = argv[i];
|
|
10
|
+
if (a.startsWith(prefix))
|
|
11
|
+
return a.slice(prefix.length);
|
|
12
|
+
if (a === name) {
|
|
13
|
+
const next = argv[i + 1];
|
|
14
|
+
return (next && !next.startsWith("--")) ? next : fallback;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
/** Returns the integer value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
20
|
+
export function getOptionInt(argv, name, fallback) {
|
|
21
|
+
const prefix = `${name}=`;
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const a = argv[i];
|
|
24
|
+
if (a.startsWith(prefix))
|
|
25
|
+
return parseInt(a.slice(prefix.length), 10);
|
|
26
|
+
if (a === name) {
|
|
27
|
+
const next = argv[i + 1];
|
|
28
|
+
return next ? parseInt(next, 10) : fallback;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
package/dist/utils/expand.d.ts
CHANGED
|
@@ -61,10 +61,13 @@ export declare function expandAsync(input: string, env: Record<string, string>,
|
|
|
61
61
|
* Supports * (any chars in segment) and ** (any path).
|
|
62
62
|
* Returns the original pattern if no matches found (bash behavior).
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
type GlobVfs = {
|
|
65
65
|
list: (p: string) => string[];
|
|
66
66
|
exists: (p: string) => boolean;
|
|
67
67
|
stat: (p: string) => {
|
|
68
68
|
type: string;
|
|
69
69
|
};
|
|
70
|
-
|
|
70
|
+
statType?: (p: string) => string | null;
|
|
71
|
+
};
|
|
72
|
+
export declare function expandGlob(pattern: string, cwd: string, vfs: GlobVfs): string[];
|
|
73
|
+
export {};
|