typescript-virtual-container 1.3.4 → 1.4.1
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 +674 -1504
- package/benchmark-results.txt +21 -21
- package/builds/self-standalone.js +282 -332
- package/builds/self-standalone.js.map +4 -4
- package/builds/standalone-wo-sftp.js +218 -282
- package/builds/standalone-wo-sftp.js.map +4 -4
- package/builds/standalone.js +271 -335
- package/builds/standalone.js.map +4 -4
- package/builds/web-full-api.min.js +3 -3
- package/builds/web-full-api.min.js.map +4 -4
- package/builds/web.min.js +2 -2
- package/builds/web.min.js.map +4 -4
- package/bun.lock +14 -12
- package/dist/SSHClient/index.d.ts.map +1 -1
- package/dist/SSHClient/index.js +5 -3
- package/dist/SSHMimic/executor.d.ts +1 -3
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +20 -22
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +5 -3
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +26 -21
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +29 -1
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +25 -3
- package/dist/VirtualShell/shellParser.d.ts +1 -8
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +2 -81
- package/dist/VirtualUserManager/index.d.ts +7 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +47 -16
- package/dist/commands/adduser.d.ts +10 -4
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +75 -12
- 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 +5 -0
- package/dist/commands/awk.d.ts +10 -8
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +156 -28
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +0 -3
- package/dist/commands/clear.d.ts +5 -0
- package/dist/commands/clear.d.ts.map +1 -1
- package/dist/commands/clear.js +5 -0
- package/dist/commands/command-helpers.d.ts.map +1 -1
- package/dist/commands/command-helpers.js +8 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +2 -1
- package/dist/commands/declare.d.ts +5 -0
- package/dist/commands/declare.d.ts.map +1 -1
- package/dist/commands/declare.js +5 -0
- package/dist/commands/deluser.d.ts +12 -0
- package/dist/commands/deluser.d.ts.map +1 -1
- package/dist/commands/deluser.js +72 -6
- package/dist/commands/df.d.ts +5 -0
- package/dist/commands/df.d.ts.map +1 -1
- package/dist/commands/df.js +5 -0
- package/dist/commands/du.d.ts +5 -0
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +5 -0
- package/dist/commands/export.d.ts +5 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +22 -4
- package/dist/commands/groups.d.ts +5 -0
- package/dist/commands/groups.d.ts.map +1 -1
- package/dist/commands/groups.js +5 -0
- package/dist/commands/gzip.d.ts +5 -2
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +54 -28
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +12 -3
- package/dist/commands/htop.d.ts +5 -0
- package/dist/commands/htop.d.ts.map +1 -1
- package/dist/commands/htop.js +5 -0
- package/dist/commands/kill.d.ts +5 -0
- package/dist/commands/kill.d.ts.map +1 -1
- package/dist/commands/kill.js +5 -0
- package/dist/commands/ln.d.ts +2 -0
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +22 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +15 -0
- package/dist/commands/lsb-release.d.ts +5 -0
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +5 -0
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +30 -136
- package/dist/commands/mkdir.d.ts +5 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +5 -0
- package/dist/commands/mv.d.ts +5 -0
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +5 -0
- package/dist/commands/nano.d.ts +5 -0
- package/dist/commands/nano.d.ts.map +1 -1
- package/dist/commands/nano.js +5 -0
- package/dist/commands/neofetch.d.ts +5 -0
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +14 -5
- package/dist/commands/passwd.d.ts +8 -0
- package/dist/commands/passwd.d.ts.map +1 -1
- package/dist/commands/passwd.js +32 -11
- package/dist/commands/ping.d.ts +5 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +5 -0
- package/dist/commands/printf.d.ts +5 -0
- package/dist/commands/printf.d.ts.map +1 -1
- package/dist/commands/printf.js +43 -12
- package/dist/commands/ps.d.ts +5 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +5 -0
- package/dist/commands/read.d.ts +5 -0
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +5 -0
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +4 -1
- package/dist/commands/rm.d.ts +5 -0
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +5 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +1 -57
- package/dist/commands/sed.d.ts +5 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +5 -0
- package/dist/commands/set.d.ts +5 -6
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +5 -22
- package/dist/commands/sh.d.ts +6 -0
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +6 -0
- package/dist/commands/shift.d.ts +10 -0
- package/dist/commands/shift.d.ts.map +1 -1
- package/dist/commands/shift.js +10 -0
- package/dist/commands/sleep.d.ts +5 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sleep.js +5 -0
- package/dist/commands/sort.d.ts +5 -0
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +5 -0
- package/dist/commands/source.d.ts +5 -0
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -0
- package/dist/commands/stat.d.ts +7 -0
- package/dist/commands/stat.d.ts.map +1 -0
- package/dist/commands/stat.js +56 -0
- package/dist/commands/su.d.ts +13 -0
- package/dist/commands/su.d.ts.map +1 -1
- package/dist/commands/su.js +45 -14
- package/dist/commands/sudo.d.ts.map +1 -1
- package/dist/commands/sudo.js +5 -0
- package/dist/commands/tail.d.ts +5 -0
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +15 -3
- package/dist/commands/tar.d.ts +5 -0
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +40 -10
- package/dist/commands/tee.d.ts +5 -0
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +5 -0
- package/dist/commands/touch.d.ts +5 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +5 -0
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +45 -10
- package/dist/commands/tree.d.ts +5 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +5 -0
- package/dist/commands/true.d.ts +10 -0
- package/dist/commands/true.d.ts.map +1 -1
- package/dist/commands/true.js +10 -0
- package/dist/commands/type.d.ts +5 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +5 -0
- package/dist/commands/uname.d.ts +5 -0
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +5 -0
- package/dist/commands/uniq.d.ts +5 -0
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uniq.js +5 -0
- package/dist/commands/unset.d.ts +5 -0
- package/dist/commands/unset.d.ts.map +1 -1
- package/dist/commands/unset.js +5 -0
- package/dist/commands/uptime.d.ts +5 -0
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +5 -0
- package/dist/commands/wc.d.ts +5 -0
- package/dist/commands/wc.d.ts.map +1 -1
- package/dist/commands/wc.js +5 -0
- package/dist/commands/wget.d.ts +5 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +16 -1
- package/dist/commands/who.d.ts +5 -0
- package/dist/commands/who.d.ts.map +1 -1
- package/dist/commands/who.js +5 -0
- package/dist/commands/whoami.d.ts +5 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +5 -0
- package/dist/commands/xargs.d.ts +5 -0
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +5 -0
- package/dist/self-standalone.js +254 -30
- package/dist/types/commands.d.ts +36 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/tokenize.d.ts +20 -0
- package/dist/utils/tokenize.d.ts.map +1 -0
- package/dist/utils/tokenize.js +74 -0
- package/examples/web.min.js +2 -2
- package/package.json +2 -2
- package/src/SSHClient/index.ts +6 -3
- package/src/SSHMimic/executor.ts +21 -44
- package/src/SSHMimic/index.ts +7 -5
- package/src/SSHMimic/sftp.ts +28 -21
- package/src/VirtualPackageManager/index.ts +29 -1
- package/src/VirtualShell/shell.ts +34 -4
- package/src/VirtualShell/shellParser.ts +2 -103
- package/src/VirtualUserManager/index.ts +43 -19
- package/src/commands/adduser.ts +86 -13
- package/src/commands/alias.ts +5 -0
- package/src/commands/apt.ts +5 -0
- package/src/commands/awk.ts +154 -29
- package/src/commands/cd.ts +0 -4
- package/src/commands/clear.ts +5 -0
- package/src/commands/command-helpers.ts +9 -0
- package/src/commands/curl.ts +2 -1
- package/src/commands/declare.ts +5 -0
- package/src/commands/deluser.ts +84 -7
- package/src/commands/df.ts +5 -0
- package/src/commands/du.ts +5 -0
- package/src/commands/export.ts +5 -0
- package/src/commands/grep.ts +21 -8
- package/src/commands/groups.ts +5 -0
- package/src/commands/gzip.ts +61 -28
- package/src/commands/head.ts +14 -4
- package/src/commands/htop.ts +5 -0
- package/src/commands/kill.ts +5 -0
- package/src/commands/ln.ts +22 -0
- package/src/commands/ls.ts +17 -0
- package/src/commands/lsb-release.ts +5 -0
- package/src/commands/man.ts +38 -143
- package/src/commands/manuals/adduser.txt +11 -0
- package/src/commands/manuals/apt-cache.txt +12 -0
- package/src/commands/manuals/apt.txt +20 -0
- package/src/commands/manuals/awk.txt +13 -0
- package/src/commands/manuals/cat.txt +14 -0
- package/src/commands/manuals/cd.txt +16 -0
- package/src/commands/manuals/chmod.txt +16 -0
- package/src/commands/manuals/clear.txt +10 -0
- package/src/commands/manuals/cp.txt +10 -0
- package/src/commands/manuals/curl.txt +20 -0
- package/src/commands/manuals/date.txt +14 -0
- package/src/commands/manuals/declare.txt +12 -0
- package/src/commands/manuals/deluser.txt +10 -0
- package/src/commands/manuals/df.txt +10 -0
- package/src/commands/manuals/dpkg-query.txt +11 -0
- package/src/commands/manuals/dpkg.txt +14 -0
- package/src/commands/manuals/du.txt +11 -0
- package/src/commands/manuals/echo.txt +11 -0
- package/src/commands/manuals/false.txt +10 -0
- package/src/commands/manuals/find.txt +11 -0
- package/src/commands/manuals/free.txt +12 -0
- package/src/commands/manuals/grep.txt +13 -0
- package/src/commands/manuals/groups.txt +10 -0
- package/src/commands/manuals/gzip.txt +11 -0
- package/src/commands/manuals/head.txt +10 -0
- package/src/commands/manuals/help.txt +11 -0
- package/src/commands/manuals/history.txt +11 -0
- package/src/commands/manuals/hostname.txt +10 -0
- package/src/commands/manuals/id.txt +10 -0
- package/src/commands/manuals/kill.txt +13 -0
- package/src/commands/manuals/ls.txt +20 -0
- package/src/commands/manuals/lsb_release.txt +14 -0
- package/src/commands/manuals/mkdir.txt +10 -0
- package/src/commands/manuals/mv.txt +10 -0
- package/src/commands/manuals/nano.txt +11 -0
- package/src/commands/manuals/neofetch.txt +10 -0
- package/src/commands/manuals/node.txt +13 -0
- package/src/commands/manuals/npm.txt +13 -0
- package/src/commands/manuals/npx.txt +13 -0
- package/src/commands/manuals/passwd.txt +11 -0
- package/src/commands/manuals/ping.txt +10 -0
- package/src/commands/manuals/printf.txt +11 -0
- package/src/commands/manuals/ps.txt +10 -0
- package/src/commands/manuals/pwd.txt +10 -0
- package/src/commands/manuals/python3.txt +13 -0
- package/src/commands/manuals/readlink.txt +10 -0
- package/src/commands/manuals/return.txt +10 -0
- package/src/commands/manuals/rm.txt +10 -0
- package/src/commands/manuals/sed.txt +11 -0
- package/src/commands/manuals/set.txt +11 -0
- package/src/commands/manuals/shift.txt +10 -0
- package/src/commands/manuals/sleep.txt +10 -0
- package/src/commands/manuals/sort.txt +12 -0
- package/src/commands/manuals/source.txt +11 -0
- package/src/commands/manuals/ssh.txt +11 -0
- package/src/commands/manuals/stat.txt +10 -0
- package/src/commands/manuals/su.txt +13 -0
- package/src/commands/manuals/sudo.txt +11 -0
- package/src/commands/manuals/tail.txt +10 -0
- package/src/commands/manuals/tar.txt +19 -0
- package/src/commands/manuals/tee.txt +10 -0
- package/src/commands/manuals/test.txt +11 -0
- package/src/commands/manuals/touch.txt +11 -0
- package/src/commands/manuals/tr.txt +10 -0
- package/src/commands/manuals/trap.txt +10 -0
- package/src/commands/manuals/true.txt +10 -0
- package/src/commands/manuals/type.txt +10 -0
- package/src/commands/manuals/uname.txt +12 -0
- package/src/commands/manuals/uniq.txt +12 -0
- package/src/commands/manuals/unset.txt +10 -0
- package/src/commands/manuals/uptime.txt +11 -0
- package/src/commands/manuals/wc.txt +12 -0
- package/src/commands/manuals/wget.txt +12 -0
- package/src/commands/manuals/which.txt +10 -0
- package/src/commands/manuals/whoami.txt +10 -0
- package/src/commands/manuals/xargs.txt +10 -0
- package/src/commands/mkdir.ts +5 -0
- package/src/commands/mv.ts +5 -0
- package/src/commands/nano.ts +5 -0
- package/src/commands/neofetch.ts +15 -6
- package/src/commands/passwd.ts +35 -12
- package/src/commands/ping.ts +5 -0
- package/src/commands/printf.ts +30 -13
- package/src/commands/ps.ts +5 -0
- package/src/commands/read.ts +5 -0
- package/src/commands/registry.ts +4 -1
- package/src/commands/rm.ts +5 -0
- package/src/commands/runtime.ts +1 -61
- package/src/commands/sed.ts +5 -0
- package/src/commands/set.ts +5 -24
- package/src/commands/sh.ts +9 -3
- package/src/commands/shift.ts +10 -0
- package/src/commands/sleep.ts +5 -0
- package/src/commands/sort.ts +5 -0
- package/src/commands/source.ts +5 -0
- package/src/commands/stat.ts +61 -0
- package/src/commands/su.ts +54 -16
- package/src/commands/sudo.ts +5 -0
- package/src/commands/tail.ts +17 -3
- package/src/commands/tar.ts +38 -15
- package/src/commands/tee.ts +5 -0
- package/src/commands/touch.ts +5 -0
- package/src/commands/tr.ts +54 -10
- package/src/commands/tree.ts +5 -0
- package/src/commands/true.ts +10 -0
- package/src/commands/type.ts +5 -0
- package/src/commands/uname.ts +5 -0
- package/src/commands/uniq.ts +5 -0
- package/src/commands/unset.ts +5 -0
- package/src/commands/uptime.ts +5 -0
- package/src/commands/wc.ts +5 -0
- package/src/commands/wget.ts +17 -1
- package/src/commands/who.ts +5 -0
- package/src/commands/whoami.ts +5 -0
- package/src/commands/xargs.ts +5 -0
- package/src/self-standalone.ts +316 -33
- package/src/types/commands.ts +37 -0
- package/src/utils/tokenize.ts +78 -0
- package/tests/new-features.test.ts +2 -2
- package/builds/web-iife.min.js +0 -13
- package/builds/web-iife.min.js.map +0 -7
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
Statement,
|
|
6
6
|
LogicalOp,
|
|
7
7
|
} from "../types/pipeline";
|
|
8
|
+
import { tokenizeCommand } from "../utils/tokenize";
|
|
8
9
|
|
|
9
10
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
10
11
|
|
|
@@ -25,7 +26,7 @@ export function parseScript(rawInput: string): Script {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
/**
|
|
29
|
+
/** Parse a single pipeline string (no &&/||/;) into a `Pipeline` object. */
|
|
29
30
|
export function parseShellPipeline(rawInput: string): Pipeline {
|
|
30
31
|
const trimmed = rawInput.trim();
|
|
31
32
|
if (!trimmed) return { commands: [], isValid: true };
|
|
@@ -39,49 +40,6 @@ export function parseShellPipeline(rawInput: string): Pipeline {
|
|
|
39
40
|
|
|
40
41
|
// ── Variable & tilde expansion ────────────────────────────────────────────────
|
|
41
42
|
|
|
42
|
-
/**
|
|
43
|
-
* Expand ~ and $VAR / ${VAR} / ${VAR:-default} / $(cmd placeholder) in a
|
|
44
|
-
* token, given the current env vars and home path.
|
|
45
|
-
* Command substitution $(…) is NOT executed here — it's left as a marker so
|
|
46
|
-
* the executor can handle it.
|
|
47
|
-
*/
|
|
48
|
-
export function expandToken(
|
|
49
|
-
token: string,
|
|
50
|
-
env: Record<string, string>,
|
|
51
|
-
authUser: string,
|
|
52
|
-
lastExitCode = 0,
|
|
53
|
-
): string {
|
|
54
|
-
// tilde expansion
|
|
55
|
-
token = token.replace(/^~(\/|$)/, `/home/${authUser}$1`);
|
|
56
|
-
|
|
57
|
-
// $? special var
|
|
58
|
-
token = token.replace(/\$\?/g, String(lastExitCode));
|
|
59
|
-
// $$ PID (mock)
|
|
60
|
-
token = token.replace(/\$\$/g, "1");
|
|
61
|
-
// $# argc (0 for interactive)
|
|
62
|
-
token = token.replace(/\$#/g, "0");
|
|
63
|
-
|
|
64
|
-
// ${VAR:-default} and ${VAR:+value}
|
|
65
|
-
token = token.replace(
|
|
66
|
-
/\$\{([^}:]+):-([^}]*)\}/g,
|
|
67
|
-
(_, name, def) => env[name] ?? def,
|
|
68
|
-
);
|
|
69
|
-
token = token.replace(/\$\{([^}:]+):\+([^}]*)\}/g, (_, name, val) =>
|
|
70
|
-
env[name] ? val : "",
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
// ${VAR}
|
|
74
|
-
token = token.replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? "");
|
|
75
|
-
|
|
76
|
-
// $VAR (greedy: match longest valid identifier)
|
|
77
|
-
token = token.replace(
|
|
78
|
-
/\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
79
|
-
(_, name) => env[name] ?? "",
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return token;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
43
|
/**
|
|
86
44
|
* Expand glob patterns (*, ?, [abc]) against a list of entries.
|
|
87
45
|
* Returns the original pattern if no match.
|
|
@@ -299,62 +257,3 @@ function parseCommandWithRedirections(token: string): PipelineCommand {
|
|
|
299
257
|
return { name, args: cmdParts.slice(1), inputFile, outputFile, appendOutput };
|
|
300
258
|
}
|
|
301
259
|
|
|
302
|
-
function tokenizeCommand(input: string): string[] {
|
|
303
|
-
const tokens: string[] = [];
|
|
304
|
-
let current = "";
|
|
305
|
-
let inQ = false;
|
|
306
|
-
let qChar = "";
|
|
307
|
-
let i = 0;
|
|
308
|
-
|
|
309
|
-
while (i < input.length) {
|
|
310
|
-
const ch = input[i]!;
|
|
311
|
-
const next = input[i + 1];
|
|
312
|
-
|
|
313
|
-
if ((ch === '"' || ch === "'") && !inQ) {
|
|
314
|
-
inQ = true;
|
|
315
|
-
qChar = ch;
|
|
316
|
-
i++;
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (inQ && ch === qChar) {
|
|
320
|
-
inQ = false;
|
|
321
|
-
qChar = "";
|
|
322
|
-
i++;
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
if (inQ) {
|
|
326
|
-
current += ch;
|
|
327
|
-
i++;
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (ch === " ") {
|
|
332
|
-
if (current) {
|
|
333
|
-
tokens.push(current);
|
|
334
|
-
current = "";
|
|
335
|
-
}
|
|
336
|
-
i++;
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if ((ch === ">" || ch === "<") && !inQ) {
|
|
341
|
-
if (current) {
|
|
342
|
-
tokens.push(current);
|
|
343
|
-
current = "";
|
|
344
|
-
}
|
|
345
|
-
if (ch === ">" && next === ">") {
|
|
346
|
-
tokens.push(">>");
|
|
347
|
-
i += 2;
|
|
348
|
-
} else {
|
|
349
|
-
tokens.push(ch);
|
|
350
|
-
i++;
|
|
351
|
-
}
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
current += ch;
|
|
356
|
-
i++;
|
|
357
|
-
}
|
|
358
|
-
if (current) tokens.push(current);
|
|
359
|
-
return tokens;
|
|
360
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, randomBytes, randomUUID, scryptSync } from "node:crypto";
|
|
1
|
+
import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { PerfLogger } from "../utils/perfLogger";
|
|
@@ -101,14 +101,14 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
101
101
|
// changed = true;
|
|
102
102
|
// }
|
|
103
103
|
|
|
104
|
-
const homePath = `/home/root`;
|
|
105
|
-
if (!this.vfs.exists(homePath)) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
104
|
+
// const homePath = `/home/root`;
|
|
105
|
+
// if (!this.vfs.exists(homePath)) {
|
|
106
|
+
// this.vfs.mkdir(homePath, 0o755);
|
|
107
|
+
// this.vfs.writeFile(
|
|
108
|
+
// `${homePath}/README.txt`,
|
|
109
|
+
// `Welcome to the virtual environment, root`,
|
|
110
|
+
// );
|
|
111
|
+
// }
|
|
112
112
|
|
|
113
113
|
if (changed) {
|
|
114
114
|
await this.persist();
|
|
@@ -239,10 +239,22 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
239
239
|
perf.mark("verifyPassword");
|
|
240
240
|
const record = this.users.get(username);
|
|
241
241
|
if (!record) {
|
|
242
|
+
// Perform a dummy hash to avoid timing leakage on unknown usernames
|
|
243
|
+
this.hashPassword(password, "");
|
|
242
244
|
return false;
|
|
243
245
|
}
|
|
244
246
|
|
|
245
|
-
|
|
247
|
+
const computed = this.hashPassword(password, record.salt);
|
|
248
|
+
const expected = record.passwordHash;
|
|
249
|
+
// timingSafeEqual prevents timing-based password oracle attacks
|
|
250
|
+
try {
|
|
251
|
+
const a = Buffer.from(computed, "hex");
|
|
252
|
+
const b = Buffer.from(expected, "hex");
|
|
253
|
+
if (a.length !== b.length) return false;
|
|
254
|
+
return timingSafeEqual(a, b);
|
|
255
|
+
} catch {
|
|
256
|
+
return computed === expected;
|
|
257
|
+
}
|
|
246
258
|
}
|
|
247
259
|
|
|
248
260
|
/**
|
|
@@ -617,7 +629,8 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
617
629
|
}
|
|
618
630
|
|
|
619
631
|
private createRecord(username: string, password: string): VirtualUserRecord {
|
|
620
|
-
|
|
632
|
+
// Cache key is a hash of the inputs — never store plaintext password in memory
|
|
633
|
+
const cacheKey = createHash("sha256").update(username).update(":").update(password).digest("hex");
|
|
621
634
|
const cached = VirtualUserManager.recordCache.get(cacheKey);
|
|
622
635
|
if (cached) {
|
|
623
636
|
return cached;
|
|
@@ -627,7 +640,8 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
627
640
|
const record = {
|
|
628
641
|
username,
|
|
629
642
|
salt,
|
|
630
|
-
|
|
643
|
+
// Hash uses the generated salt — verifyPassword must use record.salt
|
|
644
|
+
passwordHash: this.hashPassword(password, salt),
|
|
631
645
|
};
|
|
632
646
|
|
|
633
647
|
VirtualUserManager.recordCache.set(cacheKey, record);
|
|
@@ -644,11 +658,12 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
644
658
|
*/
|
|
645
659
|
public hasPassword(username: string): boolean {
|
|
646
660
|
perf.mark("hasPassword");
|
|
647
|
-
if (this.getPasswordHash(username) === this.hashPassword("")) {
|
|
648
|
-
return false;
|
|
649
|
-
}
|
|
650
661
|
const record = this.users.get(username);
|
|
651
|
-
|
|
662
|
+
if (!record) return false;
|
|
663
|
+
// Empty password hash computed with the record's own salt
|
|
664
|
+
const emptyHash = this.hashPassword("", record.salt);
|
|
665
|
+
if (record.passwordHash === emptyHash) return false;
|
|
666
|
+
return !!record.passwordHash;
|
|
652
667
|
}
|
|
653
668
|
|
|
654
669
|
/**
|
|
@@ -660,12 +675,21 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
660
675
|
* @param password Plaintext password string.
|
|
661
676
|
* @returns Hex-encoded hash string.
|
|
662
677
|
*/
|
|
663
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Hash a password with an optional salt.
|
|
680
|
+
* When salt is provided (verify path), the same salt is used for a
|
|
681
|
+
* deterministic hash. When omitted (create path), an empty salt is used
|
|
682
|
+
* for backward compat — callers should pass the stored salt on verify.
|
|
683
|
+
*/
|
|
684
|
+
public hashPassword(password: string, salt = ""): string {
|
|
664
685
|
if (VirtualUserManager.fastPasswordHash) {
|
|
665
|
-
return createHash("sha256")
|
|
686
|
+
return createHash("sha256")
|
|
687
|
+
.update(salt)
|
|
688
|
+
.update(password)
|
|
689
|
+
.digest("hex");
|
|
666
690
|
}
|
|
667
691
|
|
|
668
|
-
return scryptSync(password, "", 32).toString("hex");
|
|
692
|
+
return scryptSync(password, salt || "", 32).toString("hex");
|
|
669
693
|
}
|
|
670
694
|
|
|
671
695
|
private validateUsername(username: string): void {
|
package/src/commands/adduser.ts
CHANGED
|
@@ -1,30 +1,103 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
1
|
+
import type { CommandResult, ShellModule } from "../types/commands";
|
|
2
|
+
import type { VirtualShell } from "../VirtualShell";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Add a new user
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Add a new user interactively.
|
|
6
|
+
*
|
|
7
|
+
* Usage: `adduser <username>`
|
|
8
|
+
*
|
|
9
|
+
* Prompts for:
|
|
10
|
+
* New password: ****
|
|
11
|
+
* Retype new password: ****
|
|
12
|
+
*
|
|
13
|
+
* Mirrors the real `adduser` behaviour — password is never passed on the
|
|
14
|
+
* command line. Root-only.
|
|
8
15
|
*/
|
|
9
16
|
export const adduserCommand: ShellModule = {
|
|
10
17
|
name: "adduser",
|
|
11
18
|
description: "Add a new user",
|
|
12
19
|
category: "users",
|
|
13
|
-
params: ["<username>
|
|
14
|
-
run:
|
|
20
|
+
params: ["<username>"],
|
|
21
|
+
run: ({ authUser, shell, args }) => {
|
|
15
22
|
if (authUser !== "root") {
|
|
16
|
-
return { stderr: "adduser: permission denied", exitCode: 1 };
|
|
23
|
+
return { stderr: "adduser: permission denied\n", exitCode: 1 };
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
const
|
|
20
|
-
if (!username
|
|
26
|
+
const username = args[0];
|
|
27
|
+
if (!username) {
|
|
21
28
|
return {
|
|
22
|
-
stderr: "
|
|
29
|
+
stderr: "Usage: adduser <username>\n",
|
|
23
30
|
exitCode: 1,
|
|
24
31
|
};
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
// Reject if user already exists
|
|
35
|
+
if (shell.users.listUsers().includes(username)) {
|
|
36
|
+
return {
|
|
37
|
+
stderr: `adduser: user '${username}' already exists\n`,
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let newPassword = "";
|
|
43
|
+
type Step = "new" | "retype";
|
|
44
|
+
let step: Step = "new";
|
|
45
|
+
|
|
46
|
+
const onPassword = async (
|
|
47
|
+
input: string,
|
|
48
|
+
sh: VirtualShell,
|
|
49
|
+
): Promise<{ result: CommandResult | null; nextPrompt?: string }> => {
|
|
50
|
+
if (step === "new") {
|
|
51
|
+
if (input.length < 1) {
|
|
52
|
+
return {
|
|
53
|
+
result: {
|
|
54
|
+
stderr: "adduser: password cannot be empty\n",
|
|
55
|
+
exitCode: 1,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
newPassword = input;
|
|
60
|
+
step = "retype";
|
|
61
|
+
return { result: null, nextPrompt: "Retype new password: " };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// step === "retype"
|
|
65
|
+
if (input !== newPassword) {
|
|
66
|
+
return {
|
|
67
|
+
result: {
|
|
68
|
+
stderr: "adduser: passwords do not match — user not created\n",
|
|
69
|
+
exitCode: 1,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await sh.users.addUser(username, newPassword);
|
|
75
|
+
return {
|
|
76
|
+
result: {
|
|
77
|
+
stdout: `${[
|
|
78
|
+
`Adding user '${username}' ...`,
|
|
79
|
+
`Adding new group '${username}' (1001) ...`,
|
|
80
|
+
`Adding new user '${username}' (1001) with group '${username}' ...`,
|
|
81
|
+
`Creating home directory '/home/${username}' ...`,
|
|
82
|
+
`passwd: password set for '${username}'`,
|
|
83
|
+
`adduser: done.`,
|
|
84
|
+
].join("\n")}\n`,
|
|
85
|
+
exitCode: 0,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
sudoChallenge: {
|
|
92
|
+
username,
|
|
93
|
+
targetUser: username,
|
|
94
|
+
commandLine: null,
|
|
95
|
+
loginShell: false,
|
|
96
|
+
prompt: "New password: ",
|
|
97
|
+
mode: "passwd",
|
|
98
|
+
onPassword,
|
|
99
|
+
},
|
|
100
|
+
exitCode: 0,
|
|
101
|
+
};
|
|
29
102
|
},
|
|
30
103
|
};
|
package/src/commands/alias.ts
CHANGED
|
@@ -41,6 +41,11 @@ export const aliasCommand: ShellModule = {
|
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Remove shell aliases.
|
|
46
|
+
* @category shell
|
|
47
|
+
* @params ["<name...>"]
|
|
48
|
+
*/
|
|
44
49
|
export const unaliasCommand: ShellModule = {
|
|
45
50
|
name: "unalias",
|
|
46
51
|
description: "Remove alias definitions",
|
package/src/commands/apt.ts
CHANGED
|
@@ -162,6 +162,11 @@ export const aptCommand: ShellModule = {
|
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Query the package cache and retrieve package information.
|
|
167
|
+
* @category package
|
|
168
|
+
* @params ["<search|show|policy> [pkg]"]
|
|
169
|
+
*/
|
|
165
170
|
export const aptCacheCommand: ShellModule = {
|
|
166
171
|
name: "apt-cache",
|
|
167
172
|
description: "Query the package cache",
|
package/src/commands/awk.ts
CHANGED
|
@@ -1,44 +1,169 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { getFlag } from "./command-helpers";
|
|
3
|
+
import { resolvePath, assertPathAccess } from "./helpers";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Minimal
|
|
6
|
-
* @category text
|
|
7
|
-
* @params ["[-F <sep>] '<program>' [file]"]
|
|
6
|
+
* Minimal awk-like pattern scanner.
|
|
8
7
|
*
|
|
9
|
-
* Supported
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* Supported:
|
|
9
|
+
* - `NR==N` pattern (line number condition)
|
|
10
|
+
* - `NF` (number of fields)
|
|
11
|
+
* - `/regex/` pattern
|
|
12
|
+
* - `{ print $N, $M, ... }` action
|
|
13
|
+
* - `{ print }` / `{ print $0 }`
|
|
14
|
+
* - `BEGIN { ... }` and `END { ... }` blocks (no side effects)
|
|
15
|
+
* - `$NF` (last field)
|
|
16
|
+
* - `-F sep` field separator
|
|
14
17
|
*/
|
|
15
18
|
export const awkCommand: ShellModule = {
|
|
16
19
|
name: "awk",
|
|
17
|
-
description: "Pattern scanning and processing language
|
|
20
|
+
description: "Pattern scanning and processing language",
|
|
18
21
|
category: "text",
|
|
19
22
|
params: ["[-F <sep>] '<program>' [file]"],
|
|
20
|
-
run: ({ args, stdin }) => {
|
|
23
|
+
run: ({ authUser, args, stdin, cwd, shell }) => {
|
|
21
24
|
const sep = (getFlag(args, ["-F"]) as string | undefined) ?? " ";
|
|
22
|
-
const
|
|
25
|
+
const nonFlagArgs = args.filter((a) => !a.startsWith("-") && a !== sep);
|
|
26
|
+
const prog = nonFlagArgs[0];
|
|
27
|
+
const fileArg = nonFlagArgs[1];
|
|
28
|
+
|
|
23
29
|
if (!prog) return { stderr: "awk: no program", exitCode: 1 };
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
31
|
+
let input = stdin ?? "";
|
|
32
|
+
if (fileArg) {
|
|
33
|
+
const filePath = resolvePath(cwd, fileArg);
|
|
34
|
+
try {
|
|
35
|
+
assertPathAccess(authUser, filePath, "awk");
|
|
36
|
+
input = shell.vfs.readFile(filePath);
|
|
37
|
+
} catch {
|
|
38
|
+
return { stderr: `awk: ${fileArg}: No such file or directory`, exitCode: 1 };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lines = input.split("\n");
|
|
43
|
+
// Remove empty last element if input ends with \n
|
|
44
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
45
|
+
|
|
46
|
+
// Parse program into clauses: [pattern, action]
|
|
47
|
+
type Clause = { pattern: string; action: string };
|
|
48
|
+
const clauses: Clause[] = [];
|
|
49
|
+
|
|
50
|
+
const progTrim = prog.trim();
|
|
51
|
+
|
|
52
|
+
// Handle single unbraced pattern (NR==2, /regex/)
|
|
53
|
+
if (!progTrim.startsWith("{") && !progTrim.includes("{")) {
|
|
54
|
+
clauses.push({ pattern: progTrim, action: "print $0" });
|
|
55
|
+
} else {
|
|
56
|
+
// Parse "pattern { action } pattern2 { action2 }"
|
|
57
|
+
const clauseRe = /([^{]*)\{([^}]*)\}/g;
|
|
58
|
+
let m2 = clauseRe.exec(progTrim);
|
|
59
|
+
while (m2 !== null) {
|
|
60
|
+
clauses.push({ pattern: m2[1]!.trim(), action: m2[2]!.trim() });
|
|
61
|
+
m2 = clauseRe.exec(progTrim);
|
|
62
|
+
}
|
|
63
|
+
if (clauses.length === 0) {
|
|
64
|
+
clauses.push({ pattern: "", action: progTrim.replace(/[{}]/g, "").trim() });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const out: string[] = [];
|
|
69
|
+
|
|
70
|
+
// BEGIN / END
|
|
71
|
+
const beginClause = clauses.find((c) => c.pattern === "BEGIN");
|
|
72
|
+
const endClause = clauses.find((c) => c.pattern === "END");
|
|
73
|
+
const mainClauses = clauses.filter((c) => c.pattern !== "BEGIN" && c.pattern !== "END");
|
|
74
|
+
|
|
75
|
+
function splitFields(line: string): string[] {
|
|
76
|
+
if (sep === " ") return line.trim().split(/\s+/).filter(Boolean);
|
|
77
|
+
return line.split(sep);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function evalAction(action: string, line: string, nr: number): void {
|
|
81
|
+
const parts = splitFields(line);
|
|
82
|
+
const nf = parts.length;
|
|
83
|
+
|
|
84
|
+
// Expand variables
|
|
85
|
+
const resolve = (expr: string): string => {
|
|
86
|
+
expr = expr.trim();
|
|
87
|
+
if (expr === "NR") return String(nr);
|
|
88
|
+
if (expr === "NF") return String(nf);
|
|
89
|
+
if (expr === "$0") return line;
|
|
90
|
+
if (expr === "$NF") return parts[nf - 1] ?? "";
|
|
91
|
+
if (/^\$\d+$/.test(expr)) return parts[parseInt(expr.slice(1), 10) - 1] ?? "";
|
|
92
|
+
// Arithmetic NR+1, NF-1
|
|
93
|
+
const arith = expr.replace(/\bNR\b/g, String(nr)).replace(/\bNF\b/g, String(nf));
|
|
94
|
+
if (/^[\d\s+\-*/()]+$/.test(arith)) {
|
|
95
|
+
// biome-ignore lint/security/noGlobalEval: safe arithmetic — input contains only digits and operators after variable substitution
|
|
96
|
+
try { return String(Function(`"use strict"; return (${arith});`)()); } catch {} }
|
|
97
|
+
return expr.replace(/"/g, "");
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const stmts = action.split(";").map((s) => s.trim()).filter(Boolean);
|
|
101
|
+
for (const stmt of stmts) {
|
|
102
|
+
if (stmt === "print" || stmt === "print $0") {
|
|
103
|
+
out.push(line);
|
|
104
|
+
} else if (stmt.startsWith("print ")) {
|
|
105
|
+
const args2 = stmt.slice(6).split(/\s*,\s*/);
|
|
106
|
+
out.push(args2.map(resolve).join("\t"));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function matchPattern(pattern: string, line: string, nr: number): boolean {
|
|
112
|
+
if (!pattern) return true;
|
|
113
|
+
if (pattern === "1") return true;
|
|
114
|
+
|
|
115
|
+
// NR==N or NR>N etc.
|
|
116
|
+
const nrCond = pattern.match(/^NR\s*([=!<>]=?|==)\s*(\d+)$/);
|
|
117
|
+
if (nrCond) {
|
|
118
|
+
const op = nrCond[1]!;
|
|
119
|
+
const val = parseInt(nrCond[2]!, 10);
|
|
120
|
+
switch (op) {
|
|
121
|
+
case "==": return nr === val;
|
|
122
|
+
case "!=": return nr !== val;
|
|
123
|
+
case ">": return nr > val;
|
|
124
|
+
case ">=": return nr >= val;
|
|
125
|
+
case "<": return nr < val;
|
|
126
|
+
case "<=": return nr <= val;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// NR%N==M
|
|
131
|
+
const nrMod = pattern.match(/^NR%(\d+)==(\d+)$/);
|
|
132
|
+
if (nrMod) {
|
|
133
|
+
return nr % parseInt(nrMod[1]!, 10) === parseInt(nrMod[2]!, 10);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// /regex/ pattern
|
|
137
|
+
if (pattern.startsWith("/") && pattern.endsWith("/")) {
|
|
138
|
+
try {
|
|
139
|
+
return new RegExp(pattern.slice(1, -1)).test(line);
|
|
140
|
+
} catch { return false; }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// $N~/regex/
|
|
144
|
+
const fieldMatch = pattern.match(/^\$(\d+)~\/(.*)\/$/);
|
|
145
|
+
if (fieldMatch) {
|
|
146
|
+
const parts = splitFields(line);
|
|
147
|
+
const field = parts[parseInt(fieldMatch[1]!, 10) - 1] ?? "";
|
|
148
|
+
try { return new RegExp(fieldMatch[2]!).test(field); } catch { return false; }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (beginClause) evalAction(beginClause.action, "", 0);
|
|
155
|
+
|
|
156
|
+
for (let nr = 1; nr <= lines.length; nr++) {
|
|
157
|
+
const line = lines[nr - 1]!;
|
|
158
|
+
for (const clause of mainClauses) {
|
|
159
|
+
if (matchPattern(clause.pattern, line, nr)) {
|
|
160
|
+
evalAction(clause.action, line, nr);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (endClause) evalAction(endClause.action, "", lines.length + 1);
|
|
166
|
+
|
|
167
|
+
return { stdout: out.join("\n") + (out.length > 0 ? "\n" : ""), exitCode: 0 };
|
|
43
168
|
},
|
|
44
169
|
};
|
package/src/commands/cd.ts
CHANGED
package/src/commands/clear.ts
CHANGED
|
@@ -15,11 +15,20 @@ function matchFlagToken(
|
|
|
15
15
|
return { matched: true, inlineValue: null };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// --flag=value style
|
|
18
19
|
const prefix = `${flag}=`;
|
|
19
20
|
if (token.startsWith(prefix)) {
|
|
20
21
|
return { matched: true, inlineValue: token.slice(prefix.length) };
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
// Short flag inline value: -f2, -d: (single char flag like -f, -d, -n)
|
|
25
|
+
// Only applies to single-char flags (-X), not long flags (--flag)
|
|
26
|
+
if (flag.length === 2 && flag.startsWith("-") && !flag.startsWith("--")) {
|
|
27
|
+
if (token.startsWith(flag) && token.length > flag.length) {
|
|
28
|
+
return { matched: true, inlineValue: token.slice(flag.length) };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
return { matched: false, inlineValue: null };
|
|
24
33
|
}
|
|
25
34
|
|
package/src/commands/curl.ts
CHANGED
|
@@ -97,7 +97,8 @@ export const curlCommand: ShellModule = {
|
|
|
97
97
|
|
|
98
98
|
let response: Response;
|
|
99
99
|
try {
|
|
100
|
-
|
|
100
|
+
const urlWithHttp = url.startsWith("http://") || url.startsWith("https://") ? url : `http://${url}`;
|
|
101
|
+
response = await fetch(urlWithHttp, fetchOpts);
|
|
101
102
|
} catch (err) {
|
|
102
103
|
const msg = err instanceof Error ? err.message : String(err);
|
|
103
104
|
return {
|
package/src/commands/declare.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Declare variables and give them attributes (integer, readonly, export, array).
|
|
6
|
+
* @category shell
|
|
7
|
+
* @params ["[-i] [-r] [-x] [-a] [name[=value]...]"]
|
|
8
|
+
*/
|
|
4
9
|
export const declareCommand: ShellModule = {
|
|
5
10
|
name: "declare",
|
|
6
11
|
aliases: ["local", "typeset"],
|