typescript-virtual-container 1.2.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +0 -1
- package/README.md +141 -50
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +32 -16
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +192 -43
- package/dist/VirtualShell/index.d.ts +10 -4
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +18 -7
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +32 -9
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +10 -2
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +5 -0
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +34 -6
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +5 -0
- package/dist/commands/dpkg.d.ts.map +1 -1
- package/dist/commands/dpkg.js +24 -7
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +13 -4
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +5 -0
- package/dist/commands/free.d.ts.map +1 -1
- package/dist/commands/free.js +5 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +5 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -17
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -340
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +3 -1
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +8 -2
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.js +1 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +3 -1
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +4 -1
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +57 -36
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -2
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +46 -24
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +1 -1
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +4 -1
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +32 -7
- package/dist/commands/which.d.ts.map +1 -1
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -9
- package/dist/modules/linuxRootfs.d.ts +18 -1
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +160 -17
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +2 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +104 -18
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +208 -49
- package/src/VirtualShell/index.ts +35 -7
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +7 -2
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +5 -1
- package/src/commands/apt.ts +47 -17
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +13 -5
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +5 -0
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +56 -12
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +33 -11
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +22 -9
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +9 -2
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/history.ts +7 -2
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -360
- package/src/commands/ls.ts +5 -3
- package/src/commands/lsb-release.ts +8 -2
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +1 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +6 -2
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +21 -9
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +159 -55
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +15 -3
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +61 -26
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +6 -3
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +4 -1
- package/src/commands/wget.ts +51 -12
- package/src/commands/which.ts +5 -2
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +23 -24
- package/src/modules/linuxRootfs.ts +233 -30
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +253 -191
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +415 -5
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
package/src/commands/curl.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { ifFlag, parseArgs } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* HTTP client wrapper using `fetch()` semantics (virtual curl).
|
|
7
|
+
* @category network
|
|
8
|
+
* @params ["[options] <url>"]
|
|
9
|
+
*/
|
|
5
10
|
export const curlCommand: ShellModule = {
|
|
6
11
|
name: "curl",
|
|
7
12
|
description: "Transfer data from or to a server (pure fetch)",
|
|
@@ -9,7 +14,18 @@ export const curlCommand: ShellModule = {
|
|
|
9
14
|
params: ["[options] <url>"],
|
|
10
15
|
run: async ({ authUser, cwd, args, shell }) => {
|
|
11
16
|
const { flagsWithValues, positionals } = parseArgs(args, {
|
|
12
|
-
flagsWithValue: [
|
|
17
|
+
flagsWithValue: [
|
|
18
|
+
"-o",
|
|
19
|
+
"--output",
|
|
20
|
+
"-X",
|
|
21
|
+
"--request",
|
|
22
|
+
"-d",
|
|
23
|
+
"--data",
|
|
24
|
+
"-H",
|
|
25
|
+
"--header",
|
|
26
|
+
"-u",
|
|
27
|
+
"--user",
|
|
28
|
+
],
|
|
13
29
|
});
|
|
14
30
|
|
|
15
31
|
if (ifFlag(args, ["--help", "-h"])) {
|
|
@@ -32,19 +48,31 @@ export const curlCommand: ShellModule = {
|
|
|
32
48
|
const url = positionals[0];
|
|
33
49
|
if (!url) return { stderr: "curl: no URL specified", exitCode: 1 };
|
|
34
50
|
|
|
35
|
-
const outputPath =
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
51
|
+
const outputPath =
|
|
52
|
+
flagsWithValues.get("-o") ?? flagsWithValues.get("--output") ?? null;
|
|
53
|
+
const method = (
|
|
54
|
+
flagsWithValues.get("-X") ??
|
|
55
|
+
flagsWithValues.get("--request") ??
|
|
56
|
+
"GET"
|
|
57
|
+
).toUpperCase();
|
|
58
|
+
const postData =
|
|
59
|
+
flagsWithValues.get("-d") ?? flagsWithValues.get("--data") ?? null;
|
|
60
|
+
const headerRaw =
|
|
61
|
+
flagsWithValues.get("-H") ?? flagsWithValues.get("--header") ?? null;
|
|
39
62
|
const silent = ifFlag(args, ["-s", "--silent"]);
|
|
40
63
|
const headOnly = ifFlag(args, ["-I", "--head"]);
|
|
41
64
|
const followRedirects = ifFlag(args, ["-L", "--location"]);
|
|
42
65
|
const verbose = ifFlag(args, ["-v", "--verbose"]);
|
|
43
66
|
|
|
44
|
-
const extraHeaders: Record<string, string> = {
|
|
67
|
+
const extraHeaders: Record<string, string> = {
|
|
68
|
+
"User-Agent": "curl/7.88.1",
|
|
69
|
+
};
|
|
45
70
|
if (headerRaw) {
|
|
46
71
|
const idx = headerRaw.indexOf(":");
|
|
47
|
-
if (idx !== -1)
|
|
72
|
+
if (idx !== -1)
|
|
73
|
+
extraHeaders[headerRaw.slice(0, idx).trim()] = headerRaw
|
|
74
|
+
.slice(idx + 1)
|
|
75
|
+
.trim();
|
|
48
76
|
}
|
|
49
77
|
|
|
50
78
|
const finalMethod = postData && method === "GET" ? "POST" : method;
|
|
@@ -61,7 +89,10 @@ export const curlCommand: ShellModule = {
|
|
|
61
89
|
const stderrLines: string[] = [];
|
|
62
90
|
if (verbose) {
|
|
63
91
|
stderrLines.push(`* Trying ${url}...`, `* Connected`);
|
|
64
|
-
stderrLines.push(
|
|
92
|
+
stderrLines.push(
|
|
93
|
+
`> ${finalMethod} / HTTP/1.1`,
|
|
94
|
+
`> Host: ${new URL(url).host}`,
|
|
95
|
+
);
|
|
65
96
|
}
|
|
66
97
|
|
|
67
98
|
let response: Response;
|
|
@@ -69,7 +100,10 @@ export const curlCommand: ShellModule = {
|
|
|
69
100
|
response = await fetch(url, fetchOpts);
|
|
70
101
|
} catch (err) {
|
|
71
102
|
const msg = err instanceof Error ? err.message : String(err);
|
|
72
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
stderr: `curl: (6) Could not resolve host: ${msg}`,
|
|
105
|
+
exitCode: 6,
|
|
106
|
+
};
|
|
73
107
|
}
|
|
74
108
|
|
|
75
109
|
if (verbose) {
|
|
@@ -83,14 +117,24 @@ export const curlCommand: ShellModule = {
|
|
|
83
117
|
}
|
|
84
118
|
|
|
85
119
|
let body: string;
|
|
86
|
-
try {
|
|
120
|
+
try {
|
|
121
|
+
body = await response.text();
|
|
122
|
+
} catch {
|
|
123
|
+
return { stderr: "curl: failed to read response body", exitCode: 1 };
|
|
124
|
+
}
|
|
87
125
|
|
|
88
126
|
if (outputPath) {
|
|
89
127
|
const target = resolvePath(cwd, outputPath);
|
|
90
128
|
assertPathAccess(authUser, target, "curl");
|
|
91
129
|
shell.writeFileAsUser(authUser, target, body);
|
|
92
|
-
if (!silent)
|
|
93
|
-
|
|
130
|
+
if (!silent)
|
|
131
|
+
stderrLines.push(
|
|
132
|
+
` % Total % Received\n100 ${body.length} 100 ${body.length}`,
|
|
133
|
+
);
|
|
134
|
+
return {
|
|
135
|
+
stderr: stderrLines.join("\n") || undefined,
|
|
136
|
+
exitCode: response.ok ? 0 : 22,
|
|
137
|
+
};
|
|
94
138
|
}
|
|
95
139
|
|
|
96
140
|
return {
|
package/src/commands/cut.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { getFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Extract selected fields from each line of input.
|
|
6
|
+
* @category text
|
|
7
|
+
* @params ["-d <delim> -f <fields> [file]"]
|
|
8
|
+
*/
|
|
4
9
|
export const cutCommand: ShellModule = {
|
|
5
10
|
name: "cut",
|
|
6
11
|
description: "Remove sections from lines",
|
|
@@ -11,7 +16,9 @@ export const cutCommand: ShellModule = {
|
|
|
11
16
|
const fields = (getFlag(args, ["-f"]) as string | undefined) ?? "1";
|
|
12
17
|
const cols = fields.split(",").map((f) => {
|
|
13
18
|
const [a, b] = f.split("-").map(Number);
|
|
14
|
-
return b !== undefined
|
|
19
|
+
return b !== undefined
|
|
20
|
+
? { from: (a ?? 1) - 1, to: b - 1 }
|
|
21
|
+
: { from: (a ?? 1) - 1, to: (a ?? 1) - 1 };
|
|
15
22
|
});
|
|
16
23
|
const lines = (stdin ?? "").split("\n");
|
|
17
24
|
const out = lines.map((line) => {
|
package/src/commands/date.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Print the current date/time or a formatted representation.
|
|
5
|
+
* @category system
|
|
6
|
+
* @params ["[+format]"]
|
|
7
|
+
*/
|
|
3
8
|
export const dateCommand: ShellModule = {
|
|
4
9
|
name: "date",
|
|
5
10
|
description: "Print current date and time",
|
|
@@ -9,7 +14,8 @@ export const dateCommand: ShellModule = {
|
|
|
9
14
|
const now = new Date();
|
|
10
15
|
const fmt = args[0];
|
|
11
16
|
if (fmt?.startsWith("+")) {
|
|
12
|
-
const f = fmt
|
|
17
|
+
const f = fmt
|
|
18
|
+
.slice(1)
|
|
13
19
|
.replace("%Y", String(now.getFullYear()))
|
|
14
20
|
.replace("%m", String(now.getMonth() + 1).padStart(2, "0"))
|
|
15
21
|
.replace("%d", String(now.getDate()).padStart(2, "0"))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
import { ifFlag } from "./command-helpers";
|
|
3
|
+
|
|
4
|
+
export const declareCommand: ShellModule = {
|
|
5
|
+
name: "declare",
|
|
6
|
+
aliases: ["local", "typeset"],
|
|
7
|
+
description: "Declare variables and give them attributes",
|
|
8
|
+
category: "shell",
|
|
9
|
+
params: ["[-i] [-r] [-x] [-a] [name[=value]...]"],
|
|
10
|
+
run: ({ args, env }) => {
|
|
11
|
+
if (!env) return { exitCode: 0 };
|
|
12
|
+
|
|
13
|
+
const integer = ifFlag(args, ["-i"]);
|
|
14
|
+
const _readonly = ifFlag(args, ["-r"]);
|
|
15
|
+
const _export_ = ifFlag(args, ["-x"]);
|
|
16
|
+
const printAll = args.filter((a) => !a.startsWith("-")).length === 0;
|
|
17
|
+
|
|
18
|
+
if (printAll) {
|
|
19
|
+
const lines = Object.entries(env.vars).map(
|
|
20
|
+
([k, v]) => `declare -- ${k}="${v}"`,
|
|
21
|
+
);
|
|
22
|
+
return { stdout: lines.join("\n"), exitCode: 0 };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const assignments = args.filter((a) => !a.startsWith("-"));
|
|
26
|
+
for (const token of assignments) {
|
|
27
|
+
const eq = token.indexOf("=");
|
|
28
|
+
if (eq === -1) {
|
|
29
|
+
// Just declare (no value)
|
|
30
|
+
if (!(token in env.vars)) env.vars[token] = "";
|
|
31
|
+
} else {
|
|
32
|
+
const name = token.slice(0, eq);
|
|
33
|
+
let val = token.slice(eq + 1);
|
|
34
|
+
if (integer) {
|
|
35
|
+
const n = parseInt(val, 10);
|
|
36
|
+
val = Number.isNaN(n) ? "0" : String(n);
|
|
37
|
+
}
|
|
38
|
+
env.vars[name] = val;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { exitCode: 0 };
|
|
43
|
+
},
|
|
44
|
+
};
|
package/src/commands/diff.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { resolvePath } from "./helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Compare files line-by-line and print differing lines.
|
|
6
|
+
* @category text
|
|
7
|
+
* @params ["<file1> <file2>"]
|
|
8
|
+
*/
|
|
4
9
|
export const diffCommand: ShellModule = {
|
|
5
10
|
name: "diff",
|
|
6
11
|
description: "Compare files line by line",
|
|
@@ -12,13 +17,22 @@ export const diffCommand: ShellModule = {
|
|
|
12
17
|
const p1 = resolvePath(cwd, f1);
|
|
13
18
|
const p2 = resolvePath(cwd, f2);
|
|
14
19
|
let a: string[], b: string[];
|
|
15
|
-
try {
|
|
16
|
-
|
|
20
|
+
try {
|
|
21
|
+
a = shell.vfs.readFile(p1).split("\n");
|
|
22
|
+
} catch {
|
|
23
|
+
return { stderr: `diff: ${f1}: No such file or directory`, exitCode: 2 };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
b = shell.vfs.readFile(p2).split("\n");
|
|
27
|
+
} catch {
|
|
28
|
+
return { stderr: `diff: ${f2}: No such file or directory`, exitCode: 2 };
|
|
29
|
+
}
|
|
17
30
|
|
|
18
31
|
const out: string[] = [];
|
|
19
32
|
const max = Math.max(a.length, b.length);
|
|
20
33
|
for (let i = 0; i < max; i++) {
|
|
21
|
-
const la = a[i];
|
|
34
|
+
const la = a[i];
|
|
35
|
+
const lb = b[i];
|
|
22
36
|
if (la !== lb) {
|
|
23
37
|
if (la !== undefined) out.push(`< ${la}`);
|
|
24
38
|
if (lb !== undefined) out.push(`> ${lb}`);
|
package/src/commands/dpkg.ts
CHANGED
|
@@ -2,14 +2,20 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { ifFlag, parseArgs } from "./command-helpers";
|
|
3
3
|
import { getPackageManager } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* dpkg compatibility command (query/remove/list) backed by the virtual package manager.
|
|
7
|
+
* @category package
|
|
8
|
+
* @params ["[-l] [-s pkg] [-L pkg] [-i pkg] [--remove pkg]"]
|
|
9
|
+
*/
|
|
5
10
|
export const dpkgCommand: ShellModule = {
|
|
6
11
|
name: "dpkg",
|
|
7
12
|
description: "Debian package manager low-level tool",
|
|
8
|
-
category: "
|
|
13
|
+
category: "package",
|
|
9
14
|
params: ["[-l] [-s pkg] [-L pkg] [-i pkg] [--remove pkg]"],
|
|
10
15
|
run: ({ args, authUser, shell }) => {
|
|
11
16
|
const pm = getPackageManager(shell);
|
|
12
|
-
if (!pm)
|
|
17
|
+
if (!pm)
|
|
18
|
+
return { stderr: "dpkg: package manager not initialised", exitCode: 1 };
|
|
13
19
|
|
|
14
20
|
const listFlag = ifFlag(args, ["-l", "--list"]);
|
|
15
21
|
const statusFlag = ifFlag(args, ["-s", "--status"]);
|
|
@@ -18,7 +24,18 @@ export const dpkgCommand: ShellModule = {
|
|
|
18
24
|
const purgeFlag = ifFlag(args, ["-P", "--purge"]);
|
|
19
25
|
|
|
20
26
|
const { positionals } = parseArgs(args, {
|
|
21
|
-
flags: [
|
|
27
|
+
flags: [
|
|
28
|
+
"-l",
|
|
29
|
+
"--list",
|
|
30
|
+
"-s",
|
|
31
|
+
"--status",
|
|
32
|
+
"-L",
|
|
33
|
+
"--listfiles",
|
|
34
|
+
"-r",
|
|
35
|
+
"--remove",
|
|
36
|
+
"-P",
|
|
37
|
+
"--purge",
|
|
38
|
+
],
|
|
22
39
|
});
|
|
23
40
|
|
|
24
41
|
if (listFlag) {
|
|
@@ -58,7 +75,8 @@ export const dpkgCommand: ShellModule = {
|
|
|
58
75
|
|
|
59
76
|
if (statusFlag) {
|
|
60
77
|
const pkgName = positionals[0];
|
|
61
|
-
if (!pkgName)
|
|
78
|
+
if (!pkgName)
|
|
79
|
+
return { stderr: "dpkg: -s needs a package name", exitCode: 1 };
|
|
62
80
|
const info = pm.show(pkgName);
|
|
63
81
|
if (!info)
|
|
64
82
|
return {
|
|
@@ -70,7 +88,8 @@ export const dpkgCommand: ShellModule = {
|
|
|
70
88
|
|
|
71
89
|
if (listFilesFlag) {
|
|
72
90
|
const pkgName = positionals[0];
|
|
73
|
-
if (!pkgName)
|
|
91
|
+
if (!pkgName)
|
|
92
|
+
return { stderr: "dpkg: -L needs a package name", exitCode: 1 };
|
|
74
93
|
const installed = pm.listInstalled().find((p) => p.name === pkgName);
|
|
75
94
|
if (!installed)
|
|
76
95
|
return {
|
|
@@ -85,7 +104,8 @@ export const dpkgCommand: ShellModule = {
|
|
|
85
104
|
if (removeFlag || purgeFlag) {
|
|
86
105
|
if (authUser !== "root")
|
|
87
106
|
return {
|
|
88
|
-
stderr:
|
|
107
|
+
stderr:
|
|
108
|
+
"dpkg: error: requested operation requires superuser privilege",
|
|
89
109
|
exitCode: 2,
|
|
90
110
|
};
|
|
91
111
|
if (positionals.length === 0)
|
|
@@ -114,11 +134,15 @@ export const dpkgCommand: ShellModule = {
|
|
|
114
134
|
export const dpkgQueryCommand: ShellModule = {
|
|
115
135
|
name: "dpkg-query",
|
|
116
136
|
description: "Show information about installed packages",
|
|
117
|
-
category: "
|
|
137
|
+
category: "package",
|
|
118
138
|
params: ["-W [pkg] | -l [pattern]"],
|
|
119
139
|
run: ({ args, shell }) => {
|
|
120
140
|
const pm = getPackageManager(shell);
|
|
121
|
-
if (!pm)
|
|
141
|
+
if (!pm)
|
|
142
|
+
return {
|
|
143
|
+
stderr: "dpkg-query: package manager not initialised",
|
|
144
|
+
exitCode: 1,
|
|
145
|
+
};
|
|
122
146
|
|
|
123
147
|
const listFlag = ifFlag(args, ["-l"]);
|
|
124
148
|
const showFlag = ifFlag(args, ["-W", "--show"]);
|
|
@@ -135,9 +159,7 @@ export const dpkgQueryCommand: ShellModule = {
|
|
|
135
159
|
|
|
136
160
|
if (showFlag) {
|
|
137
161
|
return {
|
|
138
|
-
stdout: filtered
|
|
139
|
-
.map((p) => `${p.name}\t${p.version}`)
|
|
140
|
-
.join("\n"),
|
|
162
|
+
stdout: filtered.map((p) => `${p.name}\t${p.version}`).join("\n"),
|
|
141
163
|
exitCode: 0,
|
|
142
164
|
};
|
|
143
165
|
}
|
package/src/commands/du.ts
CHANGED
|
@@ -13,22 +13,34 @@ export const duCommand: ShellModule = {
|
|
|
13
13
|
const target = args.find((a) => !a.startsWith("-")) ?? ".";
|
|
14
14
|
const p = resolvePath(cwd, target);
|
|
15
15
|
|
|
16
|
-
const fmt = (b: number) =>
|
|
16
|
+
const fmt = (b: number) =>
|
|
17
|
+
human ? `${(b / 1024).toFixed(1)}K` : String(Math.ceil(b / 1024));
|
|
17
18
|
|
|
18
|
-
if (!shell.vfs.exists(p))
|
|
19
|
+
if (!shell.vfs.exists(p))
|
|
20
|
+
return {
|
|
21
|
+
stderr: `du: ${target}: No such file or directory`,
|
|
22
|
+
exitCode: 1,
|
|
23
|
+
};
|
|
19
24
|
|
|
20
25
|
if (summary || shell.vfs.stat(p).type === "file") {
|
|
21
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
stdout: `${fmt(shell.vfs.getUsageBytes(p))}\t${target}`,
|
|
28
|
+
exitCode: 0,
|
|
29
|
+
};
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
const lines: string[] = [];
|
|
25
33
|
const walk = (dir: string, rel: string) => {
|
|
26
34
|
let total = 0;
|
|
27
35
|
for (const e of shell.vfs.list(dir)) {
|
|
28
|
-
const full = `${dir}/${e}`,
|
|
36
|
+
const full = `${dir}/${e}`,
|
|
37
|
+
r = `${rel}/${e}`;
|
|
29
38
|
const st = shell.vfs.stat(full);
|
|
30
39
|
if (st.type === "directory") total += walk(full, r);
|
|
31
|
-
else {
|
|
40
|
+
else {
|
|
41
|
+
total += st.size;
|
|
42
|
+
if (!summary) lines.push(`${fmt(st.size)}\t${r}`);
|
|
43
|
+
}
|
|
32
44
|
}
|
|
33
45
|
lines.push(`${fmt(total)}\t${rel}`);
|
|
34
46
|
return total;
|
package/src/commands/echo.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { parseArgs } from "./command-helpers";
|
|
3
|
+
import { expandSync } from "../utils/expand";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Expand escape sequences for `echo -e`.
|
|
@@ -15,27 +16,39 @@ function expandEscapes(text: string): string {
|
|
|
15
16
|
.replace(/\\b/g, "\x08")
|
|
16
17
|
.replace(/\\f/g, "\x0C")
|
|
17
18
|
.replace(/\\v/g, "\x0B")
|
|
18
|
-
.replace(/\\0(\d{1,3})/g, (_, oct) =>
|
|
19
|
+
.replace(/\\0(\d{1,3})/g, (_, oct) =>
|
|
20
|
+
String.fromCharCode(parseInt(oct, 8)),
|
|
21
|
+
);
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Echo text to stdout with shell-style expansion and escape support.
|
|
26
|
+
* @category shell
|
|
27
|
+
* @params ["[-n] [-e] [text...]"]
|
|
28
|
+
*/
|
|
21
29
|
export const echoCommand: ShellModule = {
|
|
22
30
|
name: "echo",
|
|
23
31
|
description: "Display text",
|
|
24
32
|
category: "shell",
|
|
25
33
|
params: ["[-n] [-e] [text...]"],
|
|
26
34
|
run: ({ args, stdin, env }) => {
|
|
27
|
-
const { flags, positionals } = parseArgs(args, {
|
|
35
|
+
const { flags, positionals } = parseArgs(args, {
|
|
36
|
+
flags: ["-n", "-e", "-E"],
|
|
37
|
+
});
|
|
28
38
|
const noNewline = flags.has("-n");
|
|
29
|
-
const escapes
|
|
39
|
+
const escapes = flags.has("-e");
|
|
30
40
|
|
|
31
|
-
const rawText =
|
|
41
|
+
const rawText =
|
|
42
|
+
positionals.length > 0 ? positionals.join(" ") : (stdin ?? "");
|
|
32
43
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
// Full expansion: $? ${#VAR} $((expr)) ~ ${VAR:-def} $VAR etc.
|
|
45
|
+
// $(cmd) is already resolved upstream by runCommand before echo.run is called.
|
|
46
|
+
const expanded = expandSync(
|
|
47
|
+
rawText,
|
|
48
|
+
env?.vars ?? {},
|
|
49
|
+
env?.lastExitCode ?? 0,
|
|
36
50
|
);
|
|
37
|
-
|
|
38
|
-
const text = escapes ? expandEscapes(varsExpanded) : varsExpanded;
|
|
51
|
+
const text = escapes ? expandEscapes(expanded) : expanded;
|
|
39
52
|
|
|
40
53
|
return {
|
|
41
54
|
stdout: noNewline ? text : `${text}\n`,
|
package/src/commands/env.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/** biome-ignore-all lint/style/useNamingConvention: ENV VARS */
|
|
2
2
|
import type { ShellModule } from "../types/commands";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Print environment variables for the current session.
|
|
6
|
+
* @category shell
|
|
7
|
+
* @params []
|
|
8
|
+
*/
|
|
4
9
|
export const envCommand: ShellModule = {
|
|
5
10
|
name: "env",
|
|
6
11
|
description: "Print environment variables",
|
|
@@ -8,6 +13,11 @@ export const envCommand: ShellModule = {
|
|
|
8
13
|
params: [],
|
|
9
14
|
run: ({ env, authUser }) => {
|
|
10
15
|
const vars = { ...env.vars, USER: authUser, HOME: `/home/${authUser}` };
|
|
11
|
-
return {
|
|
16
|
+
return {
|
|
17
|
+
stdout: Object.entries(vars)
|
|
18
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
19
|
+
.join("\n"),
|
|
20
|
+
exitCode: 0,
|
|
21
|
+
};
|
|
12
22
|
},
|
|
13
23
|
};
|
package/src/commands/exit.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Exit the current shell session (closeSession flag).
|
|
5
|
+
* @category shell
|
|
6
|
+
* @params ["[code]"]
|
|
7
|
+
*/
|
|
3
8
|
export const exitCommand: ShellModule = {
|
|
4
9
|
name: "exit",
|
|
5
10
|
aliases: ["bye"],
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
description: "Exit the shell session",
|
|
12
|
+
category: "shell",
|
|
13
|
+
params: ["[code]"],
|
|
14
|
+
run: ({ args }) => ({
|
|
15
|
+
closeSession: true,
|
|
16
|
+
exitCode: parseInt(args[0] ?? "0", 10) || 0,
|
|
17
|
+
}),
|
|
8
18
|
};
|
package/src/commands/export.ts
CHANGED
|
@@ -7,7 +7,9 @@ export const exportCommand: ShellModule = {
|
|
|
7
7
|
params: ["[VAR=value]"],
|
|
8
8
|
run: ({ args, env }) => {
|
|
9
9
|
if (args.length === 0) {
|
|
10
|
-
const out = Object.entries(env.vars)
|
|
10
|
+
const out = Object.entries(env.vars)
|
|
11
|
+
.map(([k, v]) => `declare -x ${k}="${v}"`)
|
|
12
|
+
.join("\n");
|
|
11
13
|
return { stdout: out, exitCode: 0 };
|
|
12
14
|
}
|
|
13
15
|
for (const arg of args) {
|
package/src/commands/find.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { getFlag } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Find files and directories by name and type with minimal pattern support.
|
|
7
|
+
* @category files
|
|
8
|
+
* @params ["[path] [-name <pattern>] [-type f|d]"]
|
|
9
|
+
*/
|
|
5
10
|
export const findCommand: ShellModule = {
|
|
6
11
|
name: "find",
|
|
7
12
|
description: "Search for files",
|
package/src/commands/free.ts
CHANGED
|
@@ -2,6 +2,11 @@ import * as os from "node:os";
|
|
|
2
2
|
import type { ShellModule } from "../types/commands";
|
|
3
3
|
import { ifFlag } from "./command-helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Display memory usage information (human / MB / GB options).
|
|
7
|
+
* @category system
|
|
8
|
+
* @params ["[-h] [-m] [-g]"]
|
|
9
|
+
*/
|
|
5
10
|
export const freeCommand: ShellModule = {
|
|
6
11
|
name: "free",
|
|
7
12
|
description: "Display amount of free and used memory",
|
|
@@ -22,8 +27,10 @@ export const freeCommand: ShellModule = {
|
|
|
22
27
|
|
|
23
28
|
const fmt = (bytes: number): string => {
|
|
24
29
|
if (human) {
|
|
25
|
-
if (bytes >= 1024 * 1024 * 1024)
|
|
26
|
-
|
|
30
|
+
if (bytes >= 1024 * 1024 * 1024)
|
|
31
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
32
|
+
if (bytes >= 1024 * 1024)
|
|
33
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
27
34
|
return `${(bytes / 1024).toFixed(1)}K`;
|
|
28
35
|
}
|
|
29
36
|
if (gb) return String(Math.floor(bytes / (1024 * 1024 * 1024)));
|
package/src/commands/grep.ts
CHANGED
|
@@ -2,13 +2,20 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { parseArgs } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Search for a regex pattern in files or stdin with common flags.
|
|
7
|
+
* @category text
|
|
8
|
+
* @params ["[-i] [-v] [-n] [-r] <pattern> [file...]"]
|
|
9
|
+
*/
|
|
5
10
|
export const grepCommand: ShellModule = {
|
|
6
11
|
name: "grep",
|
|
7
12
|
description: "Search text patterns",
|
|
8
13
|
category: "text",
|
|
9
14
|
params: ["[-i] [-v] [-n] [-r] <pattern> [file...]"],
|
|
10
15
|
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
11
|
-
const { flags, positionals } = parseArgs(args, {
|
|
16
|
+
const { flags, positionals } = parseArgs(args, {
|
|
17
|
+
flags: ["-i", "-v", "-n", "-r"],
|
|
18
|
+
});
|
|
12
19
|
const caseInsensitive = flags.has("-i");
|
|
13
20
|
const invertMatch = flags.has("-v");
|
|
14
21
|
const showLineNumbers = flags.has("-n");
|
|
@@ -80,7 +87,10 @@ export const grepCommand: ShellModule = {
|
|
|
80
87
|
const prefix = resolvedPaths.length > 1 ? `${file}:` : "";
|
|
81
88
|
results.push(...matchLines(content, prefix));
|
|
82
89
|
} catch {
|
|
83
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
stderr: `grep: ${file}: No such file or directory`,
|
|
92
|
+
exitCode: 1,
|
|
93
|
+
};
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
}
|
package/src/commands/gzip.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { resolvePath } from "./helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Compress files using gzip (stores in VFS as compressed content).
|
|
6
|
+
* @category archive
|
|
7
|
+
* @params ["<file>"]
|
|
8
|
+
*/
|
|
4
9
|
export const gzipCommand: ShellModule = {
|
|
5
10
|
name: "gzip",
|
|
6
11
|
description: "Compress files",
|
|
@@ -10,12 +15,24 @@ export const gzipCommand: ShellModule = {
|
|
|
10
15
|
const file = args[0];
|
|
11
16
|
if (!file) return { stderr: "gzip: no file specified", exitCode: 1 };
|
|
12
17
|
const p = resolvePath(cwd, file);
|
|
13
|
-
try {
|
|
14
|
-
|
|
18
|
+
try {
|
|
19
|
+
shell.vfs.compressFile(p);
|
|
20
|
+
return { exitCode: 0 };
|
|
21
|
+
} catch {
|
|
22
|
+
return {
|
|
23
|
+
stderr: `gzip: ${file}: No such file or directory`,
|
|
24
|
+
exitCode: 1,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
15
27
|
},
|
|
16
28
|
};
|
|
17
29
|
|
|
18
30
|
export const gunzipCommand: ShellModule = {
|
|
31
|
+
/**
|
|
32
|
+
* Decompress gzip files (or zcat alias).
|
|
33
|
+
* @category archive
|
|
34
|
+
* @params ["<file>"]
|
|
35
|
+
*/
|
|
19
36
|
name: "gunzip",
|
|
20
37
|
description: "Decompress files",
|
|
21
38
|
category: "archive",
|
|
@@ -25,7 +42,14 @@ export const gunzipCommand: ShellModule = {
|
|
|
25
42
|
const file = args[0];
|
|
26
43
|
if (!file) return { stderr: "gunzip: no file specified", exitCode: 1 };
|
|
27
44
|
const p = resolvePath(cwd, file);
|
|
28
|
-
try {
|
|
29
|
-
|
|
45
|
+
try {
|
|
46
|
+
shell.vfs.decompressFile(p);
|
|
47
|
+
return { exitCode: 0 };
|
|
48
|
+
} catch {
|
|
49
|
+
return {
|
|
50
|
+
stderr: `gunzip: ${file}: No such file or directory`,
|
|
51
|
+
exitCode: 1,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
30
54
|
},
|
|
31
55
|
};
|
package/src/commands/head.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { getFlag } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Output the first part of files or stdin (head).
|
|
7
|
+
* @category text
|
|
8
|
+
* @params ["[-n <lines>] [file...]"]
|
|
9
|
+
*/
|
|
5
10
|
export const headCommand: ShellModule = {
|
|
6
11
|
name: "head",
|
|
7
12
|
description: "Output first lines",
|