typescript-virtual-container 1.1.0 → 1.1.1-c

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 (229) hide show
  1. package/.vscode/settings.json +18 -0
  2. package/README.md +45 -5
  3. package/dist/SSHClient/index.d.ts +138 -0
  4. package/dist/SSHClient/index.d.ts.map +1 -0
  5. package/dist/SSHClient/index.js +216 -0
  6. package/dist/SSHMimic/exec.d.ts +4 -0
  7. package/dist/SSHMimic/exec.d.ts.map +1 -0
  8. package/dist/SSHMimic/exec.js +21 -0
  9. package/dist/SSHMimic/executor.d.ts +9 -0
  10. package/dist/SSHMimic/executor.d.ts.map +1 -0
  11. package/dist/SSHMimic/executor.js +131 -0
  12. package/dist/SSHMimic/hostKey.d.ts +2 -0
  13. package/dist/SSHMimic/hostKey.d.ts.map +1 -0
  14. package/dist/SSHMimic/hostKey.js +17 -0
  15. package/dist/SSHMimic/index.d.ts +39 -0
  16. package/dist/SSHMimic/index.d.ts.map +1 -0
  17. package/dist/SSHMimic/index.js +113 -0
  18. package/dist/SSHMimic/loginFormat.d.ts +2 -0
  19. package/dist/SSHMimic/loginFormat.d.ts.map +1 -0
  20. package/dist/SSHMimic/loginFormat.js +10 -0
  21. package/dist/SSHMimic/prompt.d.ts +2 -0
  22. package/dist/SSHMimic/prompt.d.ts.map +1 -0
  23. package/dist/SSHMimic/prompt.js +9 -0
  24. package/dist/VirtualFileSystem/archive.d.ts +5 -0
  25. package/dist/VirtualFileSystem/archive.d.ts.map +1 -0
  26. package/dist/VirtualFileSystem/archive.js +56 -0
  27. package/dist/VirtualFileSystem/index.d.ts +131 -0
  28. package/dist/VirtualFileSystem/index.d.ts.map +1 -0
  29. package/dist/VirtualFileSystem/index.js +355 -0
  30. package/dist/VirtualFileSystem/internalTypes.d.ts +18 -0
  31. package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -0
  32. package/dist/VirtualFileSystem/internalTypes.js +0 -0
  33. package/dist/VirtualFileSystem/path.d.ts +9 -0
  34. package/dist/VirtualFileSystem/path.d.ts.map +1 -0
  35. package/dist/VirtualFileSystem/path.js +49 -0
  36. package/dist/VirtualFileSystem/snapshot.d.ts +5 -0
  37. package/dist/VirtualFileSystem/snapshot.d.ts.map +1 -0
  38. package/dist/VirtualFileSystem/snapshot.js +59 -0
  39. package/dist/VirtualFileSystem/tree.d.ts +3 -0
  40. package/dist/VirtualFileSystem/tree.d.ts.map +1 -0
  41. package/dist/VirtualFileSystem/tree.js +19 -0
  42. package/dist/VirtualShell/index.d.ts +86 -0
  43. package/dist/VirtualShell/index.d.ts.map +1 -0
  44. package/dist/VirtualShell/index.js +129 -0
  45. package/dist/VirtualShell/shell.d.ts +5 -0
  46. package/dist/VirtualShell/shell.d.ts.map +1 -0
  47. package/dist/VirtualShell/shell.js +473 -0
  48. package/dist/VirtualShell/shellParser.d.ts +4 -0
  49. package/dist/VirtualShell/shellParser.d.ts.map +1 -0
  50. package/dist/VirtualShell/shellParser.js +207 -0
  51. package/dist/VirtualUserManager/index.d.ts +168 -0
  52. package/dist/VirtualUserManager/index.d.ts.map +1 -0
  53. package/dist/VirtualUserManager/index.js +375 -0
  54. package/dist/commands/adduser.d.ts +3 -0
  55. package/dist/commands/adduser.d.ts.map +1 -0
  56. package/dist/commands/adduser.js +18 -0
  57. package/dist/commands/cat.d.ts +3 -0
  58. package/dist/commands/cat.d.ts.map +1 -0
  59. package/dist/commands/cat.js +15 -0
  60. package/dist/commands/cd.d.ts +3 -0
  61. package/dist/commands/cd.d.ts.map +1 -0
  62. package/dist/commands/cd.js +17 -0
  63. package/dist/commands/clear.d.ts +3 -0
  64. package/dist/commands/clear.d.ts.map +1 -0
  65. package/dist/commands/clear.js +5 -0
  66. package/dist/commands/command-helpers.d.ts +23 -0
  67. package/dist/commands/command-helpers.d.ts.map +1 -0
  68. package/dist/commands/command-helpers.js +139 -0
  69. package/dist/commands/curl.d.ts +3 -0
  70. package/dist/commands/curl.d.ts.map +1 -0
  71. package/dist/commands/curl.js +44 -0
  72. package/dist/commands/deluser.d.ts +3 -0
  73. package/dist/commands/deluser.d.ts.map +1 -0
  74. package/dist/commands/deluser.js +15 -0
  75. package/dist/commands/echo.d.ts +3 -0
  76. package/dist/commands/echo.d.ts.map +1 -0
  77. package/dist/commands/echo.js +22 -0
  78. package/dist/commands/env.d.ts +3 -0
  79. package/dist/commands/env.d.ts.map +1 -0
  80. package/dist/commands/env.js +18 -0
  81. package/dist/commands/exit.d.ts +3 -0
  82. package/dist/commands/exit.d.ts.map +1 -0
  83. package/dist/commands/exit.js +5 -0
  84. package/dist/commands/export.d.ts +3 -0
  85. package/dist/commands/export.d.ts.map +1 -0
  86. package/dist/commands/export.js +34 -0
  87. package/dist/commands/grep.d.ts +3 -0
  88. package/dist/commands/grep.d.ts.map +1 -0
  89. package/dist/commands/grep.js +69 -0
  90. package/dist/commands/help.d.ts +3 -0
  91. package/dist/commands/help.d.ts.map +1 -0
  92. package/dist/commands/help.js +7 -0
  93. package/dist/commands/helpers.d.ts +26 -0
  94. package/dist/commands/helpers.d.ts.map +1 -0
  95. package/dist/commands/helpers.js +160 -0
  96. package/dist/commands/hostname.d.ts +3 -0
  97. package/dist/commands/hostname.d.ts.map +1 -0
  98. package/dist/commands/hostname.js +5 -0
  99. package/dist/commands/htop.d.ts +3 -0
  100. package/dist/commands/htop.d.ts.map +1 -0
  101. package/dist/commands/htop.js +10 -0
  102. package/dist/commands/index.d.ts +8 -0
  103. package/dist/commands/index.d.ts.map +1 -0
  104. package/dist/commands/index.js +212 -0
  105. package/dist/commands/ls.d.ts +3 -0
  106. package/dist/commands/ls.d.ts.map +1 -0
  107. package/dist/commands/ls.js +47 -0
  108. package/dist/commands/mkdir.d.ts +3 -0
  109. package/dist/commands/mkdir.d.ts.map +1 -0
  110. package/dist/commands/mkdir.js +21 -0
  111. package/dist/commands/nano.d.ts +3 -0
  112. package/dist/commands/nano.d.ts.map +1 -0
  113. package/dist/commands/nano.js +27 -0
  114. package/dist/commands/neofetch.d.ts +3 -0
  115. package/dist/commands/neofetch.d.ts.map +1 -0
  116. package/dist/commands/neofetch.js +32 -0
  117. package/dist/commands/pwd.d.ts +3 -0
  118. package/dist/commands/pwd.d.ts.map +1 -0
  119. package/dist/commands/pwd.js +5 -0
  120. package/dist/commands/rm.d.ts +3 -0
  121. package/dist/commands/rm.d.ts.map +1 -0
  122. package/dist/commands/rm.js +29 -0
  123. package/dist/commands/set.d.ts +7 -0
  124. package/dist/commands/set.d.ts.map +1 -0
  125. package/dist/commands/set.js +64 -0
  126. package/dist/commands/sh.d.ts +4 -0
  127. package/dist/commands/sh.d.ts.map +1 -0
  128. package/dist/commands/sh.js +45 -0
  129. package/dist/commands/su.d.ts +3 -0
  130. package/dist/commands/su.d.ts.map +1 -0
  131. package/dist/commands/su.js +24 -0
  132. package/dist/commands/sudo.d.ts +3 -0
  133. package/dist/commands/sudo.d.ts.map +1 -0
  134. package/dist/commands/sudo.js +47 -0
  135. package/dist/commands/touch.d.ts +3 -0
  136. package/dist/commands/touch.d.ts.map +1 -0
  137. package/dist/commands/touch.js +18 -0
  138. package/dist/commands/tree.d.ts +3 -0
  139. package/dist/commands/tree.d.ts.map +1 -0
  140. package/dist/commands/tree.js +11 -0
  141. package/dist/commands/unset.d.ts +3 -0
  142. package/dist/commands/unset.d.ts.map +1 -0
  143. package/dist/commands/unset.js +15 -0
  144. package/dist/commands/wget.d.ts +3 -0
  145. package/dist/commands/wget.d.ts.map +1 -0
  146. package/dist/commands/wget.js +113 -0
  147. package/dist/commands/who.d.ts +3 -0
  148. package/dist/commands/who.d.ts.map +1 -0
  149. package/dist/commands/who.js +15 -0
  150. package/dist/commands/whoami.d.ts +3 -0
  151. package/dist/commands/whoami.d.ts.map +1 -0
  152. package/dist/commands/whoami.js +5 -0
  153. package/dist/index.d.ts +11 -0
  154. package/dist/index.d.ts.map +1 -0
  155. package/dist/index.js +7 -0
  156. package/dist/modules/neofetch.d.ts +19 -0
  157. package/dist/modules/neofetch.d.ts.map +1 -0
  158. package/dist/modules/neofetch.js +284 -0
  159. package/dist/modules/shellInteractive.d.ts +6 -0
  160. package/dist/modules/shellInteractive.d.ts.map +1 -0
  161. package/dist/modules/shellInteractive.js +26 -0
  162. package/dist/modules/shellRuntime.d.ts +11 -0
  163. package/dist/modules/shellRuntime.d.ts.map +1 -0
  164. package/dist/modules/shellRuntime.js +52 -0
  165. package/dist/standalone.d.ts +2 -0
  166. package/dist/standalone.d.ts.map +1 -0
  167. package/dist/standalone.js +25 -0
  168. package/dist/types/commands.d.ts +89 -0
  169. package/dist/types/commands.d.ts.map +1 -0
  170. package/dist/types/commands.js +0 -0
  171. package/dist/types/pipeline.d.ts +23 -0
  172. package/dist/types/pipeline.d.ts.map +1 -0
  173. package/dist/types/pipeline.js +0 -0
  174. package/dist/types/streams.d.ts +32 -0
  175. package/dist/types/streams.d.ts.map +1 -0
  176. package/dist/types/streams.js +0 -0
  177. package/dist/types/vfs.d.ts +71 -0
  178. package/dist/types/vfs.d.ts.map +1 -0
  179. package/dist/types/vfs.js +0 -0
  180. package/package.json +4 -2
  181. package/src/{SSHMimic/client.ts → SSHClient/index.ts} +2 -2
  182. package/src/SSHMimic/exec.ts +1 -1
  183. package/src/SSHMimic/executor.ts +8 -8
  184. package/src/VirtualFileSystem/index.ts +26 -0
  185. package/src/VirtualShell/index.ts +17 -1
  186. package/src/VirtualShell/shell.ts +19 -107
  187. package/src/VirtualShell/shellParser.ts +32 -7
  188. package/src/VirtualUserManager/index.ts +149 -0
  189. package/src/{VirtualShell/commands → commands}/adduser.ts +1 -1
  190. package/src/{VirtualShell/commands → commands}/cat.ts +1 -1
  191. package/src/{VirtualShell/commands → commands}/cd.ts +1 -1
  192. package/src/{VirtualShell/commands → commands}/clear.ts +1 -1
  193. package/src/{VirtualShell/commands → commands}/curl.ts +2 -2
  194. package/src/{VirtualShell/commands → commands}/deluser.ts +1 -1
  195. package/src/{VirtualShell/commands → commands}/echo.ts +1 -1
  196. package/src/{VirtualShell/commands → commands}/env.ts +1 -1
  197. package/src/{VirtualShell/commands → commands}/exit.ts +1 -1
  198. package/src/{VirtualShell/commands → commands}/export.ts +1 -1
  199. package/src/{VirtualShell/commands → commands}/grep.ts +1 -1
  200. package/src/{VirtualShell/commands → commands}/help.ts +1 -1
  201. package/src/{VirtualShell/commands → commands}/helpers.ts +1 -1
  202. package/src/{VirtualShell/commands → commands}/hostname.ts +1 -1
  203. package/src/{VirtualShell/commands → commands}/htop.ts +1 -1
  204. package/src/{VirtualShell/commands → commands}/index.ts +7 -4
  205. package/src/{VirtualShell/commands → commands}/ls.ts +1 -1
  206. package/src/{VirtualShell/commands → commands}/mkdir.ts +1 -1
  207. package/src/{VirtualShell/commands → commands}/nano.ts +1 -1
  208. package/src/{VirtualShell/commands → commands}/neofetch.ts +2 -2
  209. package/src/{VirtualShell/commands → commands}/pwd.ts +1 -1
  210. package/src/{VirtualShell/commands → commands}/rm.ts +1 -1
  211. package/src/{VirtualShell/commands → commands}/set.ts +1 -1
  212. package/src/{VirtualShell/commands → commands}/sh.ts +1 -1
  213. package/src/{VirtualShell/commands → commands}/su.ts +1 -1
  214. package/src/{VirtualShell/commands → commands}/sudo.ts +1 -1
  215. package/src/{VirtualShell/commands → commands}/touch.ts +2 -2
  216. package/src/{VirtualShell/commands → commands}/tree.ts +1 -1
  217. package/src/{VirtualShell/commands → commands}/unset.ts +1 -1
  218. package/src/{VirtualShell/commands → commands}/wget.ts +2 -2
  219. package/src/{VirtualShell/commands → commands}/who.ts +2 -2
  220. package/src/{VirtualShell/commands → commands}/whoami.ts +1 -1
  221. package/src/index.ts +2 -2
  222. package/{modules → src/modules}/neofetch.ts +56 -51
  223. package/src/modules/shellInteractive.ts +57 -0
  224. package/src/modules/shellRuntime.ts +76 -0
  225. package/tests/command-helpers.test.ts +1 -1
  226. package/tests/helpers.test.ts +1 -1
  227. package/tests/users.test.ts +60 -0
  228. package/tsconfig.json +19 -8
  229. /package/src/{VirtualShell/commands → commands}/command-helpers.ts +0 -0
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import type { ShellProperties } from "../src/VirtualShell";
4
+ import type { ShellProperties } from "../VirtualShell";
5
5
 
6
6
  function formatUptime(seconds: number): string {
7
7
  const totalMinutes = Math.max(1, Math.floor(seconds / 60));
@@ -56,11 +56,11 @@ function colorizeDetailLine(line: string): string {
56
56
  return line;
57
57
  }
58
58
 
59
- const colonIndex = line.indexOf(':');
59
+ const colonIndex = line.indexOf(":");
60
60
 
61
61
  if (colonIndex === -1) {
62
62
  // Pas de ':', chercher '@' pour identifier user@host
63
- if (line.includes('@')) {
63
+ if (line.includes("@")) {
64
64
  // C'est user@host, appliquer dégradé horizontal
65
65
  return applyHorizontalGradient(line);
66
66
  }
@@ -79,8 +79,8 @@ function colorizeDetailLine(line: string): string {
79
79
 
80
80
  function applyHorizontalGradient(text: string): string {
81
81
  // Nettoyer les codes ANSI existants
82
- const ansiRegex = new RegExp(`${String.fromCharCode(27)}\\[[\\d;]*m`, 'g');
83
- const cleaned = text.replace(ansiRegex, '');
82
+ const ansiRegex = new RegExp(`${String.fromCharCode(27)}\\[[\\d;]*m`, "g");
83
+ const cleaned = text.replace(ansiRegex, "");
84
84
 
85
85
  if (cleaned.trim().length === 0) {
86
86
  return text;
@@ -88,7 +88,7 @@ function applyHorizontalGradient(text: string): string {
88
88
 
89
89
  const start = { r: 255, g: 255, b: 255 };
90
90
  const end = { r: 168, g: 85, b: 247 };
91
- let result = '';
91
+ let result = "";
92
92
 
93
93
  for (let i = 0; i < cleaned.length; i += 1) {
94
94
  const ratio = cleaned.length <= 1 ? 0 : i / (cleaned.length - 1);
@@ -111,7 +111,7 @@ export interface NeofetchInfo {
111
111
  uptimeSeconds?: number;
112
112
  packages?: string;
113
113
  shell?: string;
114
- shellProps?: ShellProperties;
114
+ shellProps?: ShellProperties;
115
115
  resolution?: string;
116
116
  terminal?: string;
117
117
  cpu?: string;
@@ -180,8 +180,7 @@ function countDpkgPackages(): number | undefined {
180
180
  const data = readFileSync(filePath, "utf8");
181
181
  const matches = data.match(/^Package:\s+/gm);
182
182
  return matches?.length ?? 0;
183
- } catch {
184
- }
183
+ } catch {}
185
184
  }
186
185
 
187
186
  return undefined;
@@ -199,8 +198,7 @@ function countSnapPackages(): number | undefined {
199
198
  const entries = readdirSync(dirPath, { withFileTypes: true });
200
199
  const count = entries.filter((entry) => entry.isDirectory()).length;
201
200
  return count;
202
- } catch {
203
- }
201
+ } catch {}
204
202
  }
205
203
 
206
204
  return undefined;
@@ -250,28 +248,31 @@ function resolveDefaults(info: NeofetchInfo): Required<NeofetchInfo> {
250
248
  const totalMem = os.totalmem();
251
249
  const freeMem = os.freemem();
252
250
  const usedMem = Math.max(0, totalMem - freeMem);
253
- const shellProps = info.shellProps;
251
+ const shellProps = info.shellProps;
254
252
 
255
- const processUptime = process.uptime();
256
- if (info.uptimeSeconds === undefined) {
257
- info.uptimeSeconds = Math.round(processUptime);
258
- }
253
+ const processUptime = process.uptime();
254
+ if (info.uptimeSeconds === undefined) {
255
+ info.uptimeSeconds = Math.round(processUptime);
256
+ }
259
257
 
260
- console.log("Resolving neofetch info with shellProps:", shellProps);
258
+ console.log("Resolving neofetch info with shellProps:", shellProps);
261
259
 
262
260
  return {
263
261
  user: info.user,
264
262
  host: info.host,
265
- osName: shellProps?.os ?? info.osName ?? `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
263
+ osName:
264
+ shellProps?.os ??
265
+ info.osName ??
266
+ `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
266
267
  kernel: shellProps?.kernel ?? info.kernel ?? os.release(),
267
268
  uptimeSeconds: info.uptimeSeconds ?? os.uptime(),
268
269
  packages: info.packages ?? resolvePackagesLabel(),
269
270
  shell: resolveShellLabel(info.shell),
270
- shellProps: info.shellProps as ShellProperties ?? {
271
- kernel: info.kernel ?? os.release(),
272
- os: info.osName ?? `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
273
- arch: os.arch(),
274
- },
271
+ shellProps: (info.shellProps as ShellProperties) ?? {
272
+ kernel: info.kernel ?? os.release(),
273
+ os: info.osName ?? `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
274
+ arch: os.arch(),
275
+ },
275
276
  resolution: info.resolution ?? "n/a (ssh)",
276
277
  terminal: info.terminal ?? "unknown",
277
278
  cpu: info.cpu ?? resolveCpuLabel(),
@@ -287,32 +288,32 @@ export function buildNeofetchOutput(info: NeofetchInfo): string {
287
288
  const colorBars = buildColorBars();
288
289
 
289
290
  const distroLogo = [
290
- " .. .:. ",
291
- " .::.. .. .. ",
292
- ". .... ... .. ",
293
- ": .... .:. .. ",
294
- ": .:.:........:. .. ",
295
- ": .. ",
296
- ". : ",
297
- ". : ",
298
- ".. : ",
299
- " :. .. ",
300
- " .. .. ",
301
- " :-. :: ",
302
- " .:. :. ",
303
- " ..: ... ",
304
- " ..: :.. ",
305
- " :... :....",
306
- " .. ....",
307
- " . .. ",
308
- " .:. .: ",
309
- " :. .. ",
310
- " ::. .. ",
311
- "..... ..:... ",
312
- "...:. .. ",
313
- ".:...:. ::. .. ",
314
- " ... ..:::::.. ..:::::::.. ",
315
- ]
291
+ " .. .:. ",
292
+ " .::.. .. .. ",
293
+ ". .... ... .. ",
294
+ ": .... .:. .. ",
295
+ ": .:.:........:. .. ",
296
+ ": .. ",
297
+ ". : ",
298
+ ". : ",
299
+ ".. : ",
300
+ " :. .. ",
301
+ " .. .. ",
302
+ " :-. :: ",
303
+ " .:. :. ",
304
+ " ..: ... ",
305
+ " ..: :.. ",
306
+ " :... :....",
307
+ " .. ....",
308
+ " . .. ",
309
+ " .:. .: ",
310
+ " :. .. ",
311
+ " ::. .. ",
312
+ "..... ..:... ",
313
+ "...:. .. ",
314
+ ".:...:. ::. .. ",
315
+ " ... ..:::::.. ..:::::::.. ",
316
+ ];
316
317
 
317
318
  const details = [
318
319
  `${fields.user}@${fields.host}`,
@@ -341,7 +342,11 @@ export function buildNeofetchOutput(info: NeofetchInfo): string {
341
342
  const rawLeft = distroLogo[i] ?? "";
342
343
  const right = details[i] ?? "";
343
344
  if (right.length > 0) {
344
- const left = colorizeLogoLine(rawLeft.padEnd(31, " "), i, distroLogo.length);
345
+ const left = colorizeLogoLine(
346
+ rawLeft.padEnd(31, " "),
347
+ i,
348
+ distroLogo.length,
349
+ );
345
350
  const coloredRight = colorizeDetailLine(right);
346
351
  lines.push(`${left} ${coloredRight}`);
347
352
  continue;
@@ -351,4 +356,4 @@ export function buildNeofetchOutput(info: NeofetchInfo): string {
351
356
  }
352
357
 
353
358
  return lines.join("\n");
354
- }
359
+ }
@@ -0,0 +1,57 @@
1
+ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
+ import type { ShellStream } from "../types/streams";
3
+ import {
4
+ shellQuote,
5
+ type TerminalSize,
6
+ withTerminalSize,
7
+ } from "./shellRuntime";
8
+
9
+ function spawnScriptProcess(
10
+ command: string,
11
+ terminalSize: TerminalSize,
12
+ stream: ShellStream,
13
+ ): ChildProcessWithoutNullStreams {
14
+ const formatted = withTerminalSize(command, terminalSize);
15
+ const proc = spawn("script", ["-qfec", formatted, "/dev/null"], {
16
+ stdio: ["pipe", "pipe", "pipe"],
17
+ env: {
18
+ ...process.env,
19
+ // biome-ignore lint/style/useNamingConvention: env variable should be uppercase
20
+ TERM: process.env.TERM ?? "xterm-256color",
21
+ },
22
+ });
23
+
24
+ proc.stdout.on("data", (data: Buffer) => {
25
+ stream.write(data.toString("utf8"));
26
+ });
27
+
28
+ proc.stderr.on("data", (data: Buffer) => {
29
+ stream.write(data.toString("utf8"));
30
+ });
31
+
32
+ return proc;
33
+ }
34
+
35
+ export function spawnNanoEditorProcess(
36
+ tempPath: string,
37
+ terminalSize: TerminalSize,
38
+ stream: ShellStream,
39
+ ): ChildProcessWithoutNullStreams {
40
+ return spawnScriptProcess(
41
+ `nano -- ${shellQuote(tempPath)}`,
42
+ terminalSize,
43
+ stream,
44
+ );
45
+ }
46
+
47
+ export function spawnHtopProcess(
48
+ pidList: string,
49
+ terminalSize: TerminalSize,
50
+ stream: ShellStream,
51
+ ): ChildProcessWithoutNullStreams {
52
+ return spawnScriptProcess(
53
+ `htop -p ${shellQuote(pidList)}`,
54
+ terminalSize,
55
+ stream,
56
+ );
57
+ }
@@ -0,0 +1,76 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import * as path from "node:path";
3
+
4
+ export interface TerminalSize {
5
+ cols: number;
6
+ rows: number;
7
+ }
8
+
9
+ export function shellQuote(value: string): string {
10
+ return `'${value.replace(/'/g, `'\\''`)}'`;
11
+ }
12
+
13
+ export function toTtyLines(text: string): string {
14
+ return text
15
+ .replace(/\r\n/g, "\n")
16
+ .replace(/\r/g, "\n")
17
+ .replace(/\n/g, "\r\n");
18
+ }
19
+
20
+ export function withTerminalSize(
21
+ command: string,
22
+ terminalSize: TerminalSize,
23
+ ): string {
24
+ const cols =
25
+ Number.isFinite(terminalSize.cols) && terminalSize.cols > 0
26
+ ? Math.floor(terminalSize.cols)
27
+ : 80;
28
+ const rows =
29
+ Number.isFinite(terminalSize.rows) && terminalSize.rows > 0
30
+ ? Math.floor(terminalSize.rows)
31
+ : 24;
32
+ return `stty cols ${cols} rows ${rows} 2>/dev/null; ${command}`;
33
+ }
34
+
35
+ export function resolvePath(base: string, inputPath: string): string {
36
+ if (!inputPath || inputPath.trim() === "" || inputPath === ".") {
37
+ return base;
38
+ }
39
+ return inputPath.startsWith("/")
40
+ ? path.posix.normalize(inputPath)
41
+ : path.posix.normalize(path.posix.join(base, inputPath));
42
+ }
43
+
44
+ export async function collectChildPids(parentPid: number): Promise<number[]> {
45
+ try {
46
+ const childrenRaw = await readFile(
47
+ `/proc/${parentPid}/task/${parentPid}/children`,
48
+ "utf8",
49
+ );
50
+ const directChildren = childrenRaw
51
+ .trim()
52
+ .split(/\s+/)
53
+ .filter(Boolean)
54
+ .map((value) => Number.parseInt(value, 10))
55
+ .filter((pid) => Number.isInteger(pid) && pid > 0);
56
+
57
+ const nested = await Promise.all(
58
+ directChildren.map((pid) => collectChildPids(pid)),
59
+ );
60
+ return [...directChildren, ...nested.flat()];
61
+ } catch {
62
+ return [];
63
+ }
64
+ }
65
+
66
+ export async function getVisibleHtopPidList(
67
+ rootPid = process.pid,
68
+ ): Promise<string | null> {
69
+ const descendants = await collectChildPids(rootPid);
70
+ const unique = Array.from(new Set(descendants)).sort((a, b) => a - b);
71
+ if (unique.length === 0) {
72
+ return null;
73
+ }
74
+
75
+ return unique.join(",");
76
+ }
@@ -3,7 +3,7 @@ import {
3
3
  getArg,
4
4
  getFlag,
5
5
  ifFlag,
6
- } from "../src/VirtualShell/commands/command-helpers";
6
+ } from "../src/commands/command-helpers";
7
7
 
8
8
  describe("command-helpers", () => {
9
9
  test("ifFlag detects plain and inline flag forms", () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { assertPathAccess } from "../src/VirtualShell/commands/helpers";
2
+ import { assertPathAccess } from "../src/commands/helpers";
3
3
 
4
4
  describe("assertPathAccess", () => {
5
5
  test("blocks non-root access to auth store", () => {
@@ -39,3 +39,63 @@ describe("VirtualUserManager auto sudo", () => {
39
39
  });
40
40
  });
41
41
  });
42
+
43
+ describe("VirtualUserManager quotas", () => {
44
+ test("enforces quota for writes inside user home", async () => {
45
+ await withTempVfs(async (vfs) => {
46
+ const users = new VirtualUserManager(vfs, "root-pass");
47
+ await users.initialize();
48
+ await users.addUser("alice", "alice-pass");
49
+ const startingUsage = users.getUsageBytes("alice");
50
+ await users.setQuotaBytes("alice", startingUsage + 5);
51
+
52
+ expect(() => {
53
+ users.assertWriteWithinQuota("alice", "/home/alice/note.txt", "hello");
54
+ }).not.toThrow();
55
+
56
+ vfs.writeFile("/home/alice/note.txt", "hello");
57
+
58
+ expect(() => {
59
+ users.assertWriteWithinQuota(
60
+ "alice",
61
+ "/home/alice/note.txt",
62
+ "this exceeds the configured quota",
63
+ );
64
+ }).toThrow("quota exceeded for 'alice'");
65
+ });
66
+ });
67
+
68
+ test("does not enforce home quota outside user home", async () => {
69
+ await withTempVfs(async (vfs) => {
70
+ const users = new VirtualUserManager(vfs, "root-pass");
71
+ await users.initialize();
72
+ await users.addUser("bob", "bob-pass");
73
+ await users.setQuotaBytes("bob", 1);
74
+
75
+ expect(() => {
76
+ users.assertWriteWithinQuota("bob", "/tmp/shared.txt", "large-content");
77
+ }).not.toThrow();
78
+ });
79
+ });
80
+
81
+ test("clearQuota removes enforced limit", async () => {
82
+ await withTempVfs(async (vfs) => {
83
+ const users = new VirtualUserManager(vfs, "root-pass");
84
+ await users.initialize();
85
+ await users.addUser("charlie", "charlie-pass");
86
+ await users.setQuotaBytes("charlie", 2);
87
+
88
+ expect(users.getQuotaBytes("charlie")).toBe(2);
89
+ await users.clearQuota("charlie");
90
+ expect(users.getQuotaBytes("charlie")).toBeNull();
91
+
92
+ expect(() => {
93
+ users.assertWriteWithinQuota(
94
+ "charlie",
95
+ "/home/charlie/file.txt",
96
+ "long-content",
97
+ );
98
+ }).not.toThrow();
99
+ });
100
+ });
101
+ });
package/tsconfig.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  // Environment setup & latest features
4
- "lib": ["ESNext"],
4
+ "lib": [
5
+ "ESNext"
6
+ ],
5
7
  "target": "ESNext",
6
8
  "module": "Preserve",
7
9
  "moduleDetection": "force",
8
10
  "jsx": "react-jsx",
9
11
  "allowJs": true,
10
-
12
+
11
13
  // Bundler mode
12
14
  "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
15
  "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
16
+ "noEmit": false,
17
+
17
18
  // Best practices
18
19
  "strict": true,
19
20
  "skipLibCheck": true,
@@ -25,7 +26,17 @@
25
26
  "noUnusedLocals": false,
26
27
  "noUnusedParameters": false,
27
28
  "noPropertyAccessFromIndexSignature": false,
29
+ "types": [
30
+ "node",
31
+ "bun"
32
+ ],
28
33
 
29
- "types": ["node", "bun"]
30
- }
31
- }
34
+ "outDir": "dist",
35
+ "esModuleInterop": true,
36
+ "forceConsistentCasingInFileNames": true,
37
+ "resolveJsonModule": true,
38
+ "declaration": true,
39
+ "declarationMap": true
40
+ },
41
+ "include": ["src/**/*"]
42
+ }