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.
- package/.vscode/settings.json +18 -0
- package/README.md +45 -5
- package/dist/SSHClient/index.d.ts +138 -0
- package/dist/SSHClient/index.d.ts.map +1 -0
- package/dist/SSHClient/index.js +216 -0
- package/dist/SSHMimic/exec.d.ts +4 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -0
- package/dist/SSHMimic/exec.js +21 -0
- package/dist/SSHMimic/executor.d.ts +9 -0
- package/dist/SSHMimic/executor.d.ts.map +1 -0
- package/dist/SSHMimic/executor.js +131 -0
- package/dist/SSHMimic/hostKey.d.ts +2 -0
- package/dist/SSHMimic/hostKey.d.ts.map +1 -0
- package/dist/SSHMimic/hostKey.js +17 -0
- package/dist/SSHMimic/index.d.ts +39 -0
- package/dist/SSHMimic/index.d.ts.map +1 -0
- package/dist/SSHMimic/index.js +113 -0
- package/dist/SSHMimic/loginFormat.d.ts +2 -0
- package/dist/SSHMimic/loginFormat.d.ts.map +1 -0
- package/dist/SSHMimic/loginFormat.js +10 -0
- package/dist/SSHMimic/prompt.d.ts +2 -0
- package/dist/SSHMimic/prompt.d.ts.map +1 -0
- package/dist/SSHMimic/prompt.js +9 -0
- package/dist/VirtualFileSystem/archive.d.ts +5 -0
- package/dist/VirtualFileSystem/archive.d.ts.map +1 -0
- package/dist/VirtualFileSystem/archive.js +56 -0
- package/dist/VirtualFileSystem/index.d.ts +131 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -0
- package/dist/VirtualFileSystem/index.js +355 -0
- package/dist/VirtualFileSystem/internalTypes.d.ts +18 -0
- package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -0
- package/dist/VirtualFileSystem/internalTypes.js +0 -0
- package/dist/VirtualFileSystem/path.d.ts +9 -0
- package/dist/VirtualFileSystem/path.d.ts.map +1 -0
- package/dist/VirtualFileSystem/path.js +49 -0
- package/dist/VirtualFileSystem/snapshot.d.ts +5 -0
- package/dist/VirtualFileSystem/snapshot.d.ts.map +1 -0
- package/dist/VirtualFileSystem/snapshot.js +59 -0
- package/dist/VirtualFileSystem/tree.d.ts +3 -0
- package/dist/VirtualFileSystem/tree.d.ts.map +1 -0
- package/dist/VirtualFileSystem/tree.js +19 -0
- package/dist/VirtualShell/index.d.ts +86 -0
- package/dist/VirtualShell/index.d.ts.map +1 -0
- package/dist/VirtualShell/index.js +129 -0
- package/dist/VirtualShell/shell.d.ts +5 -0
- package/dist/VirtualShell/shell.d.ts.map +1 -0
- package/dist/VirtualShell/shell.js +473 -0
- package/dist/VirtualShell/shellParser.d.ts +4 -0
- package/dist/VirtualShell/shellParser.d.ts.map +1 -0
- package/dist/VirtualShell/shellParser.js +207 -0
- package/dist/VirtualUserManager/index.d.ts +168 -0
- package/dist/VirtualUserManager/index.d.ts.map +1 -0
- package/dist/VirtualUserManager/index.js +375 -0
- package/dist/commands/adduser.d.ts +3 -0
- package/dist/commands/adduser.d.ts.map +1 -0
- package/dist/commands/adduser.js +18 -0
- package/dist/commands/cat.d.ts +3 -0
- package/dist/commands/cat.d.ts.map +1 -0
- package/dist/commands/cat.js +15 -0
- package/dist/commands/cd.d.ts +3 -0
- package/dist/commands/cd.d.ts.map +1 -0
- package/dist/commands/cd.js +17 -0
- package/dist/commands/clear.d.ts +3 -0
- package/dist/commands/clear.d.ts.map +1 -0
- package/dist/commands/clear.js +5 -0
- package/dist/commands/command-helpers.d.ts +23 -0
- package/dist/commands/command-helpers.d.ts.map +1 -0
- package/dist/commands/command-helpers.js +139 -0
- package/dist/commands/curl.d.ts +3 -0
- package/dist/commands/curl.d.ts.map +1 -0
- package/dist/commands/curl.js +44 -0
- package/dist/commands/deluser.d.ts +3 -0
- package/dist/commands/deluser.d.ts.map +1 -0
- package/dist/commands/deluser.js +15 -0
- package/dist/commands/echo.d.ts +3 -0
- package/dist/commands/echo.d.ts.map +1 -0
- package/dist/commands/echo.js +22 -0
- package/dist/commands/env.d.ts +3 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +18 -0
- package/dist/commands/exit.d.ts +3 -0
- package/dist/commands/exit.d.ts.map +1 -0
- package/dist/commands/exit.js +5 -0
- package/dist/commands/export.d.ts +3 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +34 -0
- package/dist/commands/grep.d.ts +3 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +69 -0
- package/dist/commands/help.d.ts +3 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +7 -0
- package/dist/commands/helpers.d.ts +26 -0
- package/dist/commands/helpers.d.ts.map +1 -0
- package/dist/commands/helpers.js +160 -0
- package/dist/commands/hostname.d.ts +3 -0
- package/dist/commands/hostname.d.ts.map +1 -0
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/htop.d.ts +3 -0
- package/dist/commands/htop.d.ts.map +1 -0
- package/dist/commands/htop.js +10 -0
- package/dist/commands/index.d.ts +8 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +212 -0
- package/dist/commands/ls.d.ts +3 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +47 -0
- package/dist/commands/mkdir.d.ts +3 -0
- package/dist/commands/mkdir.d.ts.map +1 -0
- package/dist/commands/mkdir.js +21 -0
- package/dist/commands/nano.d.ts +3 -0
- package/dist/commands/nano.d.ts.map +1 -0
- package/dist/commands/nano.js +27 -0
- package/dist/commands/neofetch.d.ts +3 -0
- package/dist/commands/neofetch.d.ts.map +1 -0
- package/dist/commands/neofetch.js +32 -0
- package/dist/commands/pwd.d.ts +3 -0
- package/dist/commands/pwd.d.ts.map +1 -0
- package/dist/commands/pwd.js +5 -0
- package/dist/commands/rm.d.ts +3 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +29 -0
- package/dist/commands/set.d.ts +7 -0
- package/dist/commands/set.d.ts.map +1 -0
- package/dist/commands/set.js +64 -0
- package/dist/commands/sh.d.ts +4 -0
- package/dist/commands/sh.d.ts.map +1 -0
- package/dist/commands/sh.js +45 -0
- package/dist/commands/su.d.ts +3 -0
- package/dist/commands/su.d.ts.map +1 -0
- package/dist/commands/su.js +24 -0
- package/dist/commands/sudo.d.ts +3 -0
- package/dist/commands/sudo.d.ts.map +1 -0
- package/dist/commands/sudo.js +47 -0
- package/dist/commands/touch.d.ts +3 -0
- package/dist/commands/touch.d.ts.map +1 -0
- package/dist/commands/touch.js +18 -0
- package/dist/commands/tree.d.ts +3 -0
- package/dist/commands/tree.d.ts.map +1 -0
- package/dist/commands/tree.js +11 -0
- package/dist/commands/unset.d.ts +3 -0
- package/dist/commands/unset.d.ts.map +1 -0
- package/dist/commands/unset.js +15 -0
- package/dist/commands/wget.d.ts +3 -0
- package/dist/commands/wget.d.ts.map +1 -0
- package/dist/commands/wget.js +113 -0
- package/dist/commands/who.d.ts +3 -0
- package/dist/commands/who.d.ts.map +1 -0
- package/dist/commands/who.js +15 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/modules/neofetch.d.ts +19 -0
- package/dist/modules/neofetch.d.ts.map +1 -0
- package/dist/modules/neofetch.js +284 -0
- package/dist/modules/shellInteractive.d.ts +6 -0
- package/dist/modules/shellInteractive.d.ts.map +1 -0
- package/dist/modules/shellInteractive.js +26 -0
- package/dist/modules/shellRuntime.d.ts +11 -0
- package/dist/modules/shellRuntime.d.ts.map +1 -0
- package/dist/modules/shellRuntime.js +52 -0
- package/dist/standalone.d.ts +2 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +25 -0
- package/dist/types/commands.d.ts +89 -0
- package/dist/types/commands.d.ts.map +1 -0
- package/dist/types/commands.js +0 -0
- package/dist/types/pipeline.d.ts +23 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +0 -0
- package/dist/types/streams.d.ts +32 -0
- package/dist/types/streams.d.ts.map +1 -0
- package/dist/types/streams.js +0 -0
- package/dist/types/vfs.d.ts +71 -0
- package/dist/types/vfs.d.ts.map +1 -0
- package/dist/types/vfs.js +0 -0
- package/package.json +4 -2
- package/src/{SSHMimic/client.ts → SSHClient/index.ts} +2 -2
- package/src/SSHMimic/exec.ts +1 -1
- package/src/SSHMimic/executor.ts +8 -8
- package/src/VirtualFileSystem/index.ts +26 -0
- package/src/VirtualShell/index.ts +17 -1
- package/src/VirtualShell/shell.ts +19 -107
- package/src/VirtualShell/shellParser.ts +32 -7
- package/src/VirtualUserManager/index.ts +149 -0
- package/src/{VirtualShell/commands → commands}/adduser.ts +1 -1
- package/src/{VirtualShell/commands → commands}/cat.ts +1 -1
- package/src/{VirtualShell/commands → commands}/cd.ts +1 -1
- package/src/{VirtualShell/commands → commands}/clear.ts +1 -1
- package/src/{VirtualShell/commands → commands}/curl.ts +2 -2
- package/src/{VirtualShell/commands → commands}/deluser.ts +1 -1
- package/src/{VirtualShell/commands → commands}/echo.ts +1 -1
- package/src/{VirtualShell/commands → commands}/env.ts +1 -1
- package/src/{VirtualShell/commands → commands}/exit.ts +1 -1
- package/src/{VirtualShell/commands → commands}/export.ts +1 -1
- package/src/{VirtualShell/commands → commands}/grep.ts +1 -1
- package/src/{VirtualShell/commands → commands}/help.ts +1 -1
- package/src/{VirtualShell/commands → commands}/helpers.ts +1 -1
- package/src/{VirtualShell/commands → commands}/hostname.ts +1 -1
- package/src/{VirtualShell/commands → commands}/htop.ts +1 -1
- package/src/{VirtualShell/commands → commands}/index.ts +7 -4
- package/src/{VirtualShell/commands → commands}/ls.ts +1 -1
- package/src/{VirtualShell/commands → commands}/mkdir.ts +1 -1
- package/src/{VirtualShell/commands → commands}/nano.ts +1 -1
- package/src/{VirtualShell/commands → commands}/neofetch.ts +2 -2
- package/src/{VirtualShell/commands → commands}/pwd.ts +1 -1
- package/src/{VirtualShell/commands → commands}/rm.ts +1 -1
- package/src/{VirtualShell/commands → commands}/set.ts +1 -1
- package/src/{VirtualShell/commands → commands}/sh.ts +1 -1
- package/src/{VirtualShell/commands → commands}/su.ts +1 -1
- package/src/{VirtualShell/commands → commands}/sudo.ts +1 -1
- package/src/{VirtualShell/commands → commands}/touch.ts +2 -2
- package/src/{VirtualShell/commands → commands}/tree.ts +1 -1
- package/src/{VirtualShell/commands → commands}/unset.ts +1 -1
- package/src/{VirtualShell/commands → commands}/wget.ts +2 -2
- package/src/{VirtualShell/commands → commands}/who.ts +2 -2
- package/src/{VirtualShell/commands → commands}/whoami.ts +1 -1
- package/src/index.ts +2 -2
- package/{modules → src/modules}/neofetch.ts +56 -51
- package/src/modules/shellInteractive.ts +57 -0
- package/src/modules/shellRuntime.ts +76 -0
- package/tests/command-helpers.test.ts +1 -1
- package/tests/helpers.test.ts +1 -1
- package/tests/users.test.ts +60 -0
- package/tsconfig.json +19 -8
- /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 "../
|
|
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`,
|
|
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
|
-
|
|
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
|
-
|
|
251
|
+
const shellProps = info.shellProps;
|
|
254
252
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
const processUptime = process.uptime();
|
|
254
|
+
if (info.uptimeSeconds === undefined) {
|
|
255
|
+
info.uptimeSeconds = Math.round(processUptime);
|
|
256
|
+
}
|
|
259
257
|
|
|
260
|
-
|
|
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:
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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(
|
|
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
|
+
}
|
package/tests/helpers.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { assertPathAccess } from "../src/
|
|
2
|
+
import { assertPathAccess } from "../src/commands/helpers";
|
|
3
3
|
|
|
4
4
|
describe("assertPathAccess", () => {
|
|
5
5
|
test("blocks non-root access to auth store", () => {
|
package/tests/users.test.ts
CHANGED
|
@@ -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": [
|
|
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":
|
|
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
|
-
"
|
|
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
|
+
}
|
|
File without changes
|