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.
Files changed (281) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +141 -50
  3. package/biome.json +7 -0
  4. package/dist/SSHMimic/exec.d.ts.map +1 -1
  5. package/dist/SSHMimic/executor.d.ts.map +1 -1
  6. package/dist/SSHMimic/executor.js +32 -16
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +20 -6
  9. package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
  10. package/dist/VirtualFileSystem/binaryPack.js +29 -6
  11. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem/index.js +36 -13
  13. package/dist/VirtualPackageManager/index.d.ts.map +1 -1
  14. package/dist/VirtualPackageManager/index.js +192 -43
  15. package/dist/VirtualShell/index.d.ts +10 -4
  16. package/dist/VirtualShell/index.d.ts.map +1 -1
  17. package/dist/VirtualShell/index.js +18 -7
  18. package/dist/VirtualShell/shell.d.ts.map +1 -1
  19. package/dist/VirtualShell/shell.js +3 -1
  20. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  21. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  22. package/dist/commands/adduser.d.ts +6 -0
  23. package/dist/commands/adduser.d.ts.map +1 -1
  24. package/dist/commands/adduser.js +6 -0
  25. package/dist/commands/alias.d.ts +5 -0
  26. package/dist/commands/alias.d.ts.map +1 -1
  27. package/dist/commands/alias.js +5 -0
  28. package/dist/commands/apt.d.ts +5 -0
  29. package/dist/commands/apt.d.ts.map +1 -1
  30. package/dist/commands/apt.js +32 -9
  31. package/dist/commands/awk.d.ts +11 -0
  32. package/dist/commands/awk.d.ts.map +1 -1
  33. package/dist/commands/awk.js +15 -2
  34. package/dist/commands/base64.d.ts +5 -0
  35. package/dist/commands/base64.d.ts.map +1 -1
  36. package/dist/commands/base64.js +9 -1
  37. package/dist/commands/cat.d.ts +5 -0
  38. package/dist/commands/cat.d.ts.map +1 -1
  39. package/dist/commands/cat.js +10 -2
  40. package/dist/commands/cd.d.ts +5 -0
  41. package/dist/commands/cd.d.ts.map +1 -1
  42. package/dist/commands/cd.js +5 -0
  43. package/dist/commands/chmod.d.ts +5 -0
  44. package/dist/commands/chmod.d.ts.map +1 -1
  45. package/dist/commands/chmod.js +5 -0
  46. package/dist/commands/cp.d.ts +5 -0
  47. package/dist/commands/cp.d.ts.map +1 -1
  48. package/dist/commands/cp.js +5 -0
  49. package/dist/commands/curl.d.ts +5 -0
  50. package/dist/commands/curl.d.ts.map +1 -1
  51. package/dist/commands/curl.js +34 -6
  52. package/dist/commands/cut.d.ts +5 -0
  53. package/dist/commands/cut.d.ts.map +1 -1
  54. package/dist/commands/cut.js +8 -1
  55. package/dist/commands/date.d.ts +5 -0
  56. package/dist/commands/date.d.ts.map +1 -1
  57. package/dist/commands/date.js +7 -1
  58. package/dist/commands/declare.d.ts +3 -0
  59. package/dist/commands/declare.d.ts.map +1 -0
  60. package/dist/commands/declare.js +39 -0
  61. package/dist/commands/diff.d.ts +5 -0
  62. package/dist/commands/diff.d.ts.map +1 -1
  63. package/dist/commands/diff.js +5 -0
  64. package/dist/commands/dpkg.d.ts +5 -0
  65. package/dist/commands/dpkg.d.ts.map +1 -1
  66. package/dist/commands/dpkg.js +24 -7
  67. package/dist/commands/du.d.ts.map +1 -1
  68. package/dist/commands/du.js +8 -2
  69. package/dist/commands/echo.d.ts +5 -0
  70. package/dist/commands/echo.d.ts.map +1 -1
  71. package/dist/commands/echo.js +13 -4
  72. package/dist/commands/env.d.ts +5 -0
  73. package/dist/commands/env.d.ts.map +1 -1
  74. package/dist/commands/env.js +11 -1
  75. package/dist/commands/exit.d.ts +5 -0
  76. package/dist/commands/exit.d.ts.map +1 -1
  77. package/dist/commands/exit.js +12 -2
  78. package/dist/commands/export.d.ts.map +1 -1
  79. package/dist/commands/export.js +3 -1
  80. package/dist/commands/find.d.ts +5 -0
  81. package/dist/commands/find.d.ts.map +1 -1
  82. package/dist/commands/find.js +5 -0
  83. package/dist/commands/free.d.ts +5 -0
  84. package/dist/commands/free.d.ts.map +1 -1
  85. package/dist/commands/free.js +5 -0
  86. package/dist/commands/grep.d.ts +5 -0
  87. package/dist/commands/grep.d.ts.map +1 -1
  88. package/dist/commands/grep.js +12 -2
  89. package/dist/commands/gzip.d.ts +5 -0
  90. package/dist/commands/gzip.d.ts.map +1 -1
  91. package/dist/commands/gzip.js +18 -2
  92. package/dist/commands/head.d.ts +5 -0
  93. package/dist/commands/head.d.ts.map +1 -1
  94. package/dist/commands/head.js +5 -0
  95. package/dist/commands/help.d.ts.map +1 -1
  96. package/dist/commands/help.js +98 -45
  97. package/dist/commands/history.d.ts +5 -0
  98. package/dist/commands/history.d.ts.map +1 -1
  99. package/dist/commands/history.js +5 -0
  100. package/dist/commands/hostname.d.ts +5 -0
  101. package/dist/commands/hostname.d.ts.map +1 -1
  102. package/dist/commands/hostname.js +5 -0
  103. package/dist/commands/id.d.ts.map +1 -1
  104. package/dist/commands/id.js +4 -1
  105. package/dist/commands/index.d.ts +2 -17
  106. package/dist/commands/index.d.ts.map +1 -1
  107. package/dist/commands/index.js +2 -340
  108. package/dist/commands/ls.d.ts.map +1 -1
  109. package/dist/commands/ls.js +3 -1
  110. package/dist/commands/lsb-release.d.ts.map +1 -1
  111. package/dist/commands/lsb-release.js +8 -2
  112. package/dist/commands/nano.js +1 -1
  113. package/dist/commands/neofetch.js +1 -1
  114. package/dist/commands/node.d.ts +9 -0
  115. package/dist/commands/node.d.ts.map +1 -0
  116. package/dist/commands/node.js +316 -0
  117. package/dist/commands/npm.d.ts +19 -0
  118. package/dist/commands/npm.d.ts.map +1 -0
  119. package/dist/commands/npm.js +109 -0
  120. package/dist/commands/ping.d.ts.map +1 -1
  121. package/dist/commands/ping.js +3 -1
  122. package/dist/commands/printf.d.ts +3 -0
  123. package/dist/commands/printf.d.ts.map +1 -0
  124. package/dist/commands/printf.js +113 -0
  125. package/dist/commands/ps.d.ts.map +1 -1
  126. package/dist/commands/ps.js +4 -1
  127. package/dist/commands/python.d.ts +30 -0
  128. package/dist/commands/python.d.ts.map +1 -0
  129. package/dist/commands/python.js +2058 -0
  130. package/dist/commands/read.d.ts +3 -0
  131. package/dist/commands/read.d.ts.map +1 -0
  132. package/dist/commands/read.js +34 -0
  133. package/dist/commands/registry.d.ts +8 -0
  134. package/dist/commands/registry.d.ts.map +1 -0
  135. package/dist/commands/registry.js +229 -0
  136. package/dist/commands/runtime.d.ts +6 -0
  137. package/dist/commands/runtime.d.ts.map +1 -0
  138. package/dist/commands/runtime.js +280 -0
  139. package/dist/commands/sed.d.ts.map +1 -1
  140. package/dist/commands/sed.js +11 -3
  141. package/dist/commands/set.d.ts.map +1 -1
  142. package/dist/commands/set.js +9 -3
  143. package/dist/commands/sh.d.ts.map +1 -1
  144. package/dist/commands/sh.js +57 -36
  145. package/dist/commands/shift.d.ts +5 -0
  146. package/dist/commands/shift.d.ts.map +1 -0
  147. package/dist/commands/shift.js +52 -0
  148. package/dist/commands/sleep.d.ts.map +1 -1
  149. package/dist/commands/sort.d.ts.map +1 -1
  150. package/dist/commands/sort.js +4 -2
  151. package/dist/commands/source.d.ts.map +1 -1
  152. package/dist/commands/source.js +5 -2
  153. package/dist/commands/sudo.js +1 -1
  154. package/dist/commands/tar.d.ts.map +1 -1
  155. package/dist/commands/tar.js +11 -3
  156. package/dist/commands/tee.d.ts.map +1 -1
  157. package/dist/commands/tee.js +8 -6
  158. package/dist/commands/test.d.ts.map +1 -1
  159. package/dist/commands/test.js +46 -24
  160. package/dist/commands/tr.d.ts.map +1 -1
  161. package/dist/commands/tr.js +3 -1
  162. package/dist/commands/true.d.ts +4 -0
  163. package/dist/commands/true.d.ts.map +1 -0
  164. package/dist/commands/true.js +14 -0
  165. package/dist/commands/type.d.ts.map +1 -1
  166. package/dist/commands/type.js +1 -1
  167. package/dist/commands/uname.d.ts.map +1 -1
  168. package/dist/commands/uname.js +4 -1
  169. package/dist/commands/uniq.d.ts.map +1 -1
  170. package/dist/commands/uptime.d.ts.map +1 -1
  171. package/dist/commands/uptime.js +4 -1
  172. package/dist/commands/wget.d.ts.map +1 -1
  173. package/dist/commands/wget.js +32 -7
  174. package/dist/commands/which.d.ts.map +1 -1
  175. package/dist/commands/xargs.d.ts.map +1 -1
  176. package/dist/commands/xargs.js +1 -1
  177. package/dist/index.d.ts +15 -14
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +9 -9
  180. package/dist/modules/linuxRootfs.d.ts +18 -1
  181. package/dist/modules/linuxRootfs.d.ts.map +1 -1
  182. package/dist/modules/linuxRootfs.js +160 -17
  183. package/dist/standalone-wo-sftp.d.ts +2 -0
  184. package/dist/standalone-wo-sftp.d.ts.map +1 -0
  185. package/dist/standalone-wo-sftp.js +30 -0
  186. package/dist/utils/expand.d.ts +50 -0
  187. package/dist/utils/expand.d.ts.map +1 -0
  188. package/dist/utils/expand.js +183 -0
  189. package/dist/utils/vfsDiff.d.ts +90 -0
  190. package/dist/utils/vfsDiff.d.ts.map +1 -0
  191. package/dist/utils/vfsDiff.js +177 -0
  192. package/package.json +2 -1
  193. package/src/SSHMimic/exec.ts +10 -1
  194. package/src/SSHMimic/executor.ts +104 -18
  195. package/src/SSHMimic/index.ts +49 -15
  196. package/src/VirtualFileSystem/binaryPack.ts +35 -8
  197. package/src/VirtualFileSystem/index.ts +78 -28
  198. package/src/VirtualPackageManager/index.ts +208 -49
  199. package/src/VirtualShell/index.ts +35 -7
  200. package/src/VirtualShell/shell.ts +23 -3
  201. package/src/VirtualShell/shellParser.ts +134 -36
  202. package/src/VirtualUserManager/index.ts +7 -2
  203. package/src/commands/adduser.ts +6 -0
  204. package/src/commands/alias.ts +5 -1
  205. package/src/commands/apt.ts +47 -17
  206. package/src/commands/awk.ts +20 -6
  207. package/src/commands/base64.ts +13 -2
  208. package/src/commands/cat.ts +13 -5
  209. package/src/commands/cd.ts +5 -0
  210. package/src/commands/chmod.ts +5 -0
  211. package/src/commands/cp.ts +5 -0
  212. package/src/commands/curl.ts +56 -12
  213. package/src/commands/cut.ts +8 -1
  214. package/src/commands/date.ts +7 -1
  215. package/src/commands/declare.ts +44 -0
  216. package/src/commands/diff.ts +17 -3
  217. package/src/commands/dpkg.ts +33 -11
  218. package/src/commands/du.ts +17 -5
  219. package/src/commands/echo.ts +22 -9
  220. package/src/commands/env.ts +11 -1
  221. package/src/commands/exit.ts +12 -2
  222. package/src/commands/export.ts +3 -1
  223. package/src/commands/find.ts +5 -0
  224. package/src/commands/free.ts +9 -2
  225. package/src/commands/grep.ts +12 -2
  226. package/src/commands/gzip.ts +28 -4
  227. package/src/commands/head.ts +5 -0
  228. package/src/commands/help.ts +121 -47
  229. package/src/commands/history.ts +7 -2
  230. package/src/commands/hostname.ts +5 -0
  231. package/src/commands/id.ts +4 -1
  232. package/src/commands/index.ts +9 -360
  233. package/src/commands/ls.ts +5 -3
  234. package/src/commands/lsb-release.ts +8 -2
  235. package/src/commands/nano.ts +1 -1
  236. package/src/commands/neofetch.ts +1 -1
  237. package/src/commands/node.ts +341 -0
  238. package/src/commands/npm.ts +132 -0
  239. package/src/commands/ping.ts +6 -2
  240. package/src/commands/printf.ts +112 -0
  241. package/src/commands/ps.ts +21 -9
  242. package/src/commands/python.ts +2229 -0
  243. package/src/commands/read.ts +41 -0
  244. package/src/commands/registry.ts +244 -0
  245. package/src/commands/runtime.ts +353 -0
  246. package/src/commands/sed.ts +27 -9
  247. package/src/commands/set.ts +9 -3
  248. package/src/commands/sh.ts +159 -55
  249. package/src/commands/shift.ts +53 -0
  250. package/src/commands/sleep.ts +2 -1
  251. package/src/commands/sort.ts +10 -6
  252. package/src/commands/source.ts +15 -3
  253. package/src/commands/sudo.ts +1 -1
  254. package/src/commands/tar.ts +28 -7
  255. package/src/commands/tee.ts +7 -1
  256. package/src/commands/test.ts +61 -26
  257. package/src/commands/tr.ts +3 -1
  258. package/src/commands/true.ts +17 -0
  259. package/src/commands/type.ts +6 -3
  260. package/src/commands/uname.ts +5 -1
  261. package/src/commands/uniq.ts +8 -2
  262. package/src/commands/uptime.ts +4 -1
  263. package/src/commands/wget.ts +51 -12
  264. package/src/commands/which.ts +5 -2
  265. package/src/commands/xargs.ts +11 -2
  266. package/src/index.ts +23 -24
  267. package/src/modules/linuxRootfs.ts +233 -30
  268. package/src/standalone-wo-sftp.ts +38 -0
  269. package/src/utils/expand.ts +238 -0
  270. package/src/utils/vfsDiff.ts +275 -0
  271. package/standalone-wo-sftp.js +507 -0
  272. package/standalone-wo-sftp.js.map +7 -0
  273. package/standalone.js +253 -191
  274. package/standalone.js.map +4 -4
  275. package/tests/bun-test-shim.ts +9 -1
  276. package/tests/command-helpers.test.ts +1 -5
  277. package/tests/new-features.test.ts +415 -5
  278. package/tests/parser-executor.test.ts +27 -27
  279. package/tests/sftp.test.ts +122 -42
  280. package/tests/users.test.ts +23 -5
  281. package/CHANGELOG.md +0 -150
@@ -1,4 +1,10 @@
1
- import type { Pipeline, PipelineCommand, Script, Statement, LogicalOp } from "../types/pipeline";
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(/\$\{([^}:]+):-([^}]*)\}/g, (_, name, def) =>
60
- env[name] ?? def,
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(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, name) =>
71
- env[name] ?? "",
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 { text: string; op?: LogicalOp }
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) { inQ = true; qChar = ch; current += ch; i++; continue; }
143
- if (inQ && ch === qChar) { inQ = false; current += ch; i++; continue; }
144
- if (inQ) { current += ch; i++; continue; }
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 === "(") { depth++; current += ch; i++; continue; }
147
- if (ch === ")") { depth--; current += ch; i++; continue; }
148
- if (depth > 0) { current += ch; i++; continue; }
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 === "&&") { flush("&&"); i += 2; continue; }
151
- if (ch2 === "||") { flush("||"); i += 2; continue; }
152
- if (ch === ";") { flush(";"); i++; continue; }
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; i++;
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) { inQ = true; qChar = ch; current += ch; continue; }
174
- if (inQ && ch === qChar) { inQ = false; current += ch; continue; }
175
- if (inQ) { current += ch; continue; }
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()) throw new Error("Syntax error near unexpected token '|'");
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) throw new Error("Syntax error near unexpected token '|'");
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) throw new Error("Syntax error: expected filename after <");
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) throw new Error("Syntax error: expected filename after >>");
213
- outputFile = parts[i]; appendOutput = true; i++;
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) throw new Error("Syntax error: expected filename after >");
217
- outputFile = parts[i]; appendOutput = false; i++;
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); i++;
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; qChar = ch; i++; continue;
314
+ inQ = true;
315
+ qChar = ch;
316
+ i++;
317
+ continue;
240
318
  }
241
319
  if (inQ && ch === qChar) {
242
- inQ = false; qChar = ""; i++; continue;
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) { tokens.push(current); current = ""; }
248
- i++; continue;
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) { tokens.push(current); current = ""; }
253
- if (ch === ">" && next === ">") { tokens.push(">>"); i += 2; }
254
- else { tokens.push(ch); i++; }
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; i++;
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<string, Array<{ algo: string; data: Buffer }>>();
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(username: string): Array<{ algo: string; data: Buffer }> {
722
+ public getAuthorizedKeys(
723
+ username: string,
724
+ ): Array<{ algo: string; data: Buffer }> {
720
725
  return this.authorizedKeys.get(username) ?? [];
721
726
  }
722
727
  }
@@ -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",
@@ -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",
@@ -1,16 +1,22 @@
1
1
  import type { ShellModule } from "../types/commands";
2
- import { ifFlag, } from "./command-helpers";
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: "system",
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) return { stderr: "apt: package manager not initialised", exitCode: 1 };
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: "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?",
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) => `${p.name}/${p.section ?? "misc"} ${p.version} amd64\n ${p.shortDesc ?? p.description}`,
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) return { stderr: "apt: show requires a package name", exitCode: 1 };
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 { stderr: `N: Unable to locate package ${name}`, exitCode: 100 };
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 { stdout: "Listing... Done\n(no packages installed)", exitCode: 0 };
122
+ return {
123
+ stdout: "Listing... Done\n(no packages installed)",
124
+ exitCode: 0,
125
+ };
111
126
  const lines = pkgList.map(
112
- (p) => `${p.name}/${p.section} ${p.version} ${p.architecture} [installed]`,
127
+ (p) =>
128
+ `${p.name}/${p.section} ${p.version} ${p.architecture} [installed]`,
113
129
  );
114
- return { stdout: `Listing... Done\n${lines.join("\n")}`, exitCode: 0 };
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: "system",
168
+ category: "package",
150
169
  params: ["<search|show|policy> [pkg]"],
151
170
  run: ({ args, shell }) => {
152
171
  const pm = getPackageManager(shell);
153
- if (!pm) return { stderr: "apt-cache: package manager not initialised", exitCode: 1 };
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: results
164
- .map((p) => `${p.name} - ${p.shortDesc ?? p.description}`)
165
- .join("\n") || "(no results)",
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 { stderr: `N: Unable to locate package ${pkgName}`, exitCode: 100 };
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 { stderr: `apt-cache: unknown command '${sub ?? ""}'`, exitCode: 1 };
222
+ return {
223
+ stderr: `apt-cache: unknown command '${sub ?? ""}'`,
224
+ exitCode: 1,
225
+ };
196
226
  }
197
227
  },
198
228
  };
@@ -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) return { stderr: `awk: unsupported program: ${prog}`, exitCode: 1 };
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.map((f) => {
23
- if (f === "$0") return line;
24
- const n = parseInt(f.replace("$", ""), 10);
25
- return Number.isNaN(n) ? f.replace(/"/g, "") : (parts[n - 1] ?? "");
26
- }).join(sep === " " ? "\t" : sep);
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
  },
@@ -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 { return { stdout: Buffer.from(input.trim(), "base64").toString("utf8"), exitCode: 0 }; }
14
- catch { return { stderr: "base64: invalid input", exitCode: 1 }; }
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
  },
@@ -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 = ifFlag(args, ["-n", "--number"]);
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.split("\n").map((line) => {
38
- if (numberNonBlank && line.trim() === "") return line;
39
- return `${String(lineNum++).padStart(6)}\t${line}`;
40
- }).join("\n");
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
  },
@@ -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",
@@ -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",
@@ -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",