typescript-virtual-container 1.5.5 → 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 +117 -35
- 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/awk.d.ts +6 -11
- package/dist/commands/awk.js +462 -109
- package/dist/commands/bzip2.d.ts +11 -0
- package/dist/commands/bzip2.js +91 -0
- package/dist/commands/exit.js +1 -1
- package/dist/commands/find.d.ts +2 -2
- package/dist/commands/find.js +209 -37
- package/dist/commands/helpers.d.ts +0 -20
- package/dist/commands/helpers.js +0 -97
- package/dist/commands/lsof.d.ts +6 -0
- package/dist/commands/lsof.js +30 -0
- package/dist/commands/perl.d.ts +6 -0
- package/dist/commands/perl.js +76 -0
- package/dist/commands/python.js +5 -2
- package/dist/commands/registry.js +19 -1
- package/dist/commands/runtime.js +65 -87
- package/dist/commands/sed.d.ts +2 -2
- package/dist/commands/sed.js +216 -34
- package/dist/commands/sh.js +42 -0
- package/dist/commands/strace.d.ts +6 -0
- package/dist/commands/strace.js +26 -0
- package/dist/commands/tar.d.ts +2 -1
- package/dist/commands/tar.js +138 -52
- package/dist/commands/test.js +2 -2
- package/dist/commands/zip.d.ts +11 -0
- package/dist/commands/zip.js +232 -0
- 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 +112 -45
- 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 +9 -7
- package/dist/self-standalone.d.ts +0 -1
- package/dist/self-standalone.js +0 -444
- package/dist/standalone-wo-sftp.d.ts +0 -1
- package/dist/standalone-wo-sftp.js +0 -30
- package/dist/standalone.d.ts +0 -1
- package/dist/standalone.js +0 -61
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { gunzipSync, gzipSync } from "fflate";
|
|
2
|
+
import { resolvePath } from "./helpers";
|
|
3
|
+
// BZ2 magic bytes: "BZh" — we store gzip data after this marker so our tools
|
|
4
|
+
// can round-trip. Real bzip2 files won't be decompressable this way, but files
|
|
5
|
+
// created and extracted within this VM will work correctly.
|
|
6
|
+
const BZ2_MAGIC = Buffer.from("BZhVFS\0");
|
|
7
|
+
function vfsBzip2(data) {
|
|
8
|
+
const gz = Buffer.from(gzipSync(data));
|
|
9
|
+
return Buffer.concat([BZ2_MAGIC, gz]);
|
|
10
|
+
}
|
|
11
|
+
function vfsBunzip2(data) {
|
|
12
|
+
if (!data.subarray(0, BZ2_MAGIC.length).equals(BZ2_MAGIC))
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
return Buffer.from(gunzipSync(data.subarray(BZ2_MAGIC.length)));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Compress files using bzip2 (VFS-internal gzip with BZ2 marker — round-trips within VM).
|
|
23
|
+
* @category archive
|
|
24
|
+
*/
|
|
25
|
+
export const bzip2Command = {
|
|
26
|
+
name: "bzip2",
|
|
27
|
+
description: "Compress files using Burrows-Wheeler algorithm",
|
|
28
|
+
category: "archive",
|
|
29
|
+
params: ["[-k] [-d] <file>"],
|
|
30
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
31
|
+
const keepOrig = args.includes("-k") || args.includes("--keep");
|
|
32
|
+
const decompress = args.includes("-d") || args.includes("--decompress");
|
|
33
|
+
const file = args.find((a) => !a.startsWith("-"));
|
|
34
|
+
if (!file)
|
|
35
|
+
return { stderr: "bzip2: no file specified", exitCode: 1 };
|
|
36
|
+
const p = resolvePath(cwd, file);
|
|
37
|
+
if (!shell.vfs.exists(p))
|
|
38
|
+
return { stderr: `bzip2: ${file}: No such file or directory`, exitCode: 1 };
|
|
39
|
+
if (decompress) {
|
|
40
|
+
if (!file.endsWith(".bz2"))
|
|
41
|
+
return { stderr: `bzip2: ${file}: unknown suffix -- ignored`, exitCode: 1 };
|
|
42
|
+
const raw = shell.vfs.readFileRaw(p);
|
|
43
|
+
const result = vfsBunzip2(raw);
|
|
44
|
+
if (!result)
|
|
45
|
+
return { stderr: `bzip2: ${file}: data integrity error`, exitCode: 2 };
|
|
46
|
+
const dest = p.slice(0, -4);
|
|
47
|
+
shell.writeFileAsUser(authUser, dest, result);
|
|
48
|
+
if (!keepOrig)
|
|
49
|
+
shell.vfs.remove(p);
|
|
50
|
+
return { exitCode: 0 };
|
|
51
|
+
}
|
|
52
|
+
if (file.endsWith(".bz2"))
|
|
53
|
+
return { stderr: `bzip2: ${file}: already has .bz2 suffix -- unchanged`, exitCode: 1 };
|
|
54
|
+
const raw = shell.vfs.readFileRaw(p);
|
|
55
|
+
shell.vfs.writeFile(`${p}.bz2`, vfsBzip2(raw));
|
|
56
|
+
if (!keepOrig)
|
|
57
|
+
shell.vfs.remove(p);
|
|
58
|
+
return { exitCode: 0 };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Decompress bzip2 files (VFS-internal format).
|
|
63
|
+
* @category archive
|
|
64
|
+
*/
|
|
65
|
+
export const bunzip2Command = {
|
|
66
|
+
name: "bunzip2",
|
|
67
|
+
description: "Decompress bzip2 files",
|
|
68
|
+
category: "archive",
|
|
69
|
+
aliases: ["bzcat"],
|
|
70
|
+
params: ["[-k] <file>"],
|
|
71
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
72
|
+
const keepOrig = args.includes("-k") || args.includes("--keep");
|
|
73
|
+
const file = args.find((a) => !a.startsWith("-"));
|
|
74
|
+
if (!file)
|
|
75
|
+
return { stderr: "bunzip2: no file specified", exitCode: 1 };
|
|
76
|
+
const p = resolvePath(cwd, file);
|
|
77
|
+
if (!shell.vfs.exists(p))
|
|
78
|
+
return { stderr: `bunzip2: ${file}: No such file or directory`, exitCode: 1 };
|
|
79
|
+
if (!file.endsWith(".bz2"))
|
|
80
|
+
return { stderr: `bunzip2: ${file}: unknown suffix -- ignored`, exitCode: 1 };
|
|
81
|
+
const raw = shell.vfs.readFileRaw(p);
|
|
82
|
+
const result = vfsBunzip2(raw);
|
|
83
|
+
if (!result)
|
|
84
|
+
return { stderr: `bunzip2: ${file}: data integrity error`, exitCode: 2 };
|
|
85
|
+
const dest = p.slice(0, -4);
|
|
86
|
+
shell.writeFileAsUser(authUser, dest, result);
|
|
87
|
+
if (!keepOrig)
|
|
88
|
+
shell.vfs.remove(p);
|
|
89
|
+
return { exitCode: 0 };
|
|
90
|
+
},
|
|
91
|
+
};
|
package/dist/commands/exit.js
CHANGED
package/dist/commands/find.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
/**
|
|
3
|
-
* Find files and directories
|
|
3
|
+
* Find files and directories with filtering, -exec, -maxdepth, -not, -o, -a.
|
|
4
4
|
* @category files
|
|
5
|
-
* @params ["[path] [
|
|
5
|
+
* @params ["[path] [expression...]"]
|
|
6
6
|
*/
|
|
7
7
|
export declare const findCommand: ShellModule;
|
package/dist/commands/find.js
CHANGED
|
@@ -1,55 +1,227 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { globToRegex } from "../utils/glob";
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
|
+
import { runCommand } from "./runtime";
|
|
3
4
|
/**
|
|
4
|
-
* Find files and directories
|
|
5
|
+
* Find files and directories with filtering, -exec, -maxdepth, -not, -o, -a.
|
|
5
6
|
* @category files
|
|
6
|
-
* @params ["[path] [
|
|
7
|
+
* @params ["[path] [expression...]"]
|
|
7
8
|
*/
|
|
8
9
|
export const findCommand = {
|
|
9
10
|
name: "find",
|
|
10
11
|
description: "Search for files",
|
|
11
12
|
category: "files",
|
|
12
|
-
params: ["[path] [
|
|
13
|
-
run: ({ authUser, shell, cwd, args }) => {
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
params: ["[path] [expression...]"],
|
|
14
|
+
run: async ({ authUser, shell, cwd, args, env, hostname, mode }) => {
|
|
15
|
+
// Collect root paths (positional args before first - option)
|
|
16
|
+
const roots = [];
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < args.length && !args[i].startsWith("-") && args[i] !== "!" && args[i] !== "(") {
|
|
19
|
+
roots.push(args[i]);
|
|
20
|
+
i++;
|
|
21
|
+
}
|
|
22
|
+
if (roots.length === 0)
|
|
23
|
+
roots.push(".");
|
|
24
|
+
const exprArgs = args.slice(i);
|
|
25
|
+
let maxDepth = Infinity;
|
|
26
|
+
let minDepth = 0;
|
|
27
|
+
const execCmds = [];
|
|
28
|
+
function parseExpr(tokens, pos) {
|
|
29
|
+
return parseOr(tokens, pos);
|
|
30
|
+
}
|
|
31
|
+
function parseOr(tokens, pos) {
|
|
32
|
+
let [left, p] = parseAnd(tokens, pos);
|
|
33
|
+
while (tokens[p] === "-o" || tokens[p] === "-or") {
|
|
34
|
+
p++;
|
|
35
|
+
const [right, np] = parseAnd(tokens, p);
|
|
36
|
+
left = { type: "or", left, right };
|
|
37
|
+
p = np;
|
|
38
|
+
}
|
|
39
|
+
return [left, p];
|
|
40
|
+
}
|
|
41
|
+
function parseAnd(tokens, pos) {
|
|
42
|
+
let [left, p] = parseNot(tokens, pos);
|
|
43
|
+
while (p < tokens.length && tokens[p] !== "-o" && tokens[p] !== "-or" && tokens[p] !== ")") {
|
|
44
|
+
if (tokens[p] === "-a" || tokens[p] === "-and")
|
|
45
|
+
p++;
|
|
46
|
+
if (p >= tokens.length || tokens[p] === "-o" || tokens[p] === ")")
|
|
47
|
+
break;
|
|
48
|
+
const [right, np] = parseNot(tokens, p);
|
|
49
|
+
left = { type: "and", left, right };
|
|
50
|
+
p = np;
|
|
51
|
+
}
|
|
52
|
+
return [left, p];
|
|
53
|
+
}
|
|
54
|
+
function parseNot(tokens, pos) {
|
|
55
|
+
if (tokens[pos] === "!" || tokens[pos] === "-not") {
|
|
56
|
+
const [pred, np] = parsePrimary(tokens, pos + 1);
|
|
57
|
+
return [{ type: "not", pred }, np];
|
|
58
|
+
}
|
|
59
|
+
return parsePrimary(tokens, pos);
|
|
60
|
+
}
|
|
61
|
+
function parsePrimary(tokens, pos) {
|
|
62
|
+
const tok = tokens[pos];
|
|
63
|
+
if (!tok)
|
|
64
|
+
return [{ type: "true" }, pos];
|
|
65
|
+
if (tok === "(") {
|
|
66
|
+
const [pred, np] = parseExpr(tokens, pos + 1);
|
|
67
|
+
const closePos = tokens[np] === ")" ? np + 1 : np;
|
|
68
|
+
return [pred, closePos];
|
|
69
|
+
}
|
|
70
|
+
if (tok === "-name")
|
|
71
|
+
return [{ type: "name", pat: tokens[pos + 1] ?? "*", ignoreCase: false }, pos + 2];
|
|
72
|
+
if (tok === "-iname")
|
|
73
|
+
return [{ type: "name", pat: tokens[pos + 1] ?? "*", ignoreCase: true }, pos + 2];
|
|
74
|
+
if (tok === "-type")
|
|
75
|
+
return [{ type: "type", t: tokens[pos + 1] ?? "f" }, pos + 2];
|
|
76
|
+
if (tok === "-maxdepth") {
|
|
77
|
+
maxDepth = parseInt(tokens[pos + 1] ?? "0", 10);
|
|
78
|
+
return [{ type: "true" }, pos + 2];
|
|
79
|
+
}
|
|
80
|
+
if (tok === "-mindepth") {
|
|
81
|
+
minDepth = parseInt(tokens[pos + 1] ?? "0", 10);
|
|
82
|
+
return [{ type: "true" }, pos + 2];
|
|
83
|
+
}
|
|
84
|
+
if (tok === "-empty")
|
|
85
|
+
return [{ type: "empty" }, pos + 1];
|
|
86
|
+
if (tok === "-print" || tok === "-print0")
|
|
87
|
+
return [{ type: "print" }, pos + 1];
|
|
88
|
+
if (tok === "-true")
|
|
89
|
+
return [{ type: "true" }, pos + 1];
|
|
90
|
+
if (tok === "-false")
|
|
91
|
+
return [{ type: "false" }, pos + 1];
|
|
92
|
+
if (tok === "-size") {
|
|
93
|
+
const raw = tokens[pos + 1] ?? "0";
|
|
94
|
+
const unit = raw.slice(-1);
|
|
95
|
+
const n = parseInt(raw, 10);
|
|
96
|
+
return [{ type: "size", n, unit }, pos + 2];
|
|
26
97
|
}
|
|
98
|
+
if (tok === "-exec" || tok === "-execdir") {
|
|
99
|
+
const useDir = tok === "-execdir";
|
|
100
|
+
const cmd = [];
|
|
101
|
+
let j = pos + 1;
|
|
102
|
+
while (j < tokens.length && tokens[j] !== ";") {
|
|
103
|
+
cmd.push(tokens[j]);
|
|
104
|
+
j++;
|
|
105
|
+
}
|
|
106
|
+
execCmds.push({ cmd, useDir });
|
|
107
|
+
return [{ type: "exec", cmd, useDir }, j + 1];
|
|
108
|
+
}
|
|
109
|
+
// Unknown predicate — skip
|
|
110
|
+
return [{ type: "true" }, pos + 1];
|
|
27
111
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
112
|
+
const pred = exprArgs.length > 0 ? parseExpr(exprArgs, 0)[0] : { type: "true" };
|
|
113
|
+
function matchPred(p, fullPath, depth) {
|
|
114
|
+
switch (p.type) {
|
|
115
|
+
case "true": return true;
|
|
116
|
+
case "false": return false;
|
|
117
|
+
case "not": return !matchPred(p.pred, fullPath, depth);
|
|
118
|
+
case "and": return matchPred(p.left, fullPath, depth) && matchPred(p.right, fullPath, depth);
|
|
119
|
+
case "or": return matchPred(p.left, fullPath, depth) || matchPred(p.right, fullPath, depth);
|
|
120
|
+
case "name": {
|
|
121
|
+
const base = fullPath.split("/").pop() ?? "";
|
|
122
|
+
return globToRegex(p.pat, p.ignoreCase ? "i" : "").test(base);
|
|
123
|
+
}
|
|
124
|
+
case "type": {
|
|
125
|
+
try {
|
|
126
|
+
const st = shell.vfs.stat(fullPath);
|
|
127
|
+
if (p.t === "f")
|
|
128
|
+
return st.type === "file";
|
|
129
|
+
if (p.t === "d")
|
|
130
|
+
return st.type === "directory";
|
|
131
|
+
if (p.t === "l")
|
|
132
|
+
return false; // VFS has no symlink type
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
case "empty": {
|
|
140
|
+
try {
|
|
141
|
+
const st = shell.vfs.stat(fullPath);
|
|
142
|
+
if (st.type === "directory")
|
|
143
|
+
return shell.vfs.list(fullPath).length === 0;
|
|
144
|
+
return shell.vfs.readFile(fullPath).length === 0;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
case "size": {
|
|
151
|
+
try {
|
|
152
|
+
const content = shell.vfs.readFile(fullPath);
|
|
153
|
+
const bytes = content.length;
|
|
154
|
+
const unit = p.unit;
|
|
155
|
+
let fileSize = bytes;
|
|
156
|
+
if (unit === "k" || unit === "K")
|
|
157
|
+
fileSize = Math.ceil(bytes / 1024);
|
|
158
|
+
else if (unit === "M")
|
|
159
|
+
fileSize = Math.ceil(bytes / (1024 * 1024));
|
|
160
|
+
else if (unit === "c")
|
|
161
|
+
fileSize = bytes;
|
|
162
|
+
return fileSize === p.n;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
case "print": return true;
|
|
169
|
+
case "exec": return true; // handled separately
|
|
170
|
+
default: return true;
|
|
171
|
+
}
|
|
31
172
|
}
|
|
32
|
-
const nameRegex = namePattern
|
|
33
|
-
? new RegExp(`^${namePattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`)
|
|
34
|
-
: null;
|
|
35
173
|
const results = [];
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
(
|
|
41
|
-
|
|
42
|
-
|
|
174
|
+
function walk(currentPath, display, depth) {
|
|
175
|
+
if (depth > maxDepth)
|
|
176
|
+
return;
|
|
177
|
+
try {
|
|
178
|
+
assertPathAccess(authUser, currentPath, "find");
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (depth >= minDepth && matchPred(pred, currentPath, depth)) {
|
|
43
184
|
results.push(display);
|
|
44
|
-
|
|
185
|
+
}
|
|
186
|
+
let stat;
|
|
187
|
+
try {
|
|
188
|
+
stat = shell.vfs.stat(currentPath);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (stat.type === "directory" && depth < maxDepth) {
|
|
45
194
|
for (const entry of shell.vfs.list(currentPath)) {
|
|
46
|
-
|
|
47
|
-
const disp = `${display}/${entry}`;
|
|
48
|
-
walk(full, disp);
|
|
195
|
+
walk(`${currentPath}/${entry}`, `${display}/${entry}`, depth + 1);
|
|
49
196
|
}
|
|
50
197
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
198
|
+
}
|
|
199
|
+
for (const root of roots) {
|
|
200
|
+
const rootPath = resolvePath(cwd, root);
|
|
201
|
+
if (!shell.vfs.exists(rootPath)) {
|
|
202
|
+
return { stderr: `find: '${root}': No such file or directory`, exitCode: 1 };
|
|
203
|
+
}
|
|
204
|
+
walk(rootPath, root === "." ? "." : root, 0);
|
|
205
|
+
}
|
|
206
|
+
// Execute -exec commands
|
|
207
|
+
if (execCmds.length > 0 && results.length > 0) {
|
|
208
|
+
const execOutputs = [];
|
|
209
|
+
for (const { cmd } of execCmds) {
|
|
210
|
+
for (const filePath of results) {
|
|
211
|
+
const expanded = cmd.map((t) => t === "{}" ? filePath : t);
|
|
212
|
+
const cmdStr = expanded.map((t) => (t.includes(" ") ? `"${t}"` : t)).join(" ");
|
|
213
|
+
const r = await runCommand(cmdStr, authUser, hostname, mode, cwd, shell, undefined, env);
|
|
214
|
+
if (r.stdout)
|
|
215
|
+
execOutputs.push(r.stdout.replace(/\n$/, ""));
|
|
216
|
+
if (r.stderr)
|
|
217
|
+
execOutputs.push(r.stderr.replace(/\n$/, ""));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (execOutputs.length > 0) {
|
|
221
|
+
return { stdout: `${execOutputs.join("\n")}\n`, exitCode: 0 };
|
|
222
|
+
}
|
|
223
|
+
return { exitCode: 0 };
|
|
224
|
+
}
|
|
225
|
+
return { stdout: results.join("\n") + (results.length > 0 ? "\n" : ""), exitCode: 0 };
|
|
54
226
|
},
|
|
55
227
|
};
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List open files (stub — returns simulated process file descriptors).
|
|
3
|
+
* @category system
|
|
4
|
+
*/
|
|
5
|
+
export const lsofCommand = {
|
|
6
|
+
name: "lsof",
|
|
7
|
+
description: "List open files",
|
|
8
|
+
category: "system",
|
|
9
|
+
params: ["[-p <pid>] [-u <user>] [-i [addr]]"],
|
|
10
|
+
run: ({ authUser, args }) => {
|
|
11
|
+
const iNet = args.includes("-i");
|
|
12
|
+
if (iNet) {
|
|
13
|
+
const header = "COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME";
|
|
14
|
+
const rows = [
|
|
15
|
+
`sshd 1234 root 3u IPv4 12345 0t0 TCP *:22 (LISTEN)`,
|
|
16
|
+
`nginx 567 root 6u IPv4 23456 0t0 TCP *:80 (LISTEN)`,
|
|
17
|
+
];
|
|
18
|
+
return { stdout: `${header}\n${rows.join("\n")}`, exitCode: 0 };
|
|
19
|
+
}
|
|
20
|
+
const header = "COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME";
|
|
21
|
+
const rows = [
|
|
22
|
+
`bash 1001 ${authUser} cwd DIR 8,1 4096 2 /home/${authUser}`,
|
|
23
|
+
`bash 1001 ${authUser} txt REG 8,1 1183448 23 /bin/bash`,
|
|
24
|
+
`bash 1001 ${authUser} 0u CHR 136,0 0t0 3 /dev/pts/0`,
|
|
25
|
+
`bash 1001 ${authUser} 1u CHR 136,0 0t0 3 /dev/pts/0`,
|
|
26
|
+
`bash 1001 ${authUser} 2u CHR 136,0 0t0 3 /dev/pts/0`,
|
|
27
|
+
];
|
|
28
|
+
return { stdout: `${header}\n${rows.join("\n")}`, exitCode: 0 };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perl interpreter stub — supports -e one-liners with print, say, basic regex, and -p/-n/-i.
|
|
3
|
+
* @category scripting
|
|
4
|
+
*/
|
|
5
|
+
export const perlCommand = {
|
|
6
|
+
name: "perl",
|
|
7
|
+
description: "Practical Extraction and Report Language",
|
|
8
|
+
category: "scripting",
|
|
9
|
+
params: ["[-e <expr>] [-p] [-n] [-i] [file]"],
|
|
10
|
+
run: ({ args, stdin }) => {
|
|
11
|
+
const eIdx = args.indexOf("-e");
|
|
12
|
+
const code = eIdx !== -1 ? args[eIdx + 1] : undefined;
|
|
13
|
+
const printLoop = args.includes("-p");
|
|
14
|
+
const noAutoprint = args.includes("-n");
|
|
15
|
+
const hasLoop = printLoop || noAutoprint;
|
|
16
|
+
if (!code) {
|
|
17
|
+
return { stderr: "perl: no code specified (only -e one-liners supported)", exitCode: 1 };
|
|
18
|
+
}
|
|
19
|
+
const input = stdin ?? "";
|
|
20
|
+
const lines = input.split("\n");
|
|
21
|
+
if (lines[lines.length - 1] === "")
|
|
22
|
+
lines.pop();
|
|
23
|
+
const out = [];
|
|
24
|
+
if (hasLoop) {
|
|
25
|
+
for (let li = 0; li < lines.length; li++) {
|
|
26
|
+
let line = lines[li];
|
|
27
|
+
// $_ = line, $. = line number
|
|
28
|
+
const processed = code
|
|
29
|
+
.replace(/\$_/g, JSON.stringify(line))
|
|
30
|
+
.replace(/\$\./g, String(li + 1));
|
|
31
|
+
// s/pat/rep/[g] substitution on $_
|
|
32
|
+
const sMatch = processed.match(/^s([^a-zA-Z0-9])(.*?)\1(.*?)\1([gi]*)$/);
|
|
33
|
+
if (sMatch) {
|
|
34
|
+
const flags = sMatch[4] ?? "";
|
|
35
|
+
try {
|
|
36
|
+
const re = new RegExp(sMatch[2], flags.includes("i") ? (flags.includes("g") ? "gi" : "i") : flags.includes("g") ? "g" : "");
|
|
37
|
+
line = line.replace(re, sMatch[3]);
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore */ }
|
|
40
|
+
if (printLoop)
|
|
41
|
+
out.push(line);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// print / say
|
|
45
|
+
const printM = processed.match(/^(?:print|say)\s+(?:STDOUT\s+)?(?:"([^"]*)"|'([^']*)'|(.+))(?:\s*;)?$/);
|
|
46
|
+
if (printM) {
|
|
47
|
+
const val = (printM[1] ?? printM[2] ?? printM[3] ?? "")
|
|
48
|
+
.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
49
|
+
out.push(code.startsWith("say") ? val : val.replace(/\n$/, ""));
|
|
50
|
+
if (printLoop)
|
|
51
|
+
out.push(line);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (printLoop)
|
|
55
|
+
out.push(line);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Single expression
|
|
60
|
+
const printM = code.match(/^(?:print|say)\s+(?:STDOUT\s+)?(?:"([^"]*)"|'([^']*)'|(.*))(?:\s*;)?$/);
|
|
61
|
+
if (printM) {
|
|
62
|
+
const val = (printM[1] ?? printM[2] ?? printM[3] ?? "")
|
|
63
|
+
.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
64
|
+
out.push(val);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Version string
|
|
68
|
+
if (code.trim() === 'print $]' || code.includes("version")) {
|
|
69
|
+
out.push("5.036001");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const result = out.join("\n");
|
|
74
|
+
return { stdout: result + (result && !result.endsWith("\n") ? "\n" : ""), exitCode: 0 };
|
|
75
|
+
},
|
|
76
|
+
};
|
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;
|