typescript-virtual-container 1.2.8 → 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 (307) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +462 -44
  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 +35 -21
  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 +202 -0
  14. package/dist/VirtualPackageManager/index.d.ts.map +1 -0
  15. package/dist/VirtualPackageManager/index.js +825 -0
  16. package/dist/VirtualShell/index.d.ts +93 -12
  17. package/dist/VirtualShell/index.d.ts.map +1 -1
  18. package/dist/VirtualShell/index.js +95 -13
  19. package/dist/VirtualShell/shell.d.ts.map +1 -1
  20. package/dist/VirtualShell/shell.js +3 -1
  21. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  22. package/dist/VirtualUserManager/index.d.ts +52 -20
  23. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  24. package/dist/VirtualUserManager/index.js +54 -20
  25. package/dist/commands/adduser.d.ts +6 -0
  26. package/dist/commands/adduser.d.ts.map +1 -1
  27. package/dist/commands/adduser.js +6 -0
  28. package/dist/commands/alias.d.ts +9 -0
  29. package/dist/commands/alias.d.ts.map +1 -0
  30. package/dist/commands/alias.js +63 -0
  31. package/dist/commands/apt.d.ts +9 -0
  32. package/dist/commands/apt.d.ts.map +1 -0
  33. package/dist/commands/apt.js +205 -0
  34. package/dist/commands/awk.d.ts +11 -0
  35. package/dist/commands/awk.d.ts.map +1 -1
  36. package/dist/commands/awk.js +15 -2
  37. package/dist/commands/base64.d.ts +5 -0
  38. package/dist/commands/base64.d.ts.map +1 -1
  39. package/dist/commands/base64.js +9 -1
  40. package/dist/commands/cat.d.ts +5 -0
  41. package/dist/commands/cat.d.ts.map +1 -1
  42. package/dist/commands/cat.js +35 -8
  43. package/dist/commands/cd.d.ts +5 -0
  44. package/dist/commands/cd.d.ts.map +1 -1
  45. package/dist/commands/cd.js +5 -0
  46. package/dist/commands/chmod.d.ts +5 -0
  47. package/dist/commands/chmod.d.ts.map +1 -1
  48. package/dist/commands/chmod.js +57 -3
  49. package/dist/commands/command-helpers.d.ts +78 -4
  50. package/dist/commands/command-helpers.d.ts.map +1 -1
  51. package/dist/commands/command-helpers.js +78 -4
  52. package/dist/commands/cp.d.ts +5 -0
  53. package/dist/commands/cp.d.ts.map +1 -1
  54. package/dist/commands/cp.js +5 -0
  55. package/dist/commands/curl.d.ts +5 -0
  56. package/dist/commands/curl.d.ts.map +1 -1
  57. package/dist/commands/curl.js +106 -26
  58. package/dist/commands/cut.d.ts +5 -0
  59. package/dist/commands/cut.d.ts.map +1 -1
  60. package/dist/commands/cut.js +8 -1
  61. package/dist/commands/date.d.ts +5 -0
  62. package/dist/commands/date.d.ts.map +1 -1
  63. package/dist/commands/date.js +7 -1
  64. package/dist/commands/declare.d.ts +3 -0
  65. package/dist/commands/declare.d.ts.map +1 -0
  66. package/dist/commands/declare.js +39 -0
  67. package/dist/commands/diff.d.ts +5 -0
  68. package/dist/commands/diff.d.ts.map +1 -1
  69. package/dist/commands/diff.js +5 -0
  70. package/dist/commands/dpkg.d.ts +9 -0
  71. package/dist/commands/dpkg.d.ts.map +1 -0
  72. package/dist/commands/dpkg.js +161 -0
  73. package/dist/commands/du.d.ts.map +1 -1
  74. package/dist/commands/du.js +8 -2
  75. package/dist/commands/echo.d.ts +5 -0
  76. package/dist/commands/echo.d.ts.map +1 -1
  77. package/dist/commands/echo.js +33 -12
  78. package/dist/commands/env.d.ts +5 -0
  79. package/dist/commands/env.d.ts.map +1 -1
  80. package/dist/commands/env.js +11 -1
  81. package/dist/commands/exit.d.ts +5 -0
  82. package/dist/commands/exit.d.ts.map +1 -1
  83. package/dist/commands/exit.js +12 -2
  84. package/dist/commands/export.d.ts.map +1 -1
  85. package/dist/commands/export.js +3 -1
  86. package/dist/commands/find.d.ts +5 -0
  87. package/dist/commands/find.d.ts.map +1 -1
  88. package/dist/commands/find.js +5 -0
  89. package/dist/commands/free.d.ts +8 -0
  90. package/dist/commands/free.d.ts.map +1 -0
  91. package/dist/commands/free.js +43 -0
  92. package/dist/commands/grep.d.ts +5 -0
  93. package/dist/commands/grep.d.ts.map +1 -1
  94. package/dist/commands/grep.js +12 -2
  95. package/dist/commands/gzip.d.ts +5 -0
  96. package/dist/commands/gzip.d.ts.map +1 -1
  97. package/dist/commands/gzip.js +18 -2
  98. package/dist/commands/head.d.ts +5 -0
  99. package/dist/commands/head.d.ts.map +1 -1
  100. package/dist/commands/head.js +5 -0
  101. package/dist/commands/help.d.ts.map +1 -1
  102. package/dist/commands/help.js +98 -45
  103. package/dist/commands/helpers.d.ts +3 -0
  104. package/dist/commands/helpers.d.ts.map +1 -1
  105. package/dist/commands/helpers.js +3 -0
  106. package/dist/commands/history.d.ts +8 -0
  107. package/dist/commands/history.d.ts.map +1 -0
  108. package/dist/commands/history.js +26 -0
  109. package/dist/commands/hostname.d.ts +5 -0
  110. package/dist/commands/hostname.d.ts.map +1 -1
  111. package/dist/commands/hostname.js +5 -0
  112. package/dist/commands/id.d.ts.map +1 -1
  113. package/dist/commands/id.js +4 -1
  114. package/dist/commands/index.d.ts +2 -10
  115. package/dist/commands/index.d.ts.map +1 -1
  116. package/dist/commands/index.js +2 -231
  117. package/dist/commands/ls.d.ts.map +1 -1
  118. package/dist/commands/ls.js +6 -3
  119. package/dist/commands/lsb-release.d.ts +3 -0
  120. package/dist/commands/lsb-release.d.ts.map +1 -0
  121. package/dist/commands/lsb-release.js +56 -0
  122. package/dist/commands/man.d.ts +3 -0
  123. package/dist/commands/man.d.ts.map +1 -0
  124. package/dist/commands/man.js +155 -0
  125. package/dist/commands/nano.js +1 -1
  126. package/dist/commands/neofetch.d.ts.map +1 -1
  127. package/dist/commands/neofetch.js +6 -1
  128. package/dist/commands/node.d.ts +9 -0
  129. package/dist/commands/node.d.ts.map +1 -0
  130. package/dist/commands/node.js +316 -0
  131. package/dist/commands/npm.d.ts +19 -0
  132. package/dist/commands/npm.d.ts.map +1 -0
  133. package/dist/commands/npm.js +109 -0
  134. package/dist/commands/ping.d.ts.map +1 -1
  135. package/dist/commands/ping.js +7 -2
  136. package/dist/commands/printf.d.ts +3 -0
  137. package/dist/commands/printf.d.ts.map +1 -0
  138. package/dist/commands/printf.js +113 -0
  139. package/dist/commands/ps.d.ts.map +1 -1
  140. package/dist/commands/ps.js +30 -6
  141. package/dist/commands/python.d.ts +30 -0
  142. package/dist/commands/python.d.ts.map +1 -0
  143. package/dist/commands/python.js +2058 -0
  144. package/dist/commands/read.d.ts +3 -0
  145. package/dist/commands/read.d.ts.map +1 -0
  146. package/dist/commands/read.js +34 -0
  147. package/dist/commands/registry.d.ts +8 -0
  148. package/dist/commands/registry.d.ts.map +1 -0
  149. package/dist/commands/registry.js +229 -0
  150. package/dist/commands/runtime.d.ts +6 -0
  151. package/dist/commands/runtime.d.ts.map +1 -0
  152. package/dist/commands/runtime.js +280 -0
  153. package/dist/commands/sed.d.ts.map +1 -1
  154. package/dist/commands/sed.js +11 -3
  155. package/dist/commands/set.d.ts.map +1 -1
  156. package/dist/commands/set.js +9 -3
  157. package/dist/commands/sh.d.ts.map +1 -1
  158. package/dist/commands/sh.js +69 -30
  159. package/dist/commands/shift.d.ts +5 -0
  160. package/dist/commands/shift.d.ts.map +1 -0
  161. package/dist/commands/shift.js +52 -0
  162. package/dist/commands/sleep.d.ts.map +1 -1
  163. package/dist/commands/sort.d.ts.map +1 -1
  164. package/dist/commands/sort.js +4 -2
  165. package/dist/commands/source.d.ts +3 -0
  166. package/dist/commands/source.d.ts.map +1 -0
  167. package/dist/commands/source.js +34 -0
  168. package/dist/commands/sudo.js +1 -1
  169. package/dist/commands/tar.d.ts.map +1 -1
  170. package/dist/commands/tar.js +11 -3
  171. package/dist/commands/tee.d.ts.map +1 -1
  172. package/dist/commands/tee.js +8 -6
  173. package/dist/commands/test.d.ts +3 -0
  174. package/dist/commands/test.d.ts.map +1 -0
  175. package/dist/commands/test.js +114 -0
  176. package/dist/commands/tr.d.ts.map +1 -1
  177. package/dist/commands/tr.js +3 -1
  178. package/dist/commands/true.d.ts +4 -0
  179. package/dist/commands/true.d.ts.map +1 -0
  180. package/dist/commands/true.js +14 -0
  181. package/dist/commands/type.d.ts +3 -0
  182. package/dist/commands/type.d.ts.map +1 -0
  183. package/dist/commands/type.js +34 -0
  184. package/dist/commands/uname.d.ts.map +1 -1
  185. package/dist/commands/uname.js +4 -1
  186. package/dist/commands/uniq.d.ts.map +1 -1
  187. package/dist/commands/uptime.d.ts +3 -0
  188. package/dist/commands/uptime.d.ts.map +1 -0
  189. package/dist/commands/uptime.js +43 -0
  190. package/dist/commands/wget.d.ts.map +1 -1
  191. package/dist/commands/wget.js +92 -96
  192. package/dist/commands/which.d.ts +3 -0
  193. package/dist/commands/which.d.ts.map +1 -0
  194. package/dist/commands/which.js +32 -0
  195. package/dist/commands/xargs.d.ts.map +1 -1
  196. package/dist/commands/xargs.js +1 -1
  197. package/dist/index.d.ts +15 -11
  198. package/dist/index.d.ts.map +1 -1
  199. package/dist/index.js +9 -8
  200. package/dist/modules/linuxRootfs.d.ts +41 -0
  201. package/dist/modules/linuxRootfs.d.ts.map +1 -0
  202. package/dist/modules/linuxRootfs.js +440 -0
  203. package/dist/modules/neofetch.d.ts.map +1 -1
  204. package/dist/modules/neofetch.js +1 -0
  205. package/dist/standalone-wo-sftp.d.ts +2 -0
  206. package/dist/standalone-wo-sftp.d.ts.map +1 -0
  207. package/dist/standalone-wo-sftp.js +30 -0
  208. package/dist/utils/expand.d.ts +50 -0
  209. package/dist/utils/expand.d.ts.map +1 -0
  210. package/dist/utils/expand.js +183 -0
  211. package/dist/utils/vfsDiff.d.ts +90 -0
  212. package/dist/utils/vfsDiff.d.ts.map +1 -0
  213. package/dist/utils/vfsDiff.js +177 -0
  214. package/package.json +3 -1
  215. package/src/SSHMimic/exec.ts +10 -1
  216. package/src/SSHMimic/executor.ts +105 -21
  217. package/src/SSHMimic/index.ts +49 -15
  218. package/src/VirtualFileSystem/binaryPack.ts +35 -8
  219. package/src/VirtualFileSystem/index.ts +78 -28
  220. package/src/VirtualPackageManager/index.ts +979 -0
  221. package/src/VirtualShell/index.ts +133 -14
  222. package/src/VirtualShell/shell.ts +23 -3
  223. package/src/VirtualShell/shellParser.ts +134 -36
  224. package/src/VirtualUserManager/index.ts +62 -22
  225. package/src/commands/adduser.ts +6 -0
  226. package/src/commands/alias.ts +64 -0
  227. package/src/commands/apt.ts +228 -0
  228. package/src/commands/awk.ts +20 -6
  229. package/src/commands/base64.ts +13 -2
  230. package/src/commands/cat.ts +40 -8
  231. package/src/commands/cd.ts +5 -0
  232. package/src/commands/chmod.ts +53 -3
  233. package/src/commands/command-helpers.ts +78 -4
  234. package/src/commands/cp.ts +5 -0
  235. package/src/commands/curl.ts +118 -33
  236. package/src/commands/cut.ts +8 -1
  237. package/src/commands/date.ts +7 -1
  238. package/src/commands/declare.ts +44 -0
  239. package/src/commands/diff.ts +17 -3
  240. package/src/commands/dpkg.ts +180 -0
  241. package/src/commands/du.ts +17 -5
  242. package/src/commands/echo.ts +41 -12
  243. package/src/commands/env.ts +11 -1
  244. package/src/commands/exit.ts +12 -2
  245. package/src/commands/export.ts +3 -1
  246. package/src/commands/find.ts +5 -0
  247. package/src/commands/free.ts +47 -0
  248. package/src/commands/grep.ts +12 -2
  249. package/src/commands/gzip.ts +28 -4
  250. package/src/commands/head.ts +5 -0
  251. package/src/commands/help.ts +121 -47
  252. package/src/commands/helpers.ts +8 -0
  253. package/src/commands/history.ts +34 -0
  254. package/src/commands/hostname.ts +5 -0
  255. package/src/commands/id.ts +4 -1
  256. package/src/commands/index.ts +9 -255
  257. package/src/commands/ls.ts +6 -3
  258. package/src/commands/lsb-release.ts +58 -0
  259. package/src/commands/man.ts +166 -0
  260. package/src/commands/nano.ts +1 -1
  261. package/src/commands/neofetch.ts +6 -1
  262. package/src/commands/node.ts +341 -0
  263. package/src/commands/npm.ts +132 -0
  264. package/src/commands/ping.ts +10 -3
  265. package/src/commands/printf.ts +112 -0
  266. package/src/commands/ps.ts +40 -6
  267. package/src/commands/python.ts +2229 -0
  268. package/src/commands/read.ts +41 -0
  269. package/src/commands/registry.ts +244 -0
  270. package/src/commands/runtime.ts +353 -0
  271. package/src/commands/sed.ts +27 -9
  272. package/src/commands/set.ts +9 -3
  273. package/src/commands/sh.ts +170 -44
  274. package/src/commands/shift.ts +53 -0
  275. package/src/commands/sleep.ts +2 -1
  276. package/src/commands/sort.ts +10 -6
  277. package/src/commands/source.ts +47 -0
  278. package/src/commands/sudo.ts +1 -1
  279. package/src/commands/tar.ts +28 -7
  280. package/src/commands/tee.ts +7 -1
  281. package/src/commands/test.ts +135 -0
  282. package/src/commands/tr.ts +3 -1
  283. package/src/commands/true.ts +17 -0
  284. package/src/commands/type.ts +43 -0
  285. package/src/commands/uname.ts +5 -1
  286. package/src/commands/uniq.ts +8 -2
  287. package/src/commands/uptime.ts +49 -0
  288. package/src/commands/wget.ts +105 -119
  289. package/src/commands/which.ts +37 -0
  290. package/src/commands/xargs.ts +11 -2
  291. package/src/index.ts +27 -18
  292. package/src/modules/linuxRootfs.ts +642 -0
  293. package/src/modules/neofetch.ts +1 -0
  294. package/src/standalone-wo-sftp.ts +38 -0
  295. package/src/utils/expand.ts +238 -0
  296. package/src/utils/vfsDiff.ts +275 -0
  297. package/standalone-wo-sftp.js +507 -0
  298. package/standalone-wo-sftp.js.map +7 -0
  299. package/standalone.js +486 -109
  300. package/standalone.js.map +4 -4
  301. package/tests/bun-test-shim.ts +9 -1
  302. package/tests/command-helpers.test.ts +1 -5
  303. package/tests/new-features.test.ts +1036 -0
  304. package/tests/parser-executor.test.ts +27 -27
  305. package/tests/sftp.test.ts +122 -42
  306. package/tests/users.test.ts +23 -5
  307. package/CHANGELOG.md +0 -150
@@ -1,7 +1,12 @@
1
- import { runCommand as runSingleCommand } from "../commands";
1
+ import { runCommandDirect } from "../commands";
2
2
  import { resolvePath } from "../commands/helpers";
3
3
  import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
4
- import type { Pipeline, PipelineCommand, Script, Statement } from "../types/pipeline";
4
+ import type {
5
+ Pipeline,
6
+ PipelineCommand,
7
+ Script,
8
+ Statement,
9
+ } from "../types/pipeline";
5
10
  import type { VirtualShell } from "../VirtualShell";
6
11
 
7
12
  // ── Script executor (handles &&/||/;) ────────────────────────────────────────
@@ -15,17 +20,30 @@ export async function executeScript(
15
20
  shell: VirtualShell,
16
21
  env: ShellEnv,
17
22
  ): Promise<CommandResult> {
18
- if (!script.isValid) return { stderr: script.error || "Syntax error", exitCode: 1 };
23
+ if (!script.isValid)
24
+ return { stderr: script.error || "Syntax error", exitCode: 1 };
19
25
 
20
26
  let lastResult: CommandResult = { exitCode: 0 };
21
27
 
22
28
  for (const stmt of script.statements) {
23
29
  // Decide whether to run this statement based on previous op
24
- lastResult = await executePipeline(stmt.pipeline, authUser, hostname, mode, cwd, shell, env);
30
+ lastResult = await executePipeline(
31
+ stmt.pipeline,
32
+ authUser,
33
+ hostname,
34
+ mode,
35
+ cwd,
36
+ shell,
37
+ env,
38
+ );
25
39
  env.lastExitCode = lastResult.exitCode ?? 0;
26
40
 
27
41
  // Propagate session-control signals
28
- if (lastResult.closeSession || lastResult.switchUser || lastResult.nextCwd) {
42
+ if (
43
+ lastResult.closeSession ||
44
+ lastResult.switchUser ||
45
+ lastResult.nextCwd
46
+ ) {
29
47
  break;
30
48
  }
31
49
  }
@@ -48,7 +66,15 @@ export async function executeStatements(
48
66
 
49
67
  while (i < statements.length) {
50
68
  const stmt = statements[i]!;
51
- last = await executePipeline(stmt.pipeline, authUser, hostname, mode, cwd, shell, env);
69
+ last = await executePipeline(
70
+ stmt.pipeline,
71
+ authUser,
72
+ hostname,
73
+ mode,
74
+ cwd,
75
+ shell,
76
+ env,
77
+ );
52
78
  env.lastExitCode = last.exitCode ?? 0;
53
79
 
54
80
  if (last.closeSession || last.switchUser) return last;
@@ -83,7 +109,8 @@ export async function executePipeline(
83
109
  shell: VirtualShell,
84
110
  env?: ShellEnv,
85
111
  ): Promise<CommandResult> {
86
- if (!pipeline.isValid) return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
112
+ if (!pipeline.isValid)
113
+ return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
87
114
  if (pipeline.commands.length === 0) return { exitCode: 0 };
88
115
 
89
116
  const shellEnv: ShellEnv = env ?? { vars: {}, lastExitCode: 0 };
@@ -91,13 +118,23 @@ export async function executePipeline(
91
118
  if (pipeline.commands.length === 1) {
92
119
  return executeSingleCommandWithRedirections(
93
120
  pipeline.commands[0] as PipelineCommand,
94
- authUser, hostname, mode, cwd, shell, shellEnv,
121
+ authUser,
122
+ hostname,
123
+ mode,
124
+ cwd,
125
+ shell,
126
+ shellEnv,
95
127
  );
96
128
  }
97
129
 
98
130
  return executePipelineChain(
99
131
  pipeline.commands as PipelineCommand[],
100
- authUser, hostname, mode, cwd, shell, shellEnv,
132
+ authUser,
133
+ hostname,
134
+ mode,
135
+ cwd,
136
+ shell,
137
+ shellEnv,
101
138
  );
102
139
  }
103
140
 
@@ -113,26 +150,51 @@ async function executeSingleCommandWithRedirections(
113
150
  let stdin: string | undefined;
114
151
  if (cmd.inputFile) {
115
152
  const inputPath = resolvePath(cwd, cmd.inputFile);
116
- try { stdin = shell.vfs.readFile(inputPath); }
117
- catch { return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 }; }
153
+ try {
154
+ stdin = shell.vfs.readFile(inputPath);
155
+ } catch {
156
+ return {
157
+ stderr: `${cmd.inputFile}: No such file or directory`,
158
+ exitCode: 1,
159
+ };
160
+ }
118
161
  }
119
162
 
120
- const rawInput = [cmd.name, ...cmd.args].join(" ");
121
- const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin, env);
163
+ const result = await runCommandDirect(
164
+ cmd.name,
165
+ cmd.args,
166
+ authUser,
167
+ hostname,
168
+ mode,
169
+ cwd,
170
+ shell,
171
+ stdin,
172
+ env,
173
+ );
122
174
 
123
175
  if (cmd.outputFile) {
124
176
  const outputPath = resolvePath(cwd, cmd.outputFile);
125
177
  const output = result.stdout || "";
126
178
  try {
127
179
  if (cmd.appendOutput) {
128
- const existing = (() => { try { return shell.vfs.readFile(outputPath); } catch { return ""; } })();
180
+ const existing = (() => {
181
+ try {
182
+ return shell.vfs.readFile(outputPath);
183
+ } catch {
184
+ return "";
185
+ }
186
+ })();
129
187
  shell.writeFileAsUser(authUser, outputPath, existing + output);
130
188
  } else {
131
189
  shell.writeFileAsUser(authUser, outputPath, output);
132
190
  }
133
191
  return { ...result, stdout: "" };
134
192
  } catch {
135
- return { ...result, stderr: `Failed to write to ${cmd.outputFile}`, exitCode: 1 };
193
+ return {
194
+ ...result,
195
+ stderr: `Failed to write to ${cmd.outputFile}`,
196
+ exitCode: 1,
197
+ };
136
198
  }
137
199
  }
138
200
 
@@ -156,12 +218,27 @@ async function executePipelineChain(
156
218
 
157
219
  if (i === 0 && cmd.inputFile) {
158
220
  const inputPath = resolvePath(cwd, cmd.inputFile);
159
- try { currentOutput = shell.vfs.readFile(inputPath); }
160
- catch { return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 }; }
221
+ try {
222
+ currentOutput = shell.vfs.readFile(inputPath);
223
+ } catch {
224
+ return {
225
+ stderr: `${cmd.inputFile}: No such file or directory`,
226
+ exitCode: 1,
227
+ };
228
+ }
161
229
  }
162
230
 
163
- const rawInput = [cmd.name, ...cmd.args].join(" ");
164
- const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, currentOutput, env);
231
+ const result = await runCommandDirect(
232
+ cmd.name,
233
+ cmd.args,
234
+ authUser,
235
+ hostname,
236
+ mode,
237
+ cwd,
238
+ shell,
239
+ currentOutput,
240
+ env,
241
+ );
165
242
  exitCode = result.exitCode ?? 0;
166
243
 
167
244
  if (i === commands.length - 1 && cmd.outputFile) {
@@ -169,7 +246,13 @@ async function executePipelineChain(
169
246
  const output = result.stdout || "";
170
247
  try {
171
248
  if (cmd.appendOutput) {
172
- const existing = (() => { try { return shell.vfs.readFile(outputPath); } catch { return ""; } })();
249
+ const existing = (() => {
250
+ try {
251
+ return shell.vfs.readFile(outputPath);
252
+ } catch {
253
+ return "";
254
+ }
255
+ })();
173
256
  shell.writeFileAsUser(authUser, outputPath, existing + output);
174
257
  } else {
175
258
  shell.writeFileAsUser(authUser, outputPath, output);
@@ -182,7 +265,8 @@ async function executePipelineChain(
182
265
  currentOutput = result.stdout || "";
183
266
  }
184
267
 
185
- if (result.stderr && exitCode !== 0) return { stderr: result.stderr, exitCode };
268
+ if (result.stderr && exitCode !== 0)
269
+ return { stderr: result.stderr, exitCode };
186
270
  if (result.closeSession || result.switchUser) return result;
187
271
  }
188
272
 
@@ -140,7 +140,11 @@ class SshMimic extends EventEmitter {
140
140
 
141
141
  // Rate-limit check
142
142
  if (this.isLockedOut(remoteAddress)) {
143
- this.emit("auth:failure", { username: candidateUser, remoteAddress, reason: "lockout" });
143
+ this.emit("auth:failure", {
144
+ username: candidateUser,
145
+ remoteAddress,
146
+ reason: "lockout",
147
+ });
144
148
  ctx.reject();
145
149
  return;
146
150
  }
@@ -152,7 +156,10 @@ class SshMimic extends EventEmitter {
152
156
  `User ${candidateUser} has no password set, allowing login without verification`,
153
157
  );
154
158
  authUser = candidateUser;
155
- sessionId = shell.users.registerSession(authUser, remoteAddress).id;
159
+ sessionId = shell.users.registerSession(
160
+ authUser,
161
+ remoteAddress,
162
+ ).id;
156
163
  this.recordSuccess(remoteAddress);
157
164
  this.emit("auth:success", { username: authUser, remoteAddress });
158
165
  this.ensureHomeDir(authUser);
@@ -166,7 +173,10 @@ class SshMimic extends EventEmitter {
166
173
  !shell.users.verifyPassword(candidateUser, ctx.password)
167
174
  ) {
168
175
  this.recordFailure(remoteAddress);
169
- this.emit("auth:failure", { username: candidateUser, remoteAddress });
176
+ this.emit("auth:failure", {
177
+ username: candidateUser,
178
+ remoteAddress,
179
+ });
170
180
  ctx.reject();
171
181
  return;
172
182
  }
@@ -192,13 +202,16 @@ class SshMimic extends EventEmitter {
192
202
  const incomingKey = ctx.key;
193
203
  const keyMatches = authorizedKeys.some(
194
204
  (k) =>
195
- k.algo === incomingKey.algo &&
196
- k.data.equals(incomingKey.data),
205
+ k.algo === incomingKey.algo && k.data.equals(incomingKey.data),
197
206
  );
198
207
 
199
208
  if (!keyMatches) {
200
209
  this.recordFailure(remoteAddress);
201
- this.emit("auth:failure", { username: candidateUser, remoteAddress, method: "publickey" });
210
+ this.emit("auth:failure", {
211
+ username: candidateUser,
212
+ remoteAddress,
213
+ method: "publickey",
214
+ });
202
215
  ctx.reject();
203
216
  return;
204
217
  }
@@ -206,9 +219,16 @@ class SshMimic extends EventEmitter {
206
219
  // Key matched — if this is a signature check step, accept
207
220
  if (ctx.signature) {
208
221
  authUser = candidateUser;
209
- sessionId = shell.users.registerSession(authUser, remoteAddress).id;
222
+ sessionId = shell.users.registerSession(
223
+ authUser,
224
+ remoteAddress,
225
+ ).id;
210
226
  this.recordSuccess(remoteAddress);
211
- this.emit("auth:success", { username: authUser, remoteAddress, method: "publickey" });
227
+ this.emit("auth:success", {
228
+ username: authUser,
229
+ remoteAddress,
230
+ method: "publickey",
231
+ });
212
232
  this.ensureHomeDir(authUser);
213
233
  ctx.accept();
214
234
  } else {
@@ -238,20 +258,35 @@ class SshMimic extends EventEmitter {
238
258
  acceptPty();
239
259
  });
240
260
 
241
- session.on("window-change", (_acceptChange, _rejectChange, info) => {
242
- terminalSize.cols = info?.cols ?? terminalSize.cols;
243
- terminalSize.rows = info?.rows ?? terminalSize.rows;
244
- });
261
+ session.on(
262
+ "window-change",
263
+ (_acceptChange, _rejectChange, info) => {
264
+ terminalSize.cols = info?.cols ?? terminalSize.cols;
265
+ terminalSize.rows = info?.rows ?? terminalSize.rows;
266
+ },
267
+ );
245
268
 
246
269
  session.on("shell", (acceptShell) => {
247
270
  const stream = acceptShell();
248
- shell?.startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize);
271
+ shell?.startInteractiveSession(
272
+ stream,
273
+ authUser,
274
+ sessionId,
275
+ remoteAddress,
276
+ terminalSize,
277
+ );
249
278
  });
250
279
 
251
280
  session.on("exec", (acceptExec, _rejectExec, info) => {
252
281
  const stream = acceptExec();
253
282
  if (stream) {
254
- runExec(stream, info.command.trim(), authUser, shell.hostname, shell);
283
+ runExec(
284
+ stream,
285
+ info.command.trim(),
286
+ authUser,
287
+ shell.hostname,
288
+ shell,
289
+ );
255
290
  }
256
291
  });
257
292
  });
@@ -293,4 +328,3 @@ class SshMimic extends EventEmitter {
293
328
 
294
329
  export { SftpMimic } from "./sftp";
295
330
  export { SshMimic };
296
-
@@ -31,19 +31,25 @@
31
31
  * Binary pack : ~1.00 MB + ~40 bytes/node header → ~27% smaller, no string parsing
32
32
  */
33
33
 
34
- import type { InternalDirectoryNode, InternalFileNode, InternalNode } from "./internalTypes";
34
+ import type {
35
+ InternalDirectoryNode,
36
+ InternalFileNode,
37
+ InternalNode,
38
+ } from "./internalTypes";
35
39
 
36
40
  const MAGIC = Buffer.from([0x56, 0x46, 0x53, 0x21]); // "VFS!"
37
41
  const VERSION = 0x01;
38
42
  const TYPE_FILE = 0x01;
39
- const TYPE_DIR = 0x02;
43
+ const TYPE_DIR = 0x02;
40
44
 
41
45
  // ── Encoder ───────────────────────────────────────────────────────────────────
42
46
 
43
47
  class Encoder {
44
48
  private chunks: Buffer[] = [];
45
49
 
46
- write(buf: Buffer): void { this.chunks.push(buf); }
50
+ write(buf: Buffer): void {
51
+ this.chunks.push(buf);
52
+ }
47
53
 
48
54
  writeUint8(n: number): void {
49
55
  const b = Buffer.allocUnsafe(1);
@@ -80,7 +86,9 @@ class Encoder {
80
86
  this.chunks.push(bytes);
81
87
  }
82
88
 
83
- toBuffer(): Buffer { return Buffer.concat(this.chunks); }
89
+ toBuffer(): Buffer {
90
+ return Buffer.concat(this.chunks);
91
+ }
84
92
  }
85
93
 
86
94
  function encodeNode(enc: Encoder, node: InternalNode): void {
@@ -124,7 +132,9 @@ class Decoder {
124
132
  private pos = 0;
125
133
  constructor(private readonly buf: Buffer) {}
126
134
 
127
- readUint8(): number { return this.buf.readUInt8(this.pos++); }
135
+ readUint8(): number {
136
+ return this.buf.readUInt8(this.pos++);
137
+ }
128
138
 
129
139
  readUint16(): number {
130
140
  const v = this.buf.readUInt16LE(this.pos);
@@ -158,7 +168,9 @@ class Decoder {
158
168
  return b;
159
169
  }
160
170
 
161
- remaining(): number { return this.buf.length - this.pos; }
171
+ remaining(): number {
172
+ return this.buf.length - this.pos;
173
+ }
162
174
  }
163
175
 
164
176
  function decodeNode(dec: Decoder): InternalNode {
@@ -171,7 +183,15 @@ function decodeNode(dec: Decoder): InternalNode {
171
183
  if (type === TYPE_FILE) {
172
184
  const compressed = dec.readUint8() === 0x01;
173
185
  const content = dec.readBytes();
174
- return { type: "file", name, mode, createdAt, updatedAt, compressed, content } satisfies InternalFileNode;
186
+ return {
187
+ type: "file",
188
+ name,
189
+ mode,
190
+ createdAt,
191
+ updatedAt,
192
+ compressed,
193
+ content,
194
+ } satisfies InternalFileNode;
175
195
  }
176
196
 
177
197
  if (type === TYPE_DIR) {
@@ -181,7 +201,14 @@ function decodeNode(dec: Decoder): InternalNode {
181
201
  const child = decodeNode(dec);
182
202
  children.set(child.name, child);
183
203
  }
184
- return { type: "directory", name, mode, createdAt, updatedAt, children } satisfies InternalDirectoryNode;
204
+ return {
205
+ type: "directory",
206
+ name,
207
+ mode,
208
+ createdAt,
209
+ updatedAt,
210
+ children,
211
+ } satisfies InternalDirectoryNode;
185
212
  }
186
213
 
187
214
  throw new Error(`[VFS binary] Unknown node type: 0x${type.toString(16)}`);
@@ -172,7 +172,9 @@ class VirtualFileSystem extends EventEmitter {
172
172
  // Legacy JSON fallback — auto-migrates on next flushMirror()
173
173
  const snapshot: VfsSnapshot = JSON.parse(raw.toString("utf8"));
174
174
  this.root = this.deserializeDir(snapshot.root, "");
175
- console.info("[VirtualFileSystem] Migrating legacy JSON snapshot to binary format.");
175
+ console.info(
176
+ "[VirtualFileSystem] Migrating legacy JSON snapshot to binary format.",
177
+ );
176
178
  }
177
179
  this.emit("snapshot:restore", { path: this.snapshotFile });
178
180
  } catch (err) {
@@ -220,7 +222,11 @@ class VirtualFileSystem extends EventEmitter {
220
222
  public mkdir(targetPath: string, mode: number = 0o755): void {
221
223
  const normalized = normalizePath(targetPath);
222
224
  const existing = (() => {
223
- try { return getNode(this.root, normalized); } catch { return null; }
225
+ try {
226
+ return getNode(this.root, normalized);
227
+ } catch {
228
+ return null;
229
+ }
224
230
  })();
225
231
  if (existing && existing.type !== "directory") {
226
232
  throw new Error(
@@ -311,7 +317,9 @@ class VirtualFileSystem extends EventEmitter {
311
317
  try {
312
318
  getNode(this.root, normalizePath(targetPath));
313
319
  return true;
314
- } catch { return false; }
320
+ } catch {
321
+ return false;
322
+ }
315
323
  }
316
324
 
317
325
  /** Updates mode bits on a node. */
@@ -327,15 +335,24 @@ class VirtualFileSystem extends EventEmitter {
327
335
  if (node.type === "file") {
328
336
  const f = node as InternalFileNode;
329
337
  return {
330
- type: "file", name, path: normalized, mode: f.mode,
331
- createdAt: f.createdAt, updatedAt: f.updatedAt,
332
- compressed: f.compressed, size: f.content.length,
338
+ type: "file",
339
+ name,
340
+ path: normalized,
341
+ mode: f.mode,
342
+ createdAt: f.createdAt,
343
+ updatedAt: f.updatedAt,
344
+ compressed: f.compressed,
345
+ size: f.content.length,
333
346
  };
334
347
  }
335
348
  const d = node as InternalDirectoryNode;
336
349
  return {
337
- type: "directory", name, path: normalized, mode: d.mode,
338
- createdAt: d.createdAt, updatedAt: d.updatedAt,
350
+ type: "directory",
351
+ name,
352
+ path: normalized,
353
+ mode: d.mode,
354
+ createdAt: d.createdAt,
355
+ updatedAt: d.updatedAt,
339
356
  childrenCount: d.children.size,
340
357
  };
341
358
  }
@@ -355,9 +372,7 @@ class VirtualFileSystem extends EventEmitter {
355
372
  const normalized = normalizePath(dirPath);
356
373
  const node = getNode(this.root, normalized);
357
374
  if (node.type !== "directory") {
358
- throw new Error(
359
- `Cannot render tree for '${dirPath}': not a directory.`,
360
- );
375
+ throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
361
376
  }
362
377
  const label = dirPath === "/" ? "/" : path.posix.basename(normalized);
363
378
  return this.renderTreeLines(node as InternalDirectoryNode, label);
@@ -375,7 +390,8 @@ class VirtualFileSystem extends EventEmitter {
375
390
  lines.push(`${connector}${name}`);
376
391
  if (child.type === "directory") {
377
392
  const sub = this.renderTreeLines(child as InternalDirectoryNode, "")
378
- .split("\n").slice(1)
393
+ .split("\n")
394
+ .slice(1)
379
395
  .map((l) => `${nextPrefix}${l}`);
380
396
  lines.push(...sub);
381
397
  }
@@ -400,7 +416,8 @@ class VirtualFileSystem extends EventEmitter {
400
416
  /** Compresses a file's content with gzip in place. */
401
417
  public compressFile(targetPath: string): void {
402
418
  const node = getNode(this.root, normalizePath(targetPath));
403
- if (node.type !== "file") throw new Error(`Cannot compress '${targetPath}': not a file.`);
419
+ if (node.type !== "file")
420
+ throw new Error(`Cannot compress '${targetPath}': not a file.`);
404
421
  const f = node as InternalFileNode;
405
422
  if (!f.compressed) {
406
423
  f.content = gzipSync(f.content);
@@ -412,7 +429,8 @@ class VirtualFileSystem extends EventEmitter {
412
429
  /** Decompresses a gzip-compressed file in place. */
413
430
  public decompressFile(targetPath: string): void {
414
431
  const node = getNode(this.root, normalizePath(targetPath));
415
- if (node.type !== "file") throw new Error(`Cannot decompress '${targetPath}': not a file.`);
432
+ if (node.type !== "file")
433
+ throw new Error(`Cannot decompress '${targetPath}': not a file.`);
416
434
  const f = node as InternalFileNode;
417
435
  if (f.compressed) {
418
436
  f.content = gunzipSync(f.content);
@@ -431,18 +449,25 @@ class VirtualFileSystem extends EventEmitter {
431
449
  ? normalizePath(targetPath)
432
450
  : targetPath;
433
451
  const { parent, name } = getParentDirectory(
434
- this.root, normalizedLink, true,
452
+ this.root,
453
+ normalizedLink,
454
+ true,
435
455
  (p) => this.mkdirRecursive(p, 0o755),
436
456
  );
437
457
  const symNode: InternalFileNode = {
438
- type: "file", name,
458
+ type: "file",
459
+ name,
439
460
  content: Buffer.from(normalizedTarget, "utf8"),
440
461
  mode: 0o120777,
441
462
  compressed: false,
442
- createdAt: new Date(), updatedAt: new Date(),
463
+ createdAt: new Date(),
464
+ updatedAt: new Date(),
443
465
  };
444
466
  parent.children.set(name, symNode);
445
- this.emit("symlink:create", { link: normalizedLink, target: normalizedTarget });
467
+ this.emit("symlink:create", {
468
+ link: normalizedLink,
469
+ target: normalizedTarget,
470
+ });
446
471
  }
447
472
 
448
473
  /** Returns true when the path is a symbolic link node. */
@@ -450,7 +475,9 @@ class VirtualFileSystem extends EventEmitter {
450
475
  try {
451
476
  const node = getNode(this.root, normalizePath(targetPath));
452
477
  return node.type === "file" && node.mode === 0o120777;
453
- } catch { return false; }
478
+ } catch {
479
+ return false;
480
+ }
454
481
  }
455
482
 
456
483
  /**
@@ -466,10 +493,14 @@ class VirtualFileSystem extends EventEmitter {
466
493
  const target = (node as InternalFileNode).content.toString("utf8");
467
494
  current = target.startsWith("/")
468
495
  ? target
469
- : normalizePath(path.posix.join(path.posix.dirname(current), target));
496
+ : normalizePath(
497
+ path.posix.join(path.posix.dirname(current), target),
498
+ );
470
499
  continue;
471
500
  }
472
- } catch { break; }
501
+ } catch {
502
+ break;
503
+ }
473
504
  return current;
474
505
  }
475
506
  throw new Error(`Too many levels of symbolic links: ${linkPath}`);
@@ -488,7 +519,12 @@ class VirtualFileSystem extends EventEmitter {
488
519
  );
489
520
  }
490
521
  }
491
- const { parent, name } = getParentDirectory(this.root, normalized, false, () => {});
522
+ const { parent, name } = getParentDirectory(
523
+ this.root,
524
+ normalized,
525
+ false,
526
+ () => {},
527
+ );
492
528
  parent.children.delete(name);
493
529
  this.emit("node:remove", { path: normalized });
494
530
  }
@@ -506,10 +542,16 @@ class VirtualFileSystem extends EventEmitter {
506
542
  }
507
543
  this.mkdirRecursive(path.posix.dirname(toNormalized), 0o755);
508
544
  const { parent: destParent, name: destName } = getParentDirectory(
509
- this.root, toNormalized, false, () => {},
545
+ this.root,
546
+ toNormalized,
547
+ false,
548
+ () => {},
510
549
  );
511
550
  const { parent: srcParent, name: srcName } = getParentDirectory(
512
- this.root, fromNormalized, false, () => {},
551
+ this.root,
552
+ fromNormalized,
553
+ false,
554
+ () => {},
513
555
  );
514
556
  srcParent.children.delete(srcName);
515
557
  node.name = destName;
@@ -538,7 +580,9 @@ class VirtualFileSystem extends EventEmitter {
538
580
  );
539
581
  }
540
582
  return {
541
- type: "directory", name: dir.name, mode: dir.mode,
583
+ type: "directory",
584
+ name: dir.name,
585
+ mode: dir.mode,
542
586
  createdAt: dir.createdAt.toISOString(),
543
587
  updatedAt: dir.updatedAt.toISOString(),
544
588
  children,
@@ -547,7 +591,9 @@ class VirtualFileSystem extends EventEmitter {
547
591
 
548
592
  private serializeFile(file: InternalFileNode): VfsSnapshotFileNode {
549
593
  return {
550
- type: "file", name: file.name, mode: file.mode,
594
+ type: "file",
595
+ name: file.name,
596
+ mode: file.mode,
551
597
  createdAt: file.createdAt.toISOString(),
552
598
  updatedAt: file.updatedAt.toISOString(),
553
599
  compressed: file.compressed,
@@ -588,7 +634,9 @@ class VirtualFileSystem extends EventEmitter {
588
634
  name: string,
589
635
  ): InternalDirectoryNode {
590
636
  const dir: InternalDirectoryNode = {
591
- type: "directory", name, mode: snap.mode,
637
+ type: "directory",
638
+ name,
639
+ mode: snap.mode,
592
640
  createdAt: new Date(snap.createdAt),
593
641
  updatedAt: new Date(snap.updatedAt),
594
642
  children: new Map(),
@@ -597,7 +645,9 @@ class VirtualFileSystem extends EventEmitter {
597
645
  if (child.type === "file") {
598
646
  const f = child as VfsSnapshotFileNode;
599
647
  dir.children.set(f.name, {
600
- type: "file", name: f.name, mode: f.mode,
648
+ type: "file",
649
+ name: f.name,
650
+ mode: f.mode,
601
651
  createdAt: new Date(f.createdAt),
602
652
  updatedAt: new Date(f.updatedAt),
603
653
  compressed: f.compressed,