typescript-virtual-container 1.5.3 → 1.5.5
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/README.md +44 -532
- package/dist/.tsbuildinfo +1 -0
- package/dist/SSHMimic/executor.js +23 -5
- package/dist/VirtualPackageManager/index.js +10 -0
- package/dist/VirtualShell/shell.js +158 -11
- package/dist/commands/basename.d.ts +13 -0
- package/dist/commands/basename.js +45 -0
- package/dist/commands/bc.d.ts +2 -0
- package/dist/commands/bc.js +28 -0
- package/dist/commands/file.d.ts +8 -0
- package/dist/commands/file.js +57 -0
- package/dist/commands/fun.d.ts +32 -0
- package/dist/commands/fun.js +172 -0
- package/dist/commands/ip.d.ts +7 -0
- package/dist/commands/ip.js +52 -0
- package/dist/commands/jobs.d.ts +4 -0
- package/dist/commands/jobs.js +27 -0
- package/dist/commands/last.d.ts +13 -0
- package/dist/commands/last.js +68 -0
- package/dist/commands/manuals-bundle.js +598 -6
- package/dist/commands/registry.js +30 -2
- package/dist/commands/runtime.js +24 -3
- package/dist/commands/set.js +20 -0
- package/dist/commands/sh.js +74 -1
- package/dist/commands/tput.d.ts +13 -0
- package/dist/commands/tput.js +76 -0
- package/dist/commands/w.d.ts +7 -0
- package/dist/commands/w.js +38 -0
- package/dist/utils/expand.d.ts +12 -0
- package/dist/utils/expand.js +87 -1
- package/package.json +9 -3
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
- package/.github/dependabot.yml +0 -27
- package/.github/pull_request_template.md +0 -21
- package/.github/workflows/create-pull-request.yml +0 -85
- package/.github/workflows/publish.yml +0 -25
- package/.github/workflows/test-battery.yml +0 -102
- package/.vscode/settings.json +0 -20
- package/CODE_OF_CONDUCT.md +0 -39
- package/CONTRIBUTING.md +0 -59
- package/HONEYPOT.md +0 -358
- package/SECURITY.md +0 -33
- package/benchmark-results.txt +0 -40
- package/benchmark-virtualshell.ts +0 -88
- package/biome.json +0 -37
- package/build.js +0 -22
- package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
- package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
- package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
- package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
- package/bun.lock +0 -244
- package/docs/.nojekyll +0 -1
- package/docs/app.js +0 -1751
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -162
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/HoneyPot.html +0 -31
- package/docs/classes/IdleManager.html +0 -162
- package/docs/classes/SshClient.html +0 -66
- package/docs/classes/VirtualFileSystem.html +0 -279
- package/docs/classes/VirtualPackageManager.html +0 -63
- package/docs/classes/VirtualSftpServer.html +0 -169
- package/docs/classes/VirtualShell.html +0 -285
- package/docs/classes/VirtualSshServer.html +0 -182
- package/docs/classes/VirtualUserManager.html +0 -276
- package/docs/demo.html +0 -82
- package/docs/functions/assertDiff.html +0 -6
- package/docs/functions/diffSnapshots.html +0 -7
- package/docs/functions/formatDiff.html +0 -6
- package/docs/functions/getArg.html +0 -13
- package/docs/functions/getFlag.html +0 -15
- package/docs/functions/ifFlag.html +0 -11
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -1869
- package/docs/interfaces/AuditLogEntry.html +0 -6
- package/docs/interfaces/CommandContext.html +0 -22
- package/docs/interfaces/CommandResult.html +0 -26
- package/docs/interfaces/ExecStream.html +0 -11
- package/docs/interfaces/HoneyPotStats.html +0 -16
- package/docs/interfaces/IdleManagerOptions.html +0 -7
- package/docs/interfaces/InstalledPackage.html +0 -20
- package/docs/interfaces/NanoEditorSession.html +0 -8
- package/docs/interfaces/PackageDefinition.html +0 -30
- package/docs/interfaces/PackageFile.html +0 -8
- package/docs/interfaces/PasswordChallenge.html +0 -16
- package/docs/interfaces/RemoveOptions.html +0 -4
- package/docs/interfaces/ShellEnv.html +0 -6
- package/docs/interfaces/ShellModule.html +0 -14
- package/docs/interfaces/ShellProperties.html +0 -14
- package/docs/interfaces/ShellStream.html +0 -11
- package/docs/interfaces/SudoChallenge.html +0 -24
- package/docs/interfaces/VfsBaseNode.html +0 -12
- package/docs/interfaces/VfsDiff.html +0 -10
- package/docs/interfaces/VfsDiffEntry.html +0 -6
- package/docs/interfaces/VfsDiffModified.html +0 -10
- package/docs/interfaces/VfsDirectoryNode.html +0 -15
- package/docs/interfaces/VfsFileNode.html +0 -17
- package/docs/interfaces/VfsOptions.html +0 -26
- package/docs/interfaces/VfsSnapshot.html +0 -3
- package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
- package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
- package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
- package/docs/interfaces/VirtualActiveSession.html +0 -12
- package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
- package/docs/interfaces/VirtualShellVfsLike.html +0 -15
- package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
- package/docs/interfaces/WriteFileOptions.html +0 -6
- package/docs/media/LICENSE +0 -21
- package/docs/modules.html +0 -1
- package/docs/types/ArgParseOptions.html +0 -4
- package/docs/types/CommandMode.html +0 -2
- package/docs/types/CommandOutcome.html +0 -2
- package/docs/types/IdleState.html +0 -1
- package/docs/types/VfsNodeStats.html +0 -2
- package/docs/types/VfsNodeType.html +0 -2
- package/docs/types/VfsPersistenceMode.html +0 -5
- package/docs/types/VfsSnapshotNode.html +0 -2
- package/examples/README.md +0 -288
- package/examples/app.js +0 -1751
- package/examples/app.ts +0 -299
- package/examples/build.js +0 -27
- package/examples/demo.html +0 -33
- package/examples/honeypot-audit.ts +0 -180
- package/examples/honeypot-export.ts +0 -253
- package/examples/honeypot-quickstart.ts +0 -110
- package/examples/index.html +0 -82
- package/examples/server.js +0 -55
- package/polyfills/buffer.js +0 -117
- package/polyfills/node_child_process/index.js +0 -2
- package/polyfills/node_crypto/index.js +0 -167
- package/polyfills/node_events/index.js +0 -9
- package/polyfills/node_fs/index.js +0 -202
- package/polyfills/node_fs/promises.js +0 -4
- package/polyfills/node_os/index.js +0 -9
- package/polyfills/node_path/index.js +0 -28
- package/polyfills/node_vm/index.js +0 -7
- package/polyfills/node_zlib/index.js +0 -3
- package/polyfills/process.js +0 -14
- package/polyfills/ssh2/index.js +0 -75
- package/scripts/build-all.mjs +0 -226
- package/scripts/build-names.mjs +0 -43
- package/scripts/generate-manuals-bundle.mjs +0 -49
- package/scripts/postinstall.js +0 -42
- package/scripts/publish-package.sh +0 -70
- package/src/Honeypot/index.ts +0 -457
- package/src/SSHClient/index.ts +0 -270
- package/src/SSHMimic/exec.ts +0 -49
- package/src/SSHMimic/executor.ts +0 -251
- package/src/SSHMimic/hostKey.ts +0 -21
- package/src/SSHMimic/index.ts +0 -337
- package/src/SSHMimic/loginBanner.ts +0 -36
- package/src/SSHMimic/loginFormat.ts +0 -10
- package/src/SSHMimic/prompt.ts +0 -14
- package/src/SSHMimic/sftp.ts +0 -883
- package/src/VirtualFileSystem/binaryPack.ts +0 -258
- package/src/VirtualFileSystem/index.ts +0 -1193
- package/src/VirtualFileSystem/internalTypes.ts +0 -43
- package/src/VirtualFileSystem/journal.ts +0 -171
- package/src/VirtualFileSystem/path.ts +0 -74
- package/src/VirtualPackageManager/index.ts +0 -996
- package/src/VirtualShell/idleManager.ts +0 -137
- package/src/VirtualShell/index.ts +0 -475
- package/src/VirtualShell/shell.ts +0 -700
- package/src/VirtualShell/shellParser.ts +0 -285
- package/src/VirtualUserManager/index.ts +0 -758
- package/src/bun.d.ts +0 -1
- package/src/commands/adduser.ts +0 -103
- package/src/commands/alias.ts +0 -69
- package/src/commands/apt.ts +0 -233
- package/src/commands/awk.ts +0 -168
- package/src/commands/base64.ts +0 -29
- package/src/commands/cat.ts +0 -52
- package/src/commands/cd.ts +0 -25
- package/src/commands/chmod.ts +0 -85
- package/src/commands/clear.ts +0 -15
- package/src/commands/command-helpers.ts +0 -286
- package/src/commands/cp.ts +0 -83
- package/src/commands/curl.ts +0 -147
- package/src/commands/cut.ts +0 -36
- package/src/commands/date.ts +0 -30
- package/src/commands/declare.ts +0 -49
- package/src/commands/deluser.ts +0 -98
- package/src/commands/df.ts +0 -23
- package/src/commands/diff.ts +0 -43
- package/src/commands/dpkg.ts +0 -180
- package/src/commands/du.ts +0 -56
- package/src/commands/echo.ts +0 -58
- package/src/commands/env.ts +0 -23
- package/src/commands/exit.ts +0 -18
- package/src/commands/export.ts +0 -34
- package/src/commands/find.ts +0 -68
- package/src/commands/free.ts +0 -47
- package/src/commands/grep.ts +0 -116
- package/src/commands/groups.ts +0 -19
- package/src/commands/gzip.ts +0 -88
- package/src/commands/head.ts +0 -52
- package/src/commands/help.ts +0 -152
- package/src/commands/helpers.ts +0 -234
- package/src/commands/history.ts +0 -34
- package/src/commands/hostname.ts +0 -14
- package/src/commands/htop.ts +0 -20
- package/src/commands/id.ts +0 -19
- package/src/commands/index.ts +0 -9
- package/src/commands/kill.ts +0 -19
- package/src/commands/ln.ts +0 -71
- package/src/commands/ls.ts +0 -243
- package/src/commands/lsb-release.ts +0 -63
- package/src/commands/man.ts +0 -31
- package/src/commands/manuals/adduser.txt +0 -11
- package/src/commands/manuals/apt-cache.txt +0 -12
- package/src/commands/manuals/apt.txt +0 -20
- package/src/commands/manuals/awk.txt +0 -13
- package/src/commands/manuals/cat.txt +0 -14
- package/src/commands/manuals/cd.txt +0 -16
- package/src/commands/manuals/chmod.txt +0 -16
- package/src/commands/manuals/clear.txt +0 -10
- package/src/commands/manuals/cp.txt +0 -10
- package/src/commands/manuals/curl.txt +0 -20
- package/src/commands/manuals/date.txt +0 -14
- package/src/commands/manuals/declare.txt +0 -12
- package/src/commands/manuals/deluser.txt +0 -10
- package/src/commands/manuals/df.txt +0 -10
- package/src/commands/manuals/dpkg-query.txt +0 -11
- package/src/commands/manuals/dpkg.txt +0 -14
- package/src/commands/manuals/du.txt +0 -11
- package/src/commands/manuals/echo.txt +0 -11
- package/src/commands/manuals/false.txt +0 -10
- package/src/commands/manuals/find.txt +0 -11
- package/src/commands/manuals/free.txt +0 -12
- package/src/commands/manuals/grep.txt +0 -13
- package/src/commands/manuals/groups.txt +0 -10
- package/src/commands/manuals/gzip.txt +0 -11
- package/src/commands/manuals/head.txt +0 -10
- package/src/commands/manuals/help.txt +0 -11
- package/src/commands/manuals/history.txt +0 -11
- package/src/commands/manuals/hostname.txt +0 -10
- package/src/commands/manuals/id.txt +0 -10
- package/src/commands/manuals/kill.txt +0 -13
- package/src/commands/manuals/ls.txt +0 -20
- package/src/commands/manuals/lsb_release.txt +0 -14
- package/src/commands/manuals/mkdir.txt +0 -10
- package/src/commands/manuals/mv.txt +0 -10
- package/src/commands/manuals/nano.txt +0 -11
- package/src/commands/manuals/neofetch.txt +0 -10
- package/src/commands/manuals/node.txt +0 -13
- package/src/commands/manuals/npm.txt +0 -13
- package/src/commands/manuals/npx.txt +0 -13
- package/src/commands/manuals/passwd.txt +0 -11
- package/src/commands/manuals/ping.txt +0 -10
- package/src/commands/manuals/printf.txt +0 -11
- package/src/commands/manuals/ps.txt +0 -10
- package/src/commands/manuals/pwd.txt +0 -10
- package/src/commands/manuals/python3.txt +0 -13
- package/src/commands/manuals/readlink.txt +0 -10
- package/src/commands/manuals/return.txt +0 -10
- package/src/commands/manuals/rm.txt +0 -10
- package/src/commands/manuals/sed.txt +0 -11
- package/src/commands/manuals/set.txt +0 -11
- package/src/commands/manuals/shift.txt +0 -10
- package/src/commands/manuals/sleep.txt +0 -10
- package/src/commands/manuals/sort.txt +0 -12
- package/src/commands/manuals/source.txt +0 -11
- package/src/commands/manuals/ssh.txt +0 -11
- package/src/commands/manuals/stat.txt +0 -10
- package/src/commands/manuals/su.txt +0 -13
- package/src/commands/manuals/sudo.txt +0 -11
- package/src/commands/manuals/tail.txt +0 -10
- package/src/commands/manuals/tar.txt +0 -19
- package/src/commands/manuals/tee.txt +0 -10
- package/src/commands/manuals/test.txt +0 -11
- package/src/commands/manuals/touch.txt +0 -11
- package/src/commands/manuals/tr.txt +0 -10
- package/src/commands/manuals/trap.txt +0 -10
- package/src/commands/manuals/true.txt +0 -10
- package/src/commands/manuals/type.txt +0 -10
- package/src/commands/manuals/uname.txt +0 -12
- package/src/commands/manuals/uniq.txt +0 -12
- package/src/commands/manuals/unset.txt +0 -10
- package/src/commands/manuals/uptime.txt +0 -11
- package/src/commands/manuals/wc.txt +0 -12
- package/src/commands/manuals/wget.txt +0 -12
- package/src/commands/manuals/which.txt +0 -10
- package/src/commands/manuals/whoami.txt +0 -10
- package/src/commands/manuals/xargs.txt +0 -10
- package/src/commands/manuals-bundle.ts +0 -898
- package/src/commands/mkdir.ts +0 -31
- package/src/commands/mv.ts +0 -50
- package/src/commands/nano.ts +0 -38
- package/src/commands/neofetch.ts +0 -53
- package/src/commands/node.ts +0 -341
- package/src/commands/npm.ts +0 -132
- package/src/commands/passwd.ts +0 -50
- package/src/commands/ping.ts +0 -32
- package/src/commands/printf.ts +0 -129
- package/src/commands/ps.ts +0 -58
- package/src/commands/pwd.ts +0 -9
- package/src/commands/python.ts +0 -2229
- package/src/commands/read.ts +0 -46
- package/src/commands/registry.ts +0 -249
- package/src/commands/rm.ts +0 -42
- package/src/commands/runtime.ts +0 -421
- package/src/commands/sed.ts +0 -68
- package/src/commands/seq.ts +0 -43
- package/src/commands/set.ts +0 -29
- package/src/commands/sh.ts +0 -467
- package/src/commands/shift.ts +0 -63
- package/src/commands/sleep.ts +0 -20
- package/src/commands/sort.ts +0 -46
- package/src/commands/source.ts +0 -52
- package/src/commands/stat.ts +0 -61
- package/src/commands/su.ts +0 -72
- package/src/commands/sudo.ts +0 -76
- package/src/commands/tail.ts +0 -53
- package/src/commands/tar.ts +0 -102
- package/src/commands/tee.ts +0 -36
- package/src/commands/test.ts +0 -137
- package/src/commands/touch.ts +0 -28
- package/src/commands/tr.ts +0 -70
- package/src/commands/tree.ts +0 -20
- package/src/commands/true.ts +0 -27
- package/src/commands/type.ts +0 -48
- package/src/commands/uname.ts +0 -29
- package/src/commands/uniq.ts +0 -39
- package/src/commands/unset.ts +0 -17
- package/src/commands/uptime.ts +0 -54
- package/src/commands/wc.ts +0 -55
- package/src/commands/wget.ts +0 -148
- package/src/commands/which.ts +0 -37
- package/src/commands/who.ts +0 -25
- package/src/commands/whoami.ts +0 -14
- package/src/commands/xargs.ts +0 -31
- package/src/index.ts +0 -67
- package/src/modules/linuxRootfs.ts +0 -1961
- package/src/modules/neofetch.ts +0 -358
- package/src/modules/shellInteractive.ts +0 -57
- package/src/modules/shellRuntime.ts +0 -76
- package/src/self-standalone.ts +0 -542
- package/src/standalone-wo-sftp.ts +0 -38
- package/src/standalone.ts +0 -72
- package/src/types/commands.ts +0 -146
- package/src/types/pipeline.ts +0 -52
- package/src/types/streams.ts +0 -32
- package/src/types/tar-stream.d.ts +0 -38
- package/src/types/vfs.ts +0 -98
- package/src/utils/expand.ts +0 -491
- package/src/utils/perfLogger.ts +0 -72
- package/src/utils/tokenize.ts +0 -98
- package/src/utils/vfsDiff.ts +0 -275
- package/tests/command-helpers.test.ts +0 -116
- package/tests/commands-admin-net.test.ts +0 -441
- package/tests/commands-advanced.test.ts +0 -456
- package/tests/commands-core.test.ts +0 -562
- package/tests/commands-missing.test.ts +0 -570
- package/tests/commands-specific-units.test.ts +0 -327
- package/tests/commands-text-sys.test.ts +0 -445
- package/tests/expand.test.ts +0 -170
- package/tests/helpers.test.ts +0 -97
- package/tests/new-features.test.ts +0 -1036
- package/tests/parser-executor.test.ts +0 -37
- package/tests/sftp.test.ts +0 -323
- package/tests/ssh-exec.test.ts +0 -45
- package/tests/test-helper.ts +0 -79
- package/tests/users.test.ts +0 -86
- package/tsconfig.json +0 -49
- package/typedoc.json +0 -47
package/src/commands/helpers.ts
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type VirtualFileSystem from "../VirtualFileSystem";
|
|
4
|
-
import type { VirtualPackageManager } from "../VirtualPackageManager";
|
|
5
|
-
import type { VirtualShell } from "../VirtualShell";
|
|
6
|
-
|
|
7
|
-
const PROTECTED_PREFIXES = ["/.virtual-env-js/.auth", "/etc/htpasswd"] as const;
|
|
8
|
-
|
|
9
|
-
function normalizeFetchUrl(input: string): string {
|
|
10
|
-
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(input)) {
|
|
11
|
-
return input;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return `http://${input}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function normalizeTerminalOutput(text: string): string {
|
|
18
|
-
return text
|
|
19
|
-
.replace(/\r\n/g, "\n")
|
|
20
|
-
.replace(/\r/g, "\n")
|
|
21
|
-
.replace(/\t/g, " ")
|
|
22
|
-
.split("\n")
|
|
23
|
-
.map((line) =>
|
|
24
|
-
line.replace(/^[ \u00A0]{8,}/, " ").replace(/[ \u00A0]{3,}/g, " "),
|
|
25
|
-
)
|
|
26
|
-
.join("\n")
|
|
27
|
-
.replace(/\n{3,}/g, "\n\n")
|
|
28
|
-
.trimEnd();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function resolvePath(cwd: string, inputPath: string, homeDir?: string): string {
|
|
32
|
-
if (!inputPath || inputPath.trim() === "") {
|
|
33
|
-
return cwd;
|
|
34
|
-
}
|
|
35
|
-
if (inputPath.startsWith("~")) {
|
|
36
|
-
const home = homeDir ?? "/root";
|
|
37
|
-
return path.posix.normalize(`${home}${inputPath.slice(1)}`);
|
|
38
|
-
}
|
|
39
|
-
return inputPath.startsWith("/")
|
|
40
|
-
? path.posix.normalize(inputPath)
|
|
41
|
-
: path.posix.normalize(path.posix.join(cwd, inputPath));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isProtectedPath(targetPath: string): boolean {
|
|
45
|
-
const normalized = targetPath.startsWith("/")
|
|
46
|
-
? path.posix.normalize(targetPath)
|
|
47
|
-
: path.posix.normalize(`/${targetPath}`);
|
|
48
|
-
|
|
49
|
-
return PROTECTED_PREFIXES.some(
|
|
50
|
-
(prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function assertPathAccess(
|
|
55
|
-
authUser: string,
|
|
56
|
-
targetPath: string,
|
|
57
|
-
operation: string,
|
|
58
|
-
): void {
|
|
59
|
-
if (authUser === "root") {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (isProtectedPath(targetPath)) {
|
|
64
|
-
throw new Error(`${operation}: permission denied: ${targetPath}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function stripUrlFilename(url: string): string {
|
|
69
|
-
const cleaned = url.split("?")[0]?.split("#")[0] ?? url;
|
|
70
|
-
const lastPart = cleaned.split("/").filter(Boolean).pop();
|
|
71
|
-
return lastPart && lastPart.length > 0 ? lastPart : "index.html";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function fetchResource(
|
|
75
|
-
url: string,
|
|
76
|
-
): Promise<{ text: string; status: number; contentType: string | null }> {
|
|
77
|
-
const response = await fetch(normalizeFetchUrl(url));
|
|
78
|
-
const contentType = response.headers.get("content-type");
|
|
79
|
-
return {
|
|
80
|
-
text: await response.text(),
|
|
81
|
-
status: response.status,
|
|
82
|
-
contentType,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Run a host command like curl or wget and capture its output.
|
|
88
|
-
* @param binary - The binary to execute (e.g., "curl", "wget").
|
|
89
|
-
* @param args - Arguments to pass to the binary.
|
|
90
|
-
* @returns Promise resolving with stdout, stderr, and exit code.
|
|
91
|
-
*/
|
|
92
|
-
export function runHostCommand(
|
|
93
|
-
binary: string,
|
|
94
|
-
args: string[],
|
|
95
|
-
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
96
|
-
return new Promise((resolve) => {
|
|
97
|
-
let childProcess: ReturnType<typeof spawn>;
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
childProcess = spawn(binary, args, {
|
|
101
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
102
|
-
});
|
|
103
|
-
} catch (error) {
|
|
104
|
-
resolve({
|
|
105
|
-
stdout: "",
|
|
106
|
-
stderr: `${binary}: ${error instanceof Error ? error.message : String(error)}`,
|
|
107
|
-
exitCode: 1,
|
|
108
|
-
});
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let stdout = "";
|
|
113
|
-
let stderr = "";
|
|
114
|
-
const stdoutStream = childProcess.stdout;
|
|
115
|
-
const stderrStream = childProcess.stderr;
|
|
116
|
-
|
|
117
|
-
if (!stdoutStream || !stderrStream) {
|
|
118
|
-
resolve({
|
|
119
|
-
stdout: "",
|
|
120
|
-
stderr: `${binary}: failed to capture process output`,
|
|
121
|
-
exitCode: 1,
|
|
122
|
-
});
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
stdoutStream.setEncoding("utf8");
|
|
127
|
-
stderrStream.setEncoding("utf8");
|
|
128
|
-
|
|
129
|
-
stdoutStream.on("data", (chunk: string) => {
|
|
130
|
-
stdout += chunk;
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
stderrStream.on("data", (chunk: string) => {
|
|
134
|
-
stderr += chunk;
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
childProcess.on("error", (error) => {
|
|
138
|
-
const errorCode =
|
|
139
|
-
error instanceof Error && "code" in error
|
|
140
|
-
? String((error as NodeJS.ErrnoException).code ?? "")
|
|
141
|
-
: "";
|
|
142
|
-
resolve({
|
|
143
|
-
stdout: "",
|
|
144
|
-
stderr: `${binary}: ${error.message}`,
|
|
145
|
-
exitCode: errorCode === "ENOENT" ? 127 : 1,
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
childProcess.on("close", (code) => {
|
|
150
|
-
resolve({
|
|
151
|
-
stdout,
|
|
152
|
-
stderr,
|
|
153
|
-
exitCode: code ?? 1,
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function levenshtein(a: string, b: string): number {
|
|
160
|
-
const dp: number[][] = Array.from({ length: a.length + 1 }, () =>
|
|
161
|
-
Array<number>(b.length + 1).fill(0),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
for (let i = 0; i <= a.length; i += 1) {
|
|
165
|
-
dp[i]![0] = i;
|
|
166
|
-
}
|
|
167
|
-
for (let j = 0; j <= b.length; j += 1) {
|
|
168
|
-
dp[0]![j] = j;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
for (let i = 1; i <= a.length; i += 1) {
|
|
172
|
-
for (let j = 1; j <= b.length; j += 1) {
|
|
173
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
174
|
-
dp[i]![j] = Math.min(
|
|
175
|
-
dp[i - 1]![j]! + 1,
|
|
176
|
-
dp[i]![j - 1]! + 1,
|
|
177
|
-
dp[i - 1]![j - 1]! + cost,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return dp[a.length]![b.length]!;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function resolveReadablePath(
|
|
186
|
-
vfs: VirtualFileSystem,
|
|
187
|
-
cwd: string,
|
|
188
|
-
inputPath: string,
|
|
189
|
-
): string {
|
|
190
|
-
const exactPath = resolvePath(cwd, inputPath);
|
|
191
|
-
if (vfs.exists(exactPath)) {
|
|
192
|
-
return exactPath;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const parent = path.posix.dirname(exactPath);
|
|
196
|
-
const fileName = path.posix.basename(exactPath);
|
|
197
|
-
const siblings = vfs.list(parent);
|
|
198
|
-
|
|
199
|
-
const caseInsensitive = siblings.filter(
|
|
200
|
-
(name) => name.toLowerCase() === fileName.toLowerCase(),
|
|
201
|
-
);
|
|
202
|
-
if (caseInsensitive.length === 1) {
|
|
203
|
-
return path.posix.join(parent, caseInsensitive[0]!);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const near = siblings.filter(
|
|
207
|
-
(name) => levenshtein(name.toLowerCase(), fileName.toLowerCase()) <= 1,
|
|
208
|
-
);
|
|
209
|
-
if (near.length === 1) {
|
|
210
|
-
return path.posix.join(parent, near[0]!);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return exactPath;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function joinListWithType(
|
|
217
|
-
cwd: string,
|
|
218
|
-
items: string[],
|
|
219
|
-
statAt: (p: string) => { type: "file" | "directory" },
|
|
220
|
-
): string {
|
|
221
|
-
return items
|
|
222
|
-
.map((name) => {
|
|
223
|
-
const childPath = resolvePath(cwd, name);
|
|
224
|
-
const stats = statAt(childPath);
|
|
225
|
-
return stats.type === "directory" ? `${name}/` : name;
|
|
226
|
-
})
|
|
227
|
-
.join(" ");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function getPackageManager(
|
|
231
|
-
shell: VirtualShell,
|
|
232
|
-
): VirtualPackageManager | undefined {
|
|
233
|
-
return shell.packageManager;
|
|
234
|
-
}
|
package/src/commands/history.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Display persisted command history for the session (from VFS).
|
|
5
|
-
* @category shell
|
|
6
|
-
* @params ["[n]"]
|
|
7
|
-
*/
|
|
8
|
-
export const historyCommand: ShellModule = {
|
|
9
|
-
name: "history",
|
|
10
|
-
description: "Display command history",
|
|
11
|
-
category: "shell",
|
|
12
|
-
params: ["[n]"],
|
|
13
|
-
run: ({ args, shell, authUser }) => {
|
|
14
|
-
// History is persisted in the VFS by the interactive shell
|
|
15
|
-
const histPath = `/home/${authUser}/.bash_history`;
|
|
16
|
-
if (!shell.vfs.exists(histPath)) {
|
|
17
|
-
return { stdout: "", exitCode: 0 };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const raw = shell.vfs.readFile(histPath);
|
|
21
|
-
const lines = raw.split("\n").filter(Boolean);
|
|
22
|
-
|
|
23
|
-
const nArg = args[0];
|
|
24
|
-
const n = nArg ? parseInt(nArg, 10) : null;
|
|
25
|
-
const slice = n && !Number.isNaN(n) ? lines.slice(-n) : lines;
|
|
26
|
-
|
|
27
|
-
const offset = lines.length - slice.length + 1;
|
|
28
|
-
const numbered = slice.map(
|
|
29
|
-
(line, i) => `${String(offset + i).padStart(5)} ${line}`,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
return { stdout: numbered.join("\n"), exitCode: 0 };
|
|
33
|
-
},
|
|
34
|
-
};
|
package/src/commands/hostname.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Print the configured hostname for the virtual shell.
|
|
5
|
-
* @category system
|
|
6
|
-
* @params []
|
|
7
|
-
*/
|
|
8
|
-
export const hostnameCommand: ShellModule = {
|
|
9
|
-
name: "hostname",
|
|
10
|
-
description: "Print hostname",
|
|
11
|
-
category: "system",
|
|
12
|
-
params: [],
|
|
13
|
-
run: ({ hostname }) => ({ stdout: hostname, exitCode: 0 }),
|
|
14
|
-
};
|
package/src/commands/htop.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Interactive system monitor (requires terminal interaction).
|
|
5
|
-
* @category system
|
|
6
|
-
* @params []
|
|
7
|
-
*/
|
|
8
|
-
export const htopCommand: ShellModule = {
|
|
9
|
-
name: "htop",
|
|
10
|
-
description: "System monitor",
|
|
11
|
-
category: "system",
|
|
12
|
-
params: [],
|
|
13
|
-
run: ({ mode }) => {
|
|
14
|
-
if (mode === "exec") {
|
|
15
|
-
return { stderr: "htop: interactive terminal required", exitCode: 1 };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return { openHtop: true, exitCode: 0 };
|
|
19
|
-
},
|
|
20
|
-
};
|
package/src/commands/id.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
export const idCommand: ShellModule = {
|
|
4
|
-
name: "id",
|
|
5
|
-
description: "Print user identity",
|
|
6
|
-
category: "system",
|
|
7
|
-
params: ["[user]"],
|
|
8
|
-
run: ({ authUser, shell, args }) => {
|
|
9
|
-
const target = args[0] ?? authUser;
|
|
10
|
-
const uid = target === "root" ? 0 : 1000;
|
|
11
|
-
const gid = uid;
|
|
12
|
-
const isSudo = shell.users.isSudoer(target);
|
|
13
|
-
const groups = isSudo ? `${gid}(${target}),0(root)` : `${gid}(${target})`;
|
|
14
|
-
return {
|
|
15
|
-
stdout: `uid=${uid}(${target}) gid=${gid}(${target}) groups=${groups}`,
|
|
16
|
-
exitCode: 0,
|
|
17
|
-
};
|
|
18
|
-
},
|
|
19
|
-
};
|
package/src/commands/index.ts
DELETED
package/src/commands/kill.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Send a signal to a process by PID.
|
|
5
|
-
* @category system
|
|
6
|
-
* @params ["[-9] <pid>"]
|
|
7
|
-
*/
|
|
8
|
-
export const killCommand: ShellModule = {
|
|
9
|
-
name: "kill",
|
|
10
|
-
description: "Send signal to process",
|
|
11
|
-
category: "system",
|
|
12
|
-
params: ["[-9] <pid>"],
|
|
13
|
-
run: ({ args }) => {
|
|
14
|
-
const pid = args.find((a) => !a.startsWith("-"));
|
|
15
|
-
if (!pid) return { stderr: "kill: no pid specified", exitCode: 1 };
|
|
16
|
-
// In virtual env, we just acknowledge the kill
|
|
17
|
-
return { stdout: ``, exitCode: 0 };
|
|
18
|
-
},
|
|
19
|
-
};
|
package/src/commands/ln.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
import { ifFlag } from "./command-helpers";
|
|
3
|
-
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
|
-
|
|
5
|
-
export const lnCommand: ShellModule = {
|
|
6
|
-
name: "ln",
|
|
7
|
-
description: "Create links",
|
|
8
|
-
category: "files",
|
|
9
|
-
params: ["[-s] <target> <link_name>"],
|
|
10
|
-
run: ({ authUser, shell, cwd, args }) => {
|
|
11
|
-
const symbolic = ifFlag(args, ["-s", "--symbolic"]);
|
|
12
|
-
const positionals = args.filter((a) => !a.startsWith("-"));
|
|
13
|
-
const [targetArg, linkArg] = positionals;
|
|
14
|
-
|
|
15
|
-
if (!targetArg || !linkArg) {
|
|
16
|
-
return { stderr: "ln: missing operand", exitCode: 1 };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const linkPath = resolvePath(cwd, linkArg);
|
|
20
|
-
const targetPath = symbolic
|
|
21
|
-
? targetArg // keep relative for symlinks
|
|
22
|
-
: resolvePath(cwd, targetArg);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
assertPathAccess(authUser, linkPath, "ln");
|
|
26
|
-
|
|
27
|
-
if (!symbolic) {
|
|
28
|
-
// Hard link — copy file contents
|
|
29
|
-
const srcPath = resolvePath(cwd, targetArg);
|
|
30
|
-
assertPathAccess(authUser, srcPath, "ln");
|
|
31
|
-
if (!shell.vfs.exists(srcPath)) {
|
|
32
|
-
return {
|
|
33
|
-
stderr: `ln: ${targetArg}: No such file or directory`,
|
|
34
|
-
exitCode: 1,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const content = shell.vfs.readFile(srcPath);
|
|
38
|
-
shell.writeFileAsUser(authUser, linkPath, content);
|
|
39
|
-
} else {
|
|
40
|
-
shell.vfs.symlink(targetPath, linkPath);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { exitCode: 0 };
|
|
44
|
-
} catch (err) {
|
|
45
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
46
|
-
return { stderr: `ln: ${msg}`, exitCode: 1 };
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** Shell command: print the value of a symbolic link. */
|
|
52
|
-
export const readlinkCommand: ShellModule = {
|
|
53
|
-
name: "readlink",
|
|
54
|
-
description: "Print resolved path of symbolic link",
|
|
55
|
-
category: "files",
|
|
56
|
-
params: ["[-f] <path>"],
|
|
57
|
-
run: ({ shell, cwd, args }) => {
|
|
58
|
-
const follow = args.includes("-f") || args.includes("-e");
|
|
59
|
-
const target = args.find((a) => !a.startsWith("-"));
|
|
60
|
-
if (!target) return { stderr: "readlink: missing operand\n", exitCode: 1 };
|
|
61
|
-
const p = resolvePath(cwd, target);
|
|
62
|
-
if (!shell.vfs.exists(p)) {
|
|
63
|
-
return { stderr: `readlink: ${target}: No such file or directory\n`, exitCode: 1 };
|
|
64
|
-
}
|
|
65
|
-
if (!shell.vfs.isSymlink(p)) {
|
|
66
|
-
return { stderr: `readlink: ${target}: not a symbolic link\n`, exitCode: 1 };
|
|
67
|
-
}
|
|
68
|
-
const resolved = shell.vfs.resolveSymlink(follow ? p : p);
|
|
69
|
-
return { stdout: `${resolved}\n`, exitCode: 0 };
|
|
70
|
-
},
|
|
71
|
-
};
|
package/src/commands/ls.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
import { getArg, ifFlag } from "./command-helpers";
|
|
3
|
-
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
|
-
|
|
5
|
-
// ─── ANSI color codes (matches GNU ls --color=auto / LS_COLORS defaults) ────
|
|
6
|
-
|
|
7
|
-
const RESET = "\x1b[0m";
|
|
8
|
-
|
|
9
|
-
// Entry-type colors
|
|
10
|
-
const C_DIR = "\x1b[1;34m"; // bold blue — directory
|
|
11
|
-
const C_LINK = "\x1b[1;36m"; // bold cyan — symlink
|
|
12
|
-
const C_EXEC = "\x1b[1;32m"; // bold green — executable file
|
|
13
|
-
const C_NORMAL = ""; // no color — regular file
|
|
14
|
-
|
|
15
|
-
// Special-mode backgrounds (GNU ls corner cases)
|
|
16
|
-
const C_STICKY_WX = "\x1b[30;42m"; // black on green — dir sticky + world-writable (/tmp)
|
|
17
|
-
const C_STICKY = "\x1b[37;44m"; // white on blue — dir sticky, NOT world-writable
|
|
18
|
-
const C_OTHER_WX = "\x1b[34;42m"; // blue on green — dir world-writable, not sticky
|
|
19
|
-
|
|
20
|
-
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
function colorize(name: string, color: string): string {
|
|
23
|
-
return color ? `${color}${name}${RESET}` : name;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function entryColor(mode: number, type: "file" | "directory", isLink: boolean): string {
|
|
27
|
-
if (isLink) return C_LINK;
|
|
28
|
-
if (type === "directory") {
|
|
29
|
-
const sticky = !!(mode & 0o1000);
|
|
30
|
-
const worldW = !!(mode & 0o002);
|
|
31
|
-
if (sticky && worldW) return C_STICKY_WX;
|
|
32
|
-
if (sticky) return C_STICKY;
|
|
33
|
-
if (worldW) return C_OTHER_WX;
|
|
34
|
-
return C_DIR;
|
|
35
|
-
}
|
|
36
|
-
if (mode & 0o111) return C_EXEC;
|
|
37
|
-
return C_NORMAL;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── permissions string ──────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
function formatPermissions(mode: number, type: "file" | "directory", isLink: boolean): string {
|
|
43
|
-
let ft: string;
|
|
44
|
-
if (isLink) ft = "l";
|
|
45
|
-
else if (type === "directory") ft = "d";
|
|
46
|
-
else ft = "-";
|
|
47
|
-
|
|
48
|
-
const r = (bit: number) => (mode & bit ? "r" : "-");
|
|
49
|
-
const w = (bit: number) => (mode & bit ? "w" : "-");
|
|
50
|
-
|
|
51
|
-
const xOwner = (() => {
|
|
52
|
-
const exec = !!(mode & 0o100);
|
|
53
|
-
if (mode & 0o4000) return exec ? "s" : "S";
|
|
54
|
-
return exec ? "x" : "-";
|
|
55
|
-
})();
|
|
56
|
-
const xGroup = (() => {
|
|
57
|
-
const exec = !!(mode & 0o010);
|
|
58
|
-
if (mode & 0o2000) return exec ? "s" : "S";
|
|
59
|
-
return exec ? "x" : "-";
|
|
60
|
-
})();
|
|
61
|
-
const xOther = (() => {
|
|
62
|
-
const exec = !!(mode & 0o001);
|
|
63
|
-
if (type === "directory" && (mode & 0o1000)) return exec ? "t" : "T";
|
|
64
|
-
return exec ? "x" : "-";
|
|
65
|
-
})();
|
|
66
|
-
|
|
67
|
-
return `${ft}${r(0o400)}${w(0o200)}${xOwner}${r(0o040)}${w(0o020)}${xGroup}${r(0o004)}${w(0o002)}${xOther}`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── date formatting (GNU ls + French locale) ────────────────────────────────
|
|
71
|
-
|
|
72
|
-
const MONTHS_EN = [
|
|
73
|
-
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
74
|
-
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
function formatDate(date: Date): string {
|
|
78
|
-
const now = new Date();
|
|
79
|
-
const sixMo = 6 * 30 * 24 * 3600 * 1000;
|
|
80
|
-
const recent = Math.abs(now.getTime() - date.getTime()) < sixMo;
|
|
81
|
-
const day = String(date.getDate()).padStart(2, " ");
|
|
82
|
-
const month = MONTHS_EN[date.getMonth()] ?? "";
|
|
83
|
-
if (recent) {
|
|
84
|
-
const hh = String(date.getHours()).padStart(2, "0");
|
|
85
|
-
const mm = String(date.getMinutes()).padStart(2, "0");
|
|
86
|
-
return `${day} ${month.padEnd(3)} ${hh}:${mm}`;
|
|
87
|
-
}
|
|
88
|
-
return `${day} ${month.padEnd(3)} ${date.getFullYear()}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ─── symlink target ──────────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
function readlinkTarget(vfs: { readFile: (p: string) => string }, path: string): string {
|
|
94
|
-
try { return vfs.readFile(path); } catch { return "?"; }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ─── short listing ───────────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
|
-
function shortListing(
|
|
100
|
-
vfs: {
|
|
101
|
-
stat: (p: string) => { mode: number; type: "file" | "directory" };
|
|
102
|
-
isSymlink: (p: string) => boolean;
|
|
103
|
-
},
|
|
104
|
-
dir: string,
|
|
105
|
-
items: string[],
|
|
106
|
-
): string {
|
|
107
|
-
const base = dir === "/" ? "" : dir;
|
|
108
|
-
return items.map((name) => {
|
|
109
|
-
const childPath = `${base}/${name}`;
|
|
110
|
-
const isLink = vfs.isSymlink(childPath);
|
|
111
|
-
let stat: { mode: number; type: "file" | "directory" };
|
|
112
|
-
try { stat = vfs.stat(childPath); } catch { return name; }
|
|
113
|
-
const color = entryColor(stat.mode, stat.type, isLink);
|
|
114
|
-
return colorize(name, color);
|
|
115
|
-
}).join(" ");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ─── long listing ────────────────────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
type VfsStat = {
|
|
121
|
-
mode: number;
|
|
122
|
-
type: "file" | "directory";
|
|
123
|
-
updatedAt: Date;
|
|
124
|
-
size?: number;
|
|
125
|
-
childrenCount?: number;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
function longListing(
|
|
129
|
-
vfs: {
|
|
130
|
-
stat: (p: string) => VfsStat;
|
|
131
|
-
isSymlink: (p: string) => boolean;
|
|
132
|
-
readFile: (p: string) => string;
|
|
133
|
-
},
|
|
134
|
-
dir: string,
|
|
135
|
-
items: string[],
|
|
136
|
-
): string {
|
|
137
|
-
const base = dir === "/" ? "" : dir;
|
|
138
|
-
|
|
139
|
-
type Row = { perms: string; nlink: string; size: string; date: string; label: string };
|
|
140
|
-
|
|
141
|
-
const rows: Row[] = items.map((name) => {
|
|
142
|
-
const childPath = `${base}/${name}`;
|
|
143
|
-
const isLink = vfs.isSymlink(childPath);
|
|
144
|
-
let stat: VfsStat;
|
|
145
|
-
try { stat = vfs.stat(childPath); } catch {
|
|
146
|
-
return {
|
|
147
|
-
perms: "----------", nlink: "1", size: "0",
|
|
148
|
-
date: formatDate(new Date()), label: name,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const mode = isLink ? 0o120777 : stat.mode;
|
|
153
|
-
const perms = formatPermissions(mode, stat.type, isLink);
|
|
154
|
-
|
|
155
|
-
// nlink: dirs = children + 2 (. and ..), files/links = 1
|
|
156
|
-
const nlink = stat.type === "directory"
|
|
157
|
-
? String((stat.childrenCount ?? 0) + 2)
|
|
158
|
-
: "1";
|
|
159
|
-
|
|
160
|
-
// size: links → target path length, files → bytes, dirs → children×4096
|
|
161
|
-
const rawSize = isLink
|
|
162
|
-
? readlinkTarget(vfs, childPath).length
|
|
163
|
-
: stat.type === "file"
|
|
164
|
-
? (stat.size ?? 0)
|
|
165
|
-
: (stat.childrenCount ?? 0) * 4096;
|
|
166
|
-
const size = String(rawSize);
|
|
167
|
-
|
|
168
|
-
const date = formatDate(stat.updatedAt);
|
|
169
|
-
const color = entryColor(mode, stat.type, isLink);
|
|
170
|
-
|
|
171
|
-
const label = isLink
|
|
172
|
-
? `${colorize(name, color)} -> ${readlinkTarget(vfs, childPath)}`
|
|
173
|
-
: colorize(name, color);
|
|
174
|
-
|
|
175
|
-
return { perms, nlink, size, date, label };
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const wNlink = Math.max(...rows.map((r) => r.nlink.length));
|
|
179
|
-
const wSize = Math.max(...rows.map((r) => r.size.length));
|
|
180
|
-
const owner = "root";
|
|
181
|
-
const group = "root";
|
|
182
|
-
|
|
183
|
-
const total = items.length * 8;
|
|
184
|
-
const lines = rows.map((r) =>
|
|
185
|
-
`${r.perms} ${r.nlink.padStart(wNlink)} ${owner} ${group} ${r.size.padStart(wSize)} ${r.date} ${r.label}`,
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
return `total ${total}\n${lines.join("\n")}`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ─── command ─────────────────────────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
export const lsCommand: ShellModule = {
|
|
194
|
-
name: "ls",
|
|
195
|
-
description: "List directory contents",
|
|
196
|
-
category: "navigation",
|
|
197
|
-
params: ["[-la] [path]"],
|
|
198
|
-
run: ({ authUser, shell, cwd, args }) => {
|
|
199
|
-
const longFormat = ifFlag(args, ["-l", "--long", "-la", "-al"]);
|
|
200
|
-
const showHidden = ifFlag(args, ["-a", "--all", "-la", "-al"]);
|
|
201
|
-
|
|
202
|
-
const targetArg = getArg(args, 0, {
|
|
203
|
-
flags: ["-l", "--long", "-a", "--all", "-la", "-al"],
|
|
204
|
-
});
|
|
205
|
-
const target = resolvePath(cwd, targetArg ?? cwd);
|
|
206
|
-
assertPathAccess(authUser, target, "ls");
|
|
207
|
-
|
|
208
|
-
// Single file or symlink
|
|
209
|
-
if (shell.vfs.exists(target)) {
|
|
210
|
-
const st = shell.vfs.stat(target);
|
|
211
|
-
const isLink = shell.vfs.isSymlink(target);
|
|
212
|
-
if (st.type === "file" || isLink) {
|
|
213
|
-
const name = target.split("/").pop() ?? target;
|
|
214
|
-
const color = entryColor(isLink ? 0o120777 : st.mode, st.type, isLink);
|
|
215
|
-
if (longFormat) {
|
|
216
|
-
const mode = isLink ? 0o120777 : st.mode;
|
|
217
|
-
const size = isLink
|
|
218
|
-
? readlinkTarget(shell.vfs, target).length
|
|
219
|
-
: (st as { size?: number }).size ?? 0;
|
|
220
|
-
const perms = formatPermissions(mode, st.type, isLink);
|
|
221
|
-
const label = isLink
|
|
222
|
-
? `${colorize(name, color)} -> ${readlinkTarget(shell.vfs, target)}`
|
|
223
|
-
: colorize(name, color);
|
|
224
|
-
return {
|
|
225
|
-
stdout: `${perms} 1 root root ${size} ${formatDate(st.updatedAt)} ${label}\n`,
|
|
226
|
-
exitCode: 0,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
return { stdout: `${colorize(name, color)}\n`, exitCode: 0 };
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const items = shell.vfs
|
|
234
|
-
.list(target)
|
|
235
|
-
.filter((name) => showHidden || !name.startsWith("."));
|
|
236
|
-
|
|
237
|
-
const rendered = longFormat
|
|
238
|
-
? longListing(shell.vfs, target, items)
|
|
239
|
-
: shortListing(shell.vfs, target, items);
|
|
240
|
-
|
|
241
|
-
return { stdout: `${rendered}\n`, exitCode: 0 };
|
|
242
|
-
},
|
|
243
|
-
};
|