typescript-virtual-container 1.2.9 → 1.3.0
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/.vscode/settings.json +0 -1
- package/README.md +141 -50
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +32 -16
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +192 -43
- package/dist/VirtualShell/index.d.ts +10 -4
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +18 -7
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +32 -9
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +10 -2
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +5 -0
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +34 -6
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +5 -0
- package/dist/commands/dpkg.d.ts.map +1 -1
- package/dist/commands/dpkg.js +24 -7
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +13 -4
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +5 -0
- package/dist/commands/free.d.ts.map +1 -1
- package/dist/commands/free.js +5 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +5 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -17
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -340
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +3 -1
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +8 -2
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.js +1 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +3 -1
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +4 -1
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +57 -36
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -2
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +46 -24
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +1 -1
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +4 -1
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +32 -7
- package/dist/commands/which.d.ts.map +1 -1
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -9
- package/dist/modules/linuxRootfs.d.ts +18 -1
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +160 -17
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +2 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +104 -18
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +208 -49
- package/src/VirtualShell/index.ts +35 -7
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +7 -2
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +5 -1
- package/src/commands/apt.ts +47 -17
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +13 -5
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +5 -0
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +56 -12
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +33 -11
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +22 -9
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +9 -2
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/history.ts +7 -2
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -360
- package/src/commands/ls.ts +5 -3
- package/src/commands/lsb-release.ts +8 -2
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +1 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +6 -2
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +21 -9
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +159 -55
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +15 -3
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +61 -26
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +6 -3
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +4 -1
- package/src/commands/wget.ts +51 -12
- package/src/commands/which.ts +5 -2
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +23 -24
- package/src/modules/linuxRootfs.ts +233 -30
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +253 -191
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +415 -5
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Pipeline,
|
|
3
|
+
PipelineCommand,
|
|
4
|
+
Script,
|
|
5
|
+
Statement,
|
|
6
|
+
LogicalOp,
|
|
7
|
+
} from "../types/pipeline";
|
|
2
8
|
|
|
3
9
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
4
10
|
|
|
@@ -56,8 +62,9 @@ export function expandToken(
|
|
|
56
62
|
token = token.replace(/\$#/g, "0");
|
|
57
63
|
|
|
58
64
|
// ${VAR:-default} and ${VAR:+value}
|
|
59
|
-
token = token.replace(
|
|
60
|
-
|
|
65
|
+
token = token.replace(
|
|
66
|
+
/\$\{([^}:]+):-([^}]*)\}/g,
|
|
67
|
+
(_, name, def) => env[name] ?? def,
|
|
61
68
|
);
|
|
62
69
|
token = token.replace(/\$\{([^}:]+):\+([^}]*)\}/g, (_, name, val) =>
|
|
63
70
|
env[name] ? val : "",
|
|
@@ -67,8 +74,9 @@ export function expandToken(
|
|
|
67
74
|
token = token.replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? "");
|
|
68
75
|
|
|
69
76
|
// $VAR (greedy: match longest valid identifier)
|
|
70
|
-
token = token.replace(
|
|
71
|
-
|
|
77
|
+
token = token.replace(
|
|
78
|
+
/\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
79
|
+
(_, name) => env[name] ?? "",
|
|
72
80
|
);
|
|
73
81
|
|
|
74
82
|
return token;
|
|
@@ -120,7 +128,10 @@ function parseStatements(input: string): Statement[] {
|
|
|
120
128
|
return statements;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
interface Segment {
|
|
131
|
+
interface Segment {
|
|
132
|
+
text: string;
|
|
133
|
+
op?: LogicalOp;
|
|
134
|
+
}
|
|
124
135
|
|
|
125
136
|
function splitByLogicalOps(input: string): Segment[] {
|
|
126
137
|
const segments: Segment[] = [];
|
|
@@ -139,19 +150,61 @@ function splitByLogicalOps(input: string): Segment[] {
|
|
|
139
150
|
const ch = input[i]!;
|
|
140
151
|
const ch2 = input.slice(i, i + 2);
|
|
141
152
|
|
|
142
|
-
if ((ch === '"' || ch === "'") && !inQ) {
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
if ((ch === '"' || ch === "'") && !inQ) {
|
|
154
|
+
inQ = true;
|
|
155
|
+
qChar = ch;
|
|
156
|
+
current += ch;
|
|
157
|
+
i++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (inQ && ch === qChar) {
|
|
161
|
+
inQ = false;
|
|
162
|
+
current += ch;
|
|
163
|
+
i++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (inQ) {
|
|
167
|
+
current += ch;
|
|
168
|
+
i++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
145
171
|
|
|
146
|
-
if (ch === "(") {
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
if (ch === "(") {
|
|
173
|
+
depth++;
|
|
174
|
+
current += ch;
|
|
175
|
+
i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ch === ")") {
|
|
179
|
+
depth--;
|
|
180
|
+
current += ch;
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (depth > 0) {
|
|
185
|
+
current += ch;
|
|
186
|
+
i++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
149
189
|
|
|
150
|
-
if (ch2 === "&&") {
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
if (ch2 === "&&") {
|
|
191
|
+
flush("&&");
|
|
192
|
+
i += 2;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (ch2 === "||") {
|
|
196
|
+
flush("||");
|
|
197
|
+
i += 2;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (ch === ";") {
|
|
201
|
+
flush(";");
|
|
202
|
+
i++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
153
205
|
|
|
154
|
-
current += ch;
|
|
206
|
+
current += ch;
|
|
207
|
+
i++;
|
|
155
208
|
}
|
|
156
209
|
flush();
|
|
157
210
|
return segments;
|
|
@@ -170,13 +223,26 @@ function splitByPipe(input: string): string[] {
|
|
|
170
223
|
|
|
171
224
|
for (let i = 0; i < input.length; i++) {
|
|
172
225
|
const ch = input[i]!;
|
|
173
|
-
if ((ch === '"' || ch === "'") && !inQ) {
|
|
174
|
-
|
|
175
|
-
|
|
226
|
+
if ((ch === '"' || ch === "'") && !inQ) {
|
|
227
|
+
inQ = true;
|
|
228
|
+
qChar = ch;
|
|
229
|
+
current += ch;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (inQ && ch === qChar) {
|
|
233
|
+
inQ = false;
|
|
234
|
+
current += ch;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (inQ) {
|
|
238
|
+
current += ch;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
176
241
|
|
|
177
242
|
// || was already consumed at statement level, bare | is pipe
|
|
178
243
|
if (ch === "|" && input[i + 1] !== "|") {
|
|
179
|
-
if (!current.trim())
|
|
244
|
+
if (!current.trim())
|
|
245
|
+
throw new Error("Syntax error near unexpected token '|'");
|
|
180
246
|
tokens.push(current.trim());
|
|
181
247
|
current = "";
|
|
182
248
|
} else {
|
|
@@ -185,7 +251,8 @@ function splitByPipe(input: string): string[] {
|
|
|
185
251
|
}
|
|
186
252
|
|
|
187
253
|
const tail = current.trim();
|
|
188
|
-
if (!tail && tokens.length > 0)
|
|
254
|
+
if (!tail && tokens.length > 0)
|
|
255
|
+
throw new Error("Syntax error near unexpected token '|'");
|
|
189
256
|
if (tail) tokens.push(tail);
|
|
190
257
|
return tokens;
|
|
191
258
|
}
|
|
@@ -204,19 +271,27 @@ function parseCommandWithRedirections(token: string): PipelineCommand {
|
|
|
204
271
|
const part = parts[i]!;
|
|
205
272
|
if (part === "<") {
|
|
206
273
|
i++;
|
|
207
|
-
if (i >= parts.length)
|
|
274
|
+
if (i >= parts.length)
|
|
275
|
+
throw new Error("Syntax error: expected filename after <");
|
|
208
276
|
inputFile = parts[i];
|
|
209
277
|
i++;
|
|
210
278
|
} else if (part === ">>") {
|
|
211
279
|
i++;
|
|
212
|
-
if (i >= parts.length)
|
|
213
|
-
|
|
280
|
+
if (i >= parts.length)
|
|
281
|
+
throw new Error("Syntax error: expected filename after >>");
|
|
282
|
+
outputFile = parts[i];
|
|
283
|
+
appendOutput = true;
|
|
284
|
+
i++;
|
|
214
285
|
} else if (part === ">") {
|
|
215
286
|
i++;
|
|
216
|
-
if (i >= parts.length)
|
|
217
|
-
|
|
287
|
+
if (i >= parts.length)
|
|
288
|
+
throw new Error("Syntax error: expected filename after >");
|
|
289
|
+
outputFile = parts[i];
|
|
290
|
+
appendOutput = false;
|
|
291
|
+
i++;
|
|
218
292
|
} else {
|
|
219
|
-
cmdParts.push(part);
|
|
293
|
+
cmdParts.push(part);
|
|
294
|
+
i++;
|
|
220
295
|
}
|
|
221
296
|
}
|
|
222
297
|
|
|
@@ -236,26 +311,49 @@ function tokenizeCommand(input: string): string[] {
|
|
|
236
311
|
const next = input[i + 1];
|
|
237
312
|
|
|
238
313
|
if ((ch === '"' || ch === "'") && !inQ) {
|
|
239
|
-
inQ = true;
|
|
314
|
+
inQ = true;
|
|
315
|
+
qChar = ch;
|
|
316
|
+
i++;
|
|
317
|
+
continue;
|
|
240
318
|
}
|
|
241
319
|
if (inQ && ch === qChar) {
|
|
242
|
-
inQ = false;
|
|
320
|
+
inQ = false;
|
|
321
|
+
qChar = "";
|
|
322
|
+
i++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (inQ) {
|
|
326
|
+
current += ch;
|
|
327
|
+
i++;
|
|
328
|
+
continue;
|
|
243
329
|
}
|
|
244
|
-
if (inQ) { current += ch; i++; continue; }
|
|
245
330
|
|
|
246
331
|
if (ch === " ") {
|
|
247
|
-
if (current) {
|
|
248
|
-
|
|
332
|
+
if (current) {
|
|
333
|
+
tokens.push(current);
|
|
334
|
+
current = "";
|
|
335
|
+
}
|
|
336
|
+
i++;
|
|
337
|
+
continue;
|
|
249
338
|
}
|
|
250
339
|
|
|
251
340
|
if ((ch === ">" || ch === "<") && !inQ) {
|
|
252
|
-
if (current) {
|
|
253
|
-
|
|
254
|
-
|
|
341
|
+
if (current) {
|
|
342
|
+
tokens.push(current);
|
|
343
|
+
current = "";
|
|
344
|
+
}
|
|
345
|
+
if (ch === ">" && next === ">") {
|
|
346
|
+
tokens.push(">>");
|
|
347
|
+
i += 2;
|
|
348
|
+
} else {
|
|
349
|
+
tokens.push(ch);
|
|
350
|
+
i++;
|
|
351
|
+
}
|
|
255
352
|
continue;
|
|
256
353
|
}
|
|
257
354
|
|
|
258
|
-
current += ch;
|
|
355
|
+
current += ch;
|
|
356
|
+
i++;
|
|
259
357
|
}
|
|
260
358
|
if (current) tokens.push(current);
|
|
261
359
|
return tokens;
|
|
@@ -683,7 +683,10 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
683
683
|
throw new Error("invalid password");
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
|
-
private readonly authorizedKeys = new Map<
|
|
686
|
+
private readonly authorizedKeys = new Map<
|
|
687
|
+
string,
|
|
688
|
+
Array<{ algo: string; data: Buffer }>
|
|
689
|
+
>();
|
|
687
690
|
|
|
688
691
|
/**
|
|
689
692
|
* Adds an SSH public key for a user, enabling public-key authentication.
|
|
@@ -716,7 +719,9 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
716
719
|
*
|
|
717
720
|
* @param username Target user.
|
|
718
721
|
*/
|
|
719
|
-
public getAuthorizedKeys(
|
|
722
|
+
public getAuthorizedKeys(
|
|
723
|
+
username: string,
|
|
724
|
+
): Array<{ algo: string; data: Buffer }> {
|
|
720
725
|
return this.authorizedKeys.get(username) ?? [];
|
|
721
726
|
}
|
|
722
727
|
}
|
package/src/commands/adduser.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Add a new user to the virtual user database.
|
|
5
|
+
* @category users
|
|
6
|
+
* @params ["<username> <password>"]
|
|
7
|
+
* @returns ShellModule
|
|
8
|
+
*/
|
|
3
9
|
export const adduserCommand: ShellModule = {
|
|
4
10
|
name: "adduser",
|
|
5
11
|
description: "Add a new user",
|
package/src/commands/alias.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Manage shell aliases (list / set / remove).
|
|
5
|
+
* @category shell
|
|
6
|
+
* @params ["[name[=value] ...]"]
|
|
7
|
+
*/
|
|
4
8
|
export const aliasCommand: ShellModule = {
|
|
5
9
|
name: "alias",
|
|
6
10
|
description: "Define or display aliases",
|
package/src/commands/apt.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
|
-
import { ifFlag
|
|
2
|
+
import { ifFlag } from "./command-helpers";
|
|
3
3
|
import { getPackageManager } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* APT package manager front-end (simulated).
|
|
7
|
+
* @category package
|
|
8
|
+
* @params ["<install|remove|update|upgrade|search|show|list> [pkg...]"]
|
|
9
|
+
*/
|
|
5
10
|
export const aptCommand: ShellModule = {
|
|
6
11
|
name: "apt",
|
|
7
12
|
aliases: ["apt-get"],
|
|
8
13
|
description: "Package manager",
|
|
9
|
-
category: "
|
|
14
|
+
category: "package",
|
|
10
15
|
params: ["<install|remove|update|upgrade|search|show|list> [pkg...]"],
|
|
11
16
|
run: ({ args, shell, authUser }) => {
|
|
12
17
|
const pm = getPackageManager(shell);
|
|
13
|
-
if (!pm)
|
|
18
|
+
if (!pm)
|
|
19
|
+
return { stderr: "apt: package manager not initialised", exitCode: 1 };
|
|
14
20
|
|
|
15
21
|
const sub = args[0]?.toLowerCase();
|
|
16
22
|
const rest = args.slice(1);
|
|
@@ -23,7 +29,8 @@ export const aptCommand: ShellModule = {
|
|
|
23
29
|
const restricted = ["install", "remove", "purge", "upgrade", "update"];
|
|
24
30
|
if (restricted.includes(sub ?? "") && authUser !== "root") {
|
|
25
31
|
return {
|
|
26
|
-
stderr:
|
|
32
|
+
stderr:
|
|
33
|
+
"E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)\nE: Unable to acquire the dpkg frontend lock, are you root?",
|
|
27
34
|
exitCode: 100,
|
|
28
35
|
};
|
|
29
36
|
}
|
|
@@ -85,7 +92,8 @@ export const aptCommand: ShellModule = {
|
|
|
85
92
|
exitCode: 0,
|
|
86
93
|
};
|
|
87
94
|
const lines = results.map(
|
|
88
|
-
(p) =>
|
|
95
|
+
(p) =>
|
|
96
|
+
`${p.name}/${p.section ?? "misc"} ${p.version} amd64\n ${p.shortDesc ?? p.description}`,
|
|
89
97
|
);
|
|
90
98
|
return {
|
|
91
99
|
stdout: `Sorting... Done\nFull Text Search... Done\n${lines.join("\n")}`,
|
|
@@ -95,10 +103,14 @@ export const aptCommand: ShellModule = {
|
|
|
95
103
|
|
|
96
104
|
case "show": {
|
|
97
105
|
const name = pkgs[0];
|
|
98
|
-
if (!name)
|
|
106
|
+
if (!name)
|
|
107
|
+
return { stderr: "apt: show requires a package name", exitCode: 1 };
|
|
99
108
|
const info = pm.show(name);
|
|
100
109
|
if (!info)
|
|
101
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
stderr: `N: Unable to locate package ${name}`,
|
|
112
|
+
exitCode: 100,
|
|
113
|
+
};
|
|
102
114
|
return { stdout: info, exitCode: 0 };
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -107,11 +119,18 @@ export const aptCommand: ShellModule = {
|
|
|
107
119
|
if (installedFlag) {
|
|
108
120
|
const pkgList = pm.listInstalled();
|
|
109
121
|
if (pkgList.length === 0)
|
|
110
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
stdout: "Listing... Done\n(no packages installed)",
|
|
124
|
+
exitCode: 0,
|
|
125
|
+
};
|
|
111
126
|
const lines = pkgList.map(
|
|
112
|
-
(p) =>
|
|
127
|
+
(p) =>
|
|
128
|
+
`${p.name}/${p.section} ${p.version} ${p.architecture} [installed]`,
|
|
113
129
|
);
|
|
114
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
stdout: `Listing... Done\n${lines.join("\n")}`,
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
};
|
|
115
134
|
}
|
|
116
135
|
// all available
|
|
117
136
|
const all = pm.listAvailable();
|
|
@@ -146,11 +165,15 @@ export const aptCommand: ShellModule = {
|
|
|
146
165
|
export const aptCacheCommand: ShellModule = {
|
|
147
166
|
name: "apt-cache",
|
|
148
167
|
description: "Query the package cache",
|
|
149
|
-
category: "
|
|
168
|
+
category: "package",
|
|
150
169
|
params: ["<search|show|policy> [pkg]"],
|
|
151
170
|
run: ({ args, shell }) => {
|
|
152
171
|
const pm = getPackageManager(shell);
|
|
153
|
-
if (!pm)
|
|
172
|
+
if (!pm)
|
|
173
|
+
return {
|
|
174
|
+
stderr: "apt-cache: package manager not initialised",
|
|
175
|
+
exitCode: 1,
|
|
176
|
+
};
|
|
154
177
|
|
|
155
178
|
const sub = args[0]?.toLowerCase();
|
|
156
179
|
const pkgName = args[1];
|
|
@@ -160,9 +183,10 @@ export const aptCacheCommand: ShellModule = {
|
|
|
160
183
|
if (!pkgName) return { stderr: "Need a search term", exitCode: 1 };
|
|
161
184
|
const results = pm.search(pkgName);
|
|
162
185
|
return {
|
|
163
|
-
stdout:
|
|
164
|
-
|
|
165
|
-
|
|
186
|
+
stdout:
|
|
187
|
+
results
|
|
188
|
+
.map((p) => `${p.name} - ${p.shortDesc ?? p.description}`)
|
|
189
|
+
.join("\n") || "(no results)",
|
|
166
190
|
exitCode: 0,
|
|
167
191
|
};
|
|
168
192
|
}
|
|
@@ -177,7 +201,10 @@ export const aptCacheCommand: ShellModule = {
|
|
|
177
201
|
if (!pkgName) return { stderr: "Need a package name", exitCode: 1 };
|
|
178
202
|
const def = pm.findInRegistry(pkgName);
|
|
179
203
|
if (!def)
|
|
180
|
-
return {
|
|
204
|
+
return {
|
|
205
|
+
stderr: `N: Unable to locate package ${pkgName}`,
|
|
206
|
+
exitCode: 100,
|
|
207
|
+
};
|
|
181
208
|
const inst = pm.isInstalled(pkgName);
|
|
182
209
|
return {
|
|
183
210
|
stdout: [
|
|
@@ -192,7 +219,10 @@ export const aptCacheCommand: ShellModule = {
|
|
|
192
219
|
};
|
|
193
220
|
}
|
|
194
221
|
default:
|
|
195
|
-
return {
|
|
222
|
+
return {
|
|
223
|
+
stderr: `apt-cache: unknown command '${sub ?? ""}'`,
|
|
224
|
+
exitCode: 1,
|
|
225
|
+
};
|
|
196
226
|
}
|
|
197
227
|
},
|
|
198
228
|
};
|
package/src/commands/awk.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { getFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Minimal `awk`-like pattern scanner (supports simple print patterns).
|
|
6
|
+
* @category text
|
|
7
|
+
* @params ["[-F <sep>] '<program>' [file]"]
|
|
8
|
+
*
|
|
9
|
+
* Supported program patterns:
|
|
10
|
+
* - `print $N` (e.g. `print $1`, `print $2, $3`, `print $0`)
|
|
11
|
+
* - `{print $N}` (e.g. `{print $1}`, `{print $2, $3}`, `{print $0}`)
|
|
12
|
+
*
|
|
13
|
+
* The field separator can be set with `-F` (default is space, which splits on any whitespace).
|
|
14
|
+
*/
|
|
4
15
|
export const awkCommand: ShellModule = {
|
|
5
16
|
name: "awk",
|
|
6
17
|
description: "Pattern scanning and processing language (minimal)",
|
|
@@ -13,17 +24,20 @@ export const awkCommand: ShellModule = {
|
|
|
13
24
|
|
|
14
25
|
// Only support print $N and {print $N} patterns
|
|
15
26
|
const printMatch = prog.match(/^\{?\s*print\s+([^}]+)\s*\}?$/);
|
|
16
|
-
if (!printMatch)
|
|
27
|
+
if (!printMatch)
|
|
28
|
+
return { stderr: `awk: unsupported program: ${prog}`, exitCode: 1 };
|
|
17
29
|
|
|
18
30
|
const fields = printMatch[1]!.split(/\s*,\s*/).map((f) => f.trim());
|
|
19
31
|
const lines = (stdin ?? "").split("\n").filter(Boolean);
|
|
20
32
|
const out = lines.map((line) => {
|
|
21
33
|
const parts = line.split(sep === " " ? /\s+/ : sep);
|
|
22
|
-
return fields
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
return fields
|
|
35
|
+
.map((f) => {
|
|
36
|
+
if (f === "$0") return line;
|
|
37
|
+
const n = parseInt(f.replace("$", ""), 10);
|
|
38
|
+
return Number.isNaN(n) ? f.replace(/"/g, "") : (parts[n - 1] ?? "");
|
|
39
|
+
})
|
|
40
|
+
.join(sep === " " ? "\t" : sep);
|
|
27
41
|
});
|
|
28
42
|
return { stdout: out.join("\n"), exitCode: 0 };
|
|
29
43
|
},
|
package/src/commands/base64.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Encode or decode base64 data.
|
|
6
|
+
* @category text
|
|
7
|
+
* @params ["[-d] [file]"]
|
|
8
|
+
*/
|
|
4
9
|
export const base64Command: ShellModule = {
|
|
5
10
|
name: "base64",
|
|
6
11
|
description: "Encode/decode base64",
|
|
@@ -10,8 +15,14 @@ export const base64Command: ShellModule = {
|
|
|
10
15
|
const decode = ifFlag(args, ["-d", "--decode"]);
|
|
11
16
|
const input = stdin ?? "";
|
|
12
17
|
if (decode) {
|
|
13
|
-
try {
|
|
14
|
-
|
|
18
|
+
try {
|
|
19
|
+
return {
|
|
20
|
+
stdout: Buffer.from(input.trim(), "base64").toString("utf8"),
|
|
21
|
+
exitCode: 0,
|
|
22
|
+
};
|
|
23
|
+
} catch {
|
|
24
|
+
return { stderr: "base64: invalid input", exitCode: 1 };
|
|
25
|
+
}
|
|
15
26
|
}
|
|
16
27
|
return { stdout: Buffer.from(input).toString("base64"), exitCode: 0 };
|
|
17
28
|
},
|
package/src/commands/cat.ts
CHANGED
|
@@ -2,13 +2,18 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolveReadablePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Concatenate and print files to stdout.
|
|
7
|
+
* @category files
|
|
8
|
+
* @params ["[-n] [-b] <file...>"]
|
|
9
|
+
*/
|
|
5
10
|
export const catCommand: ShellModule = {
|
|
6
11
|
name: "cat",
|
|
7
12
|
description: "Concatenate and print files",
|
|
8
13
|
category: "files",
|
|
9
14
|
params: ["[-n] [-b] <file...>"],
|
|
10
15
|
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
11
|
-
const numberAll
|
|
16
|
+
const numberAll = ifFlag(args, ["-n", "--number"]);
|
|
12
17
|
const numberNonBlank = ifFlag(args, ["-b", "--number-nonblank"]);
|
|
13
18
|
const fileArgs = args.filter((a) => !a.startsWith("-"));
|
|
14
19
|
|
|
@@ -34,10 +39,13 @@ export const catCommand: ShellModule = {
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
let lineNum = 1;
|
|
37
|
-
const numbered = combined
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const numbered = combined
|
|
43
|
+
.split("\n")
|
|
44
|
+
.map((line) => {
|
|
45
|
+
if (numberNonBlank && line.trim() === "") return line;
|
|
46
|
+
return `${String(lineNum++).padStart(6)}\t${line}`;
|
|
47
|
+
})
|
|
48
|
+
.join("\n");
|
|
41
49
|
|
|
42
50
|
return { stdout: numbered, exitCode: 0 };
|
|
43
51
|
},
|
package/src/commands/cd.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Change current working directory.
|
|
6
|
+
* @category navigation
|
|
7
|
+
* @params ["[path]"]
|
|
8
|
+
*/
|
|
4
9
|
export const cdCommand: ShellModule = {
|
|
5
10
|
name: "cd",
|
|
6
11
|
description: "Change directory",
|
package/src/commands/chmod.ts
CHANGED
|
@@ -37,6 +37,11 @@ function applySymbolicMode(existing: number, modeStr: string): number | null {
|
|
|
37
37
|
return mode;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Change file permissions (octal or symbolic).
|
|
42
|
+
* @category files
|
|
43
|
+
* @params ["<mode> <file>"]
|
|
44
|
+
*/
|
|
40
45
|
export const chmodCommand: ShellModule = {
|
|
41
46
|
name: "chmod",
|
|
42
47
|
description: "Change file permissions",
|
package/src/commands/cp.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Copy files or directories inside the virtual filesystem.
|
|
7
|
+
* @category files
|
|
8
|
+
* @params ["[-r] <source> <dest>"]
|
|
9
|
+
*/
|
|
5
10
|
export const cpCommand: ShellModule = {
|
|
6
11
|
name: "cp",
|
|
7
12
|
description: "Copy files or directories",
|