typescript-virtual-container 1.5.2 → 1.5.4
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 +43 -23
- package/dist/.tsbuildinfo +1 -0
- package/dist/SSHMimic/executor.js +23 -5
- package/dist/commands/basename.d.ts +13 -0
- package/dist/commands/basename.js +45 -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/ifconfig.d.ts +7 -0
- package/dist/commands/ifconfig.js +52 -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 +24 -2
- package/dist/commands/runtime.js +159 -106
- package/dist/commands/sh.js +5 -0
- 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 +84 -0
- 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.1-directbash-k6.1.0.mjs +0 -1768
- package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +0 -1768
- package/builds/fortune-nyx-v1.5.1-ssh.cjs +0 -1769
- package/builds/fortune-nyx-v1.5.1-web.min.js +0 -17022
- package/bun.lock +0 -244
- package/docs/.nojekyll +0 -1
- package/docs/app.js +0 -1755
- 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 -1755
- 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 -1006
- 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 -378
- 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/sed.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
import { getFlag, ifFlag } from "./command-helpers";
|
|
3
|
-
import { resolvePath } from "./helpers";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Stream editor for filtering and transforming text lines.
|
|
7
|
-
* @category text
|
|
8
|
-
* @params ["-e <expr> [file]", "s/pattern/replace/[g]"]
|
|
9
|
-
*/
|
|
10
|
-
export const sedCommand: ShellModule = {
|
|
11
|
-
name: "sed",
|
|
12
|
-
description: "Stream editor for filtering and transforming text",
|
|
13
|
-
category: "text",
|
|
14
|
-
params: ["-e <expr> [file]", "s/pattern/replace/[g]"],
|
|
15
|
-
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
16
|
-
const inPlace = ifFlag(args, ["-i"]);
|
|
17
|
-
const expr =
|
|
18
|
-
(getFlag(args, ["-e"]) as string | undefined) ??
|
|
19
|
-
args.find((a) => !a.startsWith("-"));
|
|
20
|
-
const fileArg = args.filter((a) => !a.startsWith("-") && a !== expr).pop();
|
|
21
|
-
|
|
22
|
-
if (!expr) return { stderr: "sed: no expression", exitCode: 1 };
|
|
23
|
-
|
|
24
|
-
let content = stdin ?? "";
|
|
25
|
-
if (fileArg) {
|
|
26
|
-
const p = resolvePath(cwd, fileArg);
|
|
27
|
-
try {
|
|
28
|
-
content = shell.vfs.readFile(p);
|
|
29
|
-
} catch {
|
|
30
|
-
return {
|
|
31
|
-
stderr: `sed: ${fileArg}: No such file or directory`,
|
|
32
|
-
exitCode: 1,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Parse s/from/to/[g]
|
|
38
|
-
const sMatch = expr.match(/^s([^a-zA-Z0-9])(.+?)\1(.*?)\1([gi]*)$/);
|
|
39
|
-
if (!sMatch)
|
|
40
|
-
return { stderr: `sed: unrecognized command: ${expr}`, exitCode: 1 };
|
|
41
|
-
|
|
42
|
-
const [, , from, to, flags] = sMatch;
|
|
43
|
-
const regexFlags = (flags ?? "").includes("i")
|
|
44
|
-
? "gi"
|
|
45
|
-
: (flags ?? "").includes("g")
|
|
46
|
-
? "g"
|
|
47
|
-
: "";
|
|
48
|
-
let regex: RegExp;
|
|
49
|
-
try {
|
|
50
|
-
regex = new RegExp(from!, regexFlags || "");
|
|
51
|
-
} catch (_e) {
|
|
52
|
-
return { stderr: `sed: invalid regex: ${from}`, exitCode: 1 };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const result =
|
|
56
|
-
(flags ?? "").includes("g") || regexFlags.includes("g")
|
|
57
|
-
? content.replace(regex, to ?? "")
|
|
58
|
-
: content.replace(regex, to ?? "");
|
|
59
|
-
|
|
60
|
-
if (inPlace && fileArg) {
|
|
61
|
-
const p = resolvePath(cwd, fileArg);
|
|
62
|
-
shell.writeFileAsUser(authUser, p, result);
|
|
63
|
-
return { exitCode: 0 };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { stdout: result, exitCode: 0 };
|
|
67
|
-
},
|
|
68
|
-
};
|
package/src/commands/seq.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/** Generate sequences of numbers. seq LAST / seq FIRST LAST / seq FIRST INCREMENT LAST */
|
|
4
|
-
export const seqCommand: ShellModule = {
|
|
5
|
-
name: "seq",
|
|
6
|
-
description: "Print a sequence of numbers",
|
|
7
|
-
category: "text",
|
|
8
|
-
params: ["[FIRST [INCREMENT]] LAST"],
|
|
9
|
-
run: ({ args }) => {
|
|
10
|
-
const nums = args.filter((a) => !a.startsWith("-") || /^-[\d.]/.test(a)).map(Number);
|
|
11
|
-
const sep = (() => { const i = args.indexOf("-s"); return i !== -1 ? (args[i + 1] ?? "\n") : "\n"; })();
|
|
12
|
-
const fmt = (() => { const i = args.indexOf("-f"); return i !== -1 ? (args[i + 1] ?? "%g") : null; })();
|
|
13
|
-
const width = args.includes("-w");
|
|
14
|
-
|
|
15
|
-
let first = 1, inc = 1, last: number;
|
|
16
|
-
if (nums.length === 1) { last = nums[0]!; }
|
|
17
|
-
else if (nums.length === 2) { first = nums[0]!; last = nums[1]!; }
|
|
18
|
-
else { first = nums[0]!; inc = nums[1]!; last = nums[2]!; }
|
|
19
|
-
|
|
20
|
-
if (inc === 0) return { stderr: "seq: zero increment\n", exitCode: 1 };
|
|
21
|
-
if ((inc > 0 && first > last) || (inc < 0 && first < last)) return { stdout: "", exitCode: 0 };
|
|
22
|
-
|
|
23
|
-
const results: string[] = [];
|
|
24
|
-
const maxSteps = 100000;
|
|
25
|
-
let steps = 0;
|
|
26
|
-
for (let n = first; inc > 0 ? n <= last : n >= last; n = Math.round((n + inc) * 1e10) / 1e10) {
|
|
27
|
-
if (++steps > maxSteps) break;
|
|
28
|
-
let s: string;
|
|
29
|
-
if (fmt) {
|
|
30
|
-
s = fmt.replace("%g", String(n)).replace("%f", n.toFixed(6)).replace("%d", String(Math.trunc(n)));
|
|
31
|
-
} else {
|
|
32
|
-
s = Number.isInteger(n) ? String(n) : n.toPrecision(12).replace(/\.?0+$/, "");
|
|
33
|
-
}
|
|
34
|
-
if (width) {
|
|
35
|
-
const maxLen = String(Math.trunc(last)).length;
|
|
36
|
-
s = s.padStart(maxLen, "0");
|
|
37
|
-
}
|
|
38
|
-
results.push(s);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return { stdout: `${results.join(sep)}\n`, exitCode: 0 };
|
|
42
|
-
},
|
|
43
|
-
};
|
package/src/commands/set.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/** biome-ignore-all lint/style/useNamingConvention: env variables */
|
|
2
|
-
import type { ShellModule } from "../types/commands";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Display or set shell variables and options.
|
|
6
|
-
* @category shell
|
|
7
|
-
* @params ["[VAR=value]"]
|
|
8
|
-
*/
|
|
9
|
-
export const setCommand: ShellModule = {
|
|
10
|
-
name: "set",
|
|
11
|
-
description: "Display or set shell variables",
|
|
12
|
-
category: "shell",
|
|
13
|
-
params: ["[VAR=value]"],
|
|
14
|
-
run: ({ args, env }) => {
|
|
15
|
-
if (args.length === 0) {
|
|
16
|
-
const out = Object.entries(env.vars)
|
|
17
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
18
|
-
.join("\n");
|
|
19
|
-
return { stdout: out, exitCode: 0 };
|
|
20
|
-
}
|
|
21
|
-
for (const arg of args) {
|
|
22
|
-
if (arg.includes("=")) {
|
|
23
|
-
const eq = arg.indexOf("=");
|
|
24
|
-
env.vars[arg.slice(0, eq)] = arg.slice(eq + 1);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return { exitCode: 0 };
|
|
28
|
-
},
|
|
29
|
-
};
|
package/src/commands/sh.ts
DELETED
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CommandContext,
|
|
3
|
-
CommandResult,
|
|
4
|
-
ShellModule,
|
|
5
|
-
} from "../types/commands";
|
|
6
|
-
import { evalArith, expandAsync, expandBraces } from "../utils/expand";
|
|
7
|
-
import { ifFlag } from "./command-helpers";
|
|
8
|
-
import { resolvePath } from "./helpers";
|
|
9
|
-
import { runCommand } from "./runtime";
|
|
10
|
-
|
|
11
|
-
/** Alias for clarity inside sh.ts */
|
|
12
|
-
type ShellContext = CommandContext;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Expand all shell forms including $(cmd) substitution.
|
|
16
|
-
* Delegates to centralised expandAsync (single-quote-aware, depth-tracked).
|
|
17
|
-
*/
|
|
18
|
-
async function expandVars(
|
|
19
|
-
line: string,
|
|
20
|
-
env: Record<string, string>,
|
|
21
|
-
lastExit: number,
|
|
22
|
-
ctx: ShellContext,
|
|
23
|
-
): Promise<string> {
|
|
24
|
-
return expandAsync(line, env, lastExit, (sub) =>
|
|
25
|
-
runCommand(
|
|
26
|
-
sub,
|
|
27
|
-
ctx.authUser,
|
|
28
|
-
ctx.hostname,
|
|
29
|
-
ctx.mode,
|
|
30
|
-
ctx.cwd,
|
|
31
|
-
ctx.shell,
|
|
32
|
-
undefined,
|
|
33
|
-
ctx.env,
|
|
34
|
-
).then((r) => r.stdout ?? ""),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type Block =
|
|
39
|
-
| {
|
|
40
|
-
type: "if";
|
|
41
|
-
cond: string;
|
|
42
|
-
then_: string[];
|
|
43
|
-
elif: Array<{ cond: string; body: string[] }>;
|
|
44
|
-
else_: string[];
|
|
45
|
-
}
|
|
46
|
-
| { type: "for"; var: string; list: string; body: string[] }
|
|
47
|
-
| { type: "while"; cond: string; body: string[] }
|
|
48
|
-
| { type: "func"; name: string; body: string[] }
|
|
49
|
-
| { type: "arith"; expr: string }
|
|
50
|
-
| { type: "cmd"; line: string };
|
|
51
|
-
|
|
52
|
-
/** Very small shell interpreter: supports if/elif/else/fi, for/do/done, while/do/done */
|
|
53
|
-
function parseBlocks(lines: string[]): Block[] {
|
|
54
|
-
const blocks: Block[] = [];
|
|
55
|
-
let i = 0;
|
|
56
|
-
while (i < lines.length) {
|
|
57
|
-
const line = lines[i]!.trim();
|
|
58
|
-
if (!line || line.startsWith("#")) {
|
|
59
|
-
i++;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Function definition: name() { or function name { or name() { body }
|
|
64
|
-
const funcMatchInline = line.match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{(.+)\}\s*$/);
|
|
65
|
-
const funcMatch = funcMatchInline ?? (
|
|
66
|
-
line.match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{?\s*$/) ||
|
|
67
|
-
line.match(/^function\s+(\w+)\s*\{?\s*$/)
|
|
68
|
-
);
|
|
69
|
-
if (funcMatch) {
|
|
70
|
-
const funcName = funcMatch[1]!;
|
|
71
|
-
const body: string[] = [];
|
|
72
|
-
// Inline: name() { cmd; } — single-line form
|
|
73
|
-
if (funcMatchInline) {
|
|
74
|
-
body.push(...funcMatchInline[2]!.split(";").map((s: string) => s.trim()).filter(Boolean));
|
|
75
|
-
blocks.push({ type: "func", name: funcName, body });
|
|
76
|
-
i++;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
i++;
|
|
80
|
-
while (i < lines.length && lines[i]?.trim() !== "}" && i < lines.length + 1) {
|
|
81
|
-
const l = lines[i]!.trim().replace(/^do\s+/, "");
|
|
82
|
-
if (l && l !== "{") body.push(l);
|
|
83
|
-
i++;
|
|
84
|
-
}
|
|
85
|
-
i++; // skip closing }
|
|
86
|
-
blocks.push({ type: "func", name: funcName, body });
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// (( expr )) arithmetic statement
|
|
91
|
-
const arithMatch = line.match(/^\(\(\s*(.+?)\s*\)\)$/);
|
|
92
|
-
if (arithMatch) {
|
|
93
|
-
blocks.push({ type: "arith", expr: arithMatch[1]! });
|
|
94
|
-
i++;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (line.startsWith("if ") || line === "if") {
|
|
99
|
-
const cond = line
|
|
100
|
-
.replace(/^if\s+/, "")
|
|
101
|
-
.replace(/;\s*then\s*$/, "")
|
|
102
|
-
.trim();
|
|
103
|
-
const thenLines: string[] = [];
|
|
104
|
-
const elifBlocks: Array<{ cond: string; body: string[] }> = [];
|
|
105
|
-
const elseLines: string[] = [];
|
|
106
|
-
let section: "then" | "elif" | "else" = "then";
|
|
107
|
-
let elifCond = "";
|
|
108
|
-
i++;
|
|
109
|
-
while (i < lines.length && lines[i]?.trim() !== "fi") {
|
|
110
|
-
const l = lines[i]!.trim();
|
|
111
|
-
if (l.startsWith("elif ")) {
|
|
112
|
-
section = "elif";
|
|
113
|
-
elifCond = l
|
|
114
|
-
.replace(/^elif\s+/, "")
|
|
115
|
-
.replace(/;\s*then\s*$/, "")
|
|
116
|
-
.trim();
|
|
117
|
-
elifBlocks.push({ cond: elifCond, body: [] });
|
|
118
|
-
} else if (l === "else") {
|
|
119
|
-
section = "else";
|
|
120
|
-
} else if (l !== "then") {
|
|
121
|
-
if (section === "then") thenLines.push(l);
|
|
122
|
-
else if (section === "elif" && elifBlocks.length > 0)
|
|
123
|
-
elifBlocks[elifBlocks.length - 1]!.body.push(l);
|
|
124
|
-
else elseLines.push(l);
|
|
125
|
-
}
|
|
126
|
-
i++;
|
|
127
|
-
}
|
|
128
|
-
blocks.push({
|
|
129
|
-
type: "if",
|
|
130
|
-
cond,
|
|
131
|
-
then_: thenLines,
|
|
132
|
-
elif: elifBlocks,
|
|
133
|
-
else_: elseLines,
|
|
134
|
-
});
|
|
135
|
-
} else if (line.startsWith("for ")) {
|
|
136
|
-
const m = line.match(/^for\s+(\w+)\s+in\s+(.+?)(?:\s*;\s*do)?$/);
|
|
137
|
-
if (m) {
|
|
138
|
-
const body: string[] = [];
|
|
139
|
-
i++;
|
|
140
|
-
while (i < lines.length && lines[i]?.trim() !== "done") {
|
|
141
|
-
const l = lines[i]!.trim().replace(/^do\s+/, "");
|
|
142
|
-
if (l && l !== "do") body.push(l);
|
|
143
|
-
i++;
|
|
144
|
-
}
|
|
145
|
-
blocks.push({ type: "for", var: m[1]!, list: m[2]!, body });
|
|
146
|
-
} else {
|
|
147
|
-
blocks.push({ type: "cmd", line });
|
|
148
|
-
}
|
|
149
|
-
} else if (line.startsWith("while ")) {
|
|
150
|
-
const cond = line
|
|
151
|
-
.replace(/^while\s+/, "")
|
|
152
|
-
.replace(/;\s*do\s*$/, "")
|
|
153
|
-
.trim();
|
|
154
|
-
const body: string[] = [];
|
|
155
|
-
i++;
|
|
156
|
-
while (i < lines.length && lines[i]?.trim() !== "done") {
|
|
157
|
-
const l = lines[i]!.trim().replace(/^do\s+/, "");
|
|
158
|
-
if (l && l !== "do") body.push(l);
|
|
159
|
-
i++;
|
|
160
|
-
}
|
|
161
|
-
blocks.push({ type: "while", cond, body });
|
|
162
|
-
} else {
|
|
163
|
-
blocks.push({ type: "cmd", line });
|
|
164
|
-
}
|
|
165
|
-
i++;
|
|
166
|
-
}
|
|
167
|
-
return blocks;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function evalCondition(
|
|
171
|
-
cond: string,
|
|
172
|
-
ctx: CommandContext,
|
|
173
|
-
): Promise<boolean> {
|
|
174
|
-
const expanded = await expandVars(
|
|
175
|
-
cond,
|
|
176
|
-
ctx.env.vars,
|
|
177
|
-
ctx.env.lastExitCode,
|
|
178
|
-
ctx,
|
|
179
|
-
);
|
|
180
|
-
// test -f / test -d / [ ... ]
|
|
181
|
-
const testMatch = expanded.match(/^\[?\s*(.+?)\s*\]?$/);
|
|
182
|
-
if (testMatch) {
|
|
183
|
-
const expr = testMatch[1]!;
|
|
184
|
-
// -f file
|
|
185
|
-
const fTest = expr.match(/^-([fdeznr])\s+(.+)$/);
|
|
186
|
-
if (fTest) {
|
|
187
|
-
const [, flag, arg] = fTest;
|
|
188
|
-
const p = resolvePath(ctx.cwd, arg!);
|
|
189
|
-
if (flag === "f")
|
|
190
|
-
return ctx.shell.vfs.exists(p) && ctx.shell.vfs.stat(p).type === "file";
|
|
191
|
-
if (flag === "d")
|
|
192
|
-
return (
|
|
193
|
-
ctx.shell.vfs.exists(p) && ctx.shell.vfs.stat(p).type === "directory"
|
|
194
|
-
);
|
|
195
|
-
if (flag === "e") return ctx.shell.vfs.exists(p);
|
|
196
|
-
if (flag === "z") return (arg ?? "").length === 0;
|
|
197
|
-
if (flag === "n") return (arg ?? "").length > 0;
|
|
198
|
-
}
|
|
199
|
-
// string comparison
|
|
200
|
-
const cmpMatch = expr.match(/^"?([^"]*)"?\s*(==|!=|=|<|>)\s*"?([^"]*)"?$/);
|
|
201
|
-
if (cmpMatch) {
|
|
202
|
-
const [, a, op, b] = cmpMatch;
|
|
203
|
-
if (op === "==" || op === "=") return a === b;
|
|
204
|
-
if (op === "!=") return a !== b;
|
|
205
|
-
}
|
|
206
|
-
// numeric
|
|
207
|
-
const numMatch = expr.match(/^(\S+)\s+(-eq|-ne|-lt|-le|-gt|-ge)\s+(\S+)$/);
|
|
208
|
-
if (numMatch) {
|
|
209
|
-
const [, a, op, b] = numMatch;
|
|
210
|
-
const na = Number(a),
|
|
211
|
-
nb = Number(b);
|
|
212
|
-
if (op === "-eq") return na === nb;
|
|
213
|
-
if (op === "-ne") return na !== nb;
|
|
214
|
-
if (op === "-lt") return na < nb;
|
|
215
|
-
if (op === "-le") return na <= nb;
|
|
216
|
-
if (op === "-gt") return na > nb;
|
|
217
|
-
if (op === "-ge") return na >= nb;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
// fallback: run command and check exit code
|
|
221
|
-
const r = await runCommand(
|
|
222
|
-
expanded,
|
|
223
|
-
ctx.authUser,
|
|
224
|
-
ctx.hostname,
|
|
225
|
-
ctx.mode,
|
|
226
|
-
ctx.cwd,
|
|
227
|
-
ctx.shell,
|
|
228
|
-
undefined,
|
|
229
|
-
ctx.env,
|
|
230
|
-
);
|
|
231
|
-
return (r.exitCode ?? 0) === 0;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function runBlocks(
|
|
235
|
-
blocks: Block[],
|
|
236
|
-
ctx: CommandContext,
|
|
237
|
-
): Promise<CommandResult> {
|
|
238
|
-
let lastResult: CommandResult = { exitCode: 0 };
|
|
239
|
-
let output = "";
|
|
240
|
-
|
|
241
|
-
for (const block of blocks) {
|
|
242
|
-
if (block.type === "cmd") {
|
|
243
|
-
const expanded = await expandVars(
|
|
244
|
-
block.line,
|
|
245
|
-
ctx.env.vars,
|
|
246
|
-
ctx.env.lastExitCode,
|
|
247
|
-
ctx,
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// Bare VAR=val assignment(s) — handle before dispatching to runCommand
|
|
251
|
-
const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)/;
|
|
252
|
-
const tokens = expanded.trim().split(/\s+/);
|
|
253
|
-
if (tokens.length > 0 && assignRe.test(tokens[0]!)) {
|
|
254
|
-
const allAssign = tokens.every((t) => assignRe.test(t));
|
|
255
|
-
if (allAssign) {
|
|
256
|
-
for (const tok of tokens) {
|
|
257
|
-
const m = tok.match(assignRe)!;
|
|
258
|
-
ctx.env.vars[m[1]!] = m[2]!;
|
|
259
|
-
}
|
|
260
|
-
ctx.env.lastExitCode = 0;
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const r = await (async () => {
|
|
266
|
-
// Check if expanded matches a registered function
|
|
267
|
-
const cmdName = expanded.trim().split(/\s+/)[0] ?? "";
|
|
268
|
-
const funcBody = ctx.env.vars[`__func_${cmdName}`];
|
|
269
|
-
if (funcBody) {
|
|
270
|
-
// Set positional params $1 $2 ... from remaining args
|
|
271
|
-
const funcArgs = expanded.trim().split(/\s+/).slice(1);
|
|
272
|
-
const savedVars = { ...ctx.env.vars };
|
|
273
|
-
funcArgs.forEach((a, i) => { ctx.env.vars[String(i + 1)] = a; });
|
|
274
|
-
ctx.env.vars["0"] = cmdName;
|
|
275
|
-
const funcLines = funcBody.split("\n");
|
|
276
|
-
const funcResult = await runBlocks(parseBlocks(funcLines), ctx);
|
|
277
|
-
// Restore positional params
|
|
278
|
-
for (let pi = 1; pi <= funcArgs.length; pi++) delete ctx.env.vars[String(pi)];
|
|
279
|
-
Object.assign(ctx.env.vars, { ...savedVars, ...ctx.env.vars });
|
|
280
|
-
return funcResult;
|
|
281
|
-
}
|
|
282
|
-
return runCommand(
|
|
283
|
-
expanded,
|
|
284
|
-
ctx.authUser,
|
|
285
|
-
ctx.hostname,
|
|
286
|
-
ctx.mode,
|
|
287
|
-
ctx.cwd,
|
|
288
|
-
ctx.shell,
|
|
289
|
-
undefined,
|
|
290
|
-
ctx.env,
|
|
291
|
-
);
|
|
292
|
-
})();
|
|
293
|
-
ctx.env.lastExitCode = r.exitCode ?? 0;
|
|
294
|
-
if (r.stdout) output += `${r.stdout}\n`;
|
|
295
|
-
if (r.stderr) return { ...r, stdout: output.trim() };
|
|
296
|
-
lastResult = r;
|
|
297
|
-
} else if (block.type === "if") {
|
|
298
|
-
let ran = false;
|
|
299
|
-
if (await evalCondition(block.cond, ctx)) {
|
|
300
|
-
const sub = await runBlocks(parseBlocks(block.then_), ctx);
|
|
301
|
-
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
302
|
-
ran = true;
|
|
303
|
-
} else {
|
|
304
|
-
for (const elif of block.elif) {
|
|
305
|
-
if (await evalCondition(elif.cond, ctx)) {
|
|
306
|
-
const sub = await runBlocks(parseBlocks(elif.body), ctx);
|
|
307
|
-
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
308
|
-
ran = true;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (!ran && block.else_.length > 0) {
|
|
313
|
-
const sub = await runBlocks(parseBlocks(block.else_), ctx);
|
|
314
|
-
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
} else if (block.type === "func") {
|
|
318
|
-
// Register function in env vars as __func_<name>=<body>
|
|
319
|
-
ctx.env.vars[`__func_${block.name}`] = block.body.join("\n");
|
|
320
|
-
} else if (block.type === "arith") {
|
|
321
|
-
// (( expr )) — evaluate arithmetic, update vars
|
|
322
|
-
const expr = block.expr.trim();
|
|
323
|
-
// Handle i++ / i-- / i+=N / i-=N
|
|
324
|
-
const incMatch = expr.match(/^(\w+)\s*(\+\+|--)$/);
|
|
325
|
-
if (incMatch) {
|
|
326
|
-
const val = parseInt(ctx.env.vars[incMatch[1]!] ?? "0", 10);
|
|
327
|
-
ctx.env.vars[incMatch[1]!] = String(incMatch[2] === "++" ? val + 1 : val - 1);
|
|
328
|
-
} else {
|
|
329
|
-
const assignMatch = expr.match(/^(\w+)\s*([+\-*/])=\s*(.+)$/);
|
|
330
|
-
if (assignMatch) {
|
|
331
|
-
const lhs = parseInt(ctx.env.vars[assignMatch[1]!] ?? "0", 10);
|
|
332
|
-
const rhs = parseInt(assignMatch[3]!, 10);
|
|
333
|
-
const ops: Record<string, number> = { "+": lhs + rhs, "-": lhs - rhs, "*": lhs * rhs, "/": Math.floor(lhs / rhs) };
|
|
334
|
-
ctx.env.vars[assignMatch[1]!] = String(ops[assignMatch[2]!] ?? lhs);
|
|
335
|
-
} else {
|
|
336
|
-
const value = evalArith(expr, ctx.env.vars);
|
|
337
|
-
if (!Number.isNaN(value)) {
|
|
338
|
-
ctx.env.lastExitCode = value === 0 ? 1 : 0;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
} else if (block.type === "for") {
|
|
343
|
-
const listExpanded = await expandVars(
|
|
344
|
-
block.list,
|
|
345
|
-
ctx.env.vars,
|
|
346
|
-
ctx.env.lastExitCode,
|
|
347
|
-
ctx,
|
|
348
|
-
);
|
|
349
|
-
// Apply brace expansion to each token in the list
|
|
350
|
-
const items = listExpanded.trim().split(/\s+/).flatMap(expandBraces);
|
|
351
|
-
for (const item of items) {
|
|
352
|
-
ctx.env.vars[block.var] = item;
|
|
353
|
-
const sub = await runBlocks(parseBlocks(block.body), ctx);
|
|
354
|
-
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
355
|
-
if (sub.closeSession) return sub;
|
|
356
|
-
}
|
|
357
|
-
} else if (block.type === "while") {
|
|
358
|
-
let iterations = 0;
|
|
359
|
-
while (iterations < 1000 && (await evalCondition(block.cond, ctx))) {
|
|
360
|
-
const sub = await runBlocks(parseBlocks(block.body), ctx);
|
|
361
|
-
if (sub.stdout) output += `${sub.stdout}\n`;
|
|
362
|
-
if (sub.closeSession) return sub;
|
|
363
|
-
iterations++;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return { ...lastResult, stdout: output.trim() || lastResult.stdout };
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Execute shell scripts or commands with a minimal shell interpreter.
|
|
372
|
-
* Supports if/elif/else, for loops, while loops, and variable expansion.
|
|
373
|
-
* @category shell
|
|
374
|
-
* @params ["-c <script>", "[<file>]"]
|
|
375
|
-
*/
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Split a sh script into logical lines, respecting:
|
|
379
|
-
* - `{...}` braces (function bodies)
|
|
380
|
-
* - Newlines and semicolons at depth 0 only
|
|
381
|
-
*/
|
|
382
|
-
function splitShScript(script: string): string[] {
|
|
383
|
-
const lines: string[] = [];
|
|
384
|
-
let current = "";
|
|
385
|
-
let depth = 0;
|
|
386
|
-
let inSingleQ = false;
|
|
387
|
-
let inDoubleQ = false;
|
|
388
|
-
let i = 0;
|
|
389
|
-
while (i < script.length) {
|
|
390
|
-
const ch = script[i]!;
|
|
391
|
-
if (!inSingleQ && !inDoubleQ) {
|
|
392
|
-
if (ch === "'") { inSingleQ = true; current += ch; i++; continue; }
|
|
393
|
-
if (ch === '"') { inDoubleQ = true; current += ch; i++; continue; }
|
|
394
|
-
if (ch === "{") { depth++; current += ch; i++; continue; }
|
|
395
|
-
if (ch === "}") {
|
|
396
|
-
depth--;
|
|
397
|
-
current += ch;
|
|
398
|
-
i++;
|
|
399
|
-
// At depth 0, closing } ends the function body line
|
|
400
|
-
if (depth === 0) {
|
|
401
|
-
const t = current.trim();
|
|
402
|
-
if (t) lines.push(t);
|
|
403
|
-
current = "";
|
|
404
|
-
// Skip trailing ; or whitespace
|
|
405
|
-
while (i < script.length && (script[i] === ";" || script[i] === " ")) i++;
|
|
406
|
-
}
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
if (depth === 0 && (ch === ";" || ch === "\n")) {
|
|
410
|
-
const t = current.trim();
|
|
411
|
-
if (t && !t.startsWith("#")) lines.push(t);
|
|
412
|
-
current = "";
|
|
413
|
-
i++;
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
} else if (inSingleQ && ch === "'") {
|
|
417
|
-
inSingleQ = false;
|
|
418
|
-
} else if (inDoubleQ && ch === '"') {
|
|
419
|
-
inDoubleQ = false;
|
|
420
|
-
}
|
|
421
|
-
current += ch;
|
|
422
|
-
i++;
|
|
423
|
-
}
|
|
424
|
-
const t = current.trim();
|
|
425
|
-
if (t && !t.startsWith("#")) lines.push(t);
|
|
426
|
-
return lines;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export const shCommand: ShellModule = {
|
|
430
|
-
name: "sh",
|
|
431
|
-
aliases: ["bash"],
|
|
432
|
-
description: "Execute shell script or command",
|
|
433
|
-
category: "shell",
|
|
434
|
-
params: ["-c <script>", "[<file>]"],
|
|
435
|
-
run: async (ctx: CommandContext) => {
|
|
436
|
-
const { args, shell, cwd } = ctx;
|
|
437
|
-
|
|
438
|
-
// sh -c "inline script"
|
|
439
|
-
if (ifFlag(args, "-c")) {
|
|
440
|
-
const script = args[args.indexOf("-c") + 1] ?? "";
|
|
441
|
-
if (!script) return { stderr: "sh: -c requires a script", exitCode: 1 };
|
|
442
|
-
const lines = splitShScript(script);
|
|
443
|
-
const blocks = parseBlocks(lines);
|
|
444
|
-
return runBlocks(blocks, ctx);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// sh <file>
|
|
448
|
-
const fileArg = args[0];
|
|
449
|
-
if (fileArg) {
|
|
450
|
-
const p = resolvePath(cwd, fileArg);
|
|
451
|
-
if (!shell.vfs.exists(p))
|
|
452
|
-
return {
|
|
453
|
-
stderr: `sh: ${fileArg}: No such file or directory`,
|
|
454
|
-
exitCode: 1,
|
|
455
|
-
};
|
|
456
|
-
const content = shell.vfs.readFile(p);
|
|
457
|
-
const lines = splitShScript(content);
|
|
458
|
-
const blocks = parseBlocks(lines);
|
|
459
|
-
return runBlocks(blocks, ctx);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return {
|
|
463
|
-
stderr: "sh: invalid usage. Use: sh -c 'cmd' or sh <file>",
|
|
464
|
-
exitCode: 1,
|
|
465
|
-
};
|
|
466
|
-
},
|
|
467
|
-
};
|
package/src/commands/shift.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Shift positional parameters (remove first N arguments).
|
|
5
|
-
* @category shell
|
|
6
|
-
* @params ["[n]"]
|
|
7
|
-
*/
|
|
8
|
-
export const shiftCommand: ShellModule = {
|
|
9
|
-
name: "shift",
|
|
10
|
-
description: "Shift positional parameters",
|
|
11
|
-
category: "shell",
|
|
12
|
-
params: ["[n]"],
|
|
13
|
-
// shift is meaningful only inside sh scripts where positional params exist.
|
|
14
|
-
// In the current impl, positional params ($1 $2 …) aren't tracked in env by default.
|
|
15
|
-
// We store them under env.vars.__argv and shift there if present.
|
|
16
|
-
run: ({ args, env }) => {
|
|
17
|
-
if (!env) return { exitCode: 0 };
|
|
18
|
-
const n = parseInt(args[0] ?? "1", 10) || 1;
|
|
19
|
-
const argv = env.vars.__argv?.split("\x00").filter(Boolean) ?? [];
|
|
20
|
-
env.vars.__argv = argv.slice(n).join("\x00");
|
|
21
|
-
// Update $1 $2 … in env
|
|
22
|
-
const shifted = argv.slice(n);
|
|
23
|
-
for (let i = 1; i <= 9; i++) {
|
|
24
|
-
env.vars[String(i)] = shifted[i - 1] ?? "";
|
|
25
|
-
}
|
|
26
|
-
return { exitCode: 0 };
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Trap signals and execute actions on signal receipt or shell exit.
|
|
32
|
-
* @category shell
|
|
33
|
-
* @params ["[action] [signal...]"]
|
|
34
|
-
*/
|
|
35
|
-
export const trapCommand: ShellModule = {
|
|
36
|
-
name: "trap",
|
|
37
|
-
description: "Trap signals and events",
|
|
38
|
-
category: "shell",
|
|
39
|
-
params: ["[action] [signal...]"],
|
|
40
|
-
// Store trap handlers in env for EXIT signal support
|
|
41
|
-
run: ({ args, env }) => {
|
|
42
|
-
if (!env || args.length === 0) return { exitCode: 0 };
|
|
43
|
-
const action = args[0] ?? "";
|
|
44
|
-
const signals = args.slice(1);
|
|
45
|
-
for (const sig of signals) {
|
|
46
|
-
env.vars[`__trap_${sig.toUpperCase()}`] = action;
|
|
47
|
-
}
|
|
48
|
-
return { exitCode: 0 };
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const returnCommand: ShellModule = {
|
|
53
|
-
name: "return",
|
|
54
|
-
description: "Return from a shell function",
|
|
55
|
-
category: "shell",
|
|
56
|
-
params: ["[n]"],
|
|
57
|
-
run: ({ args, env }) => {
|
|
58
|
-
const code = parseInt(args[0] ?? "0", 10);
|
|
59
|
-
if (env) env.lastExitCode = code;
|
|
60
|
-
// Signal the caller via exitCode; function return is handled by runBlocks
|
|
61
|
-
return { exitCode: code };
|
|
62
|
-
},
|
|
63
|
-
};
|
package/src/commands/sleep.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ShellModule } from "../types/commands";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Delay execution for a specified number of seconds.
|
|
5
|
-
* @category system
|
|
6
|
-
* @params ["<seconds>"]
|
|
7
|
-
*/
|
|
8
|
-
export const sleepCommand: ShellModule = {
|
|
9
|
-
name: "sleep",
|
|
10
|
-
description: "Delay execution",
|
|
11
|
-
category: "system",
|
|
12
|
-
params: ["<seconds>"],
|
|
13
|
-
run: async ({ args }) => {
|
|
14
|
-
const secs = parseFloat(args[0] ?? "1");
|
|
15
|
-
if (Number.isNaN(secs) || secs < 0)
|
|
16
|
-
return { stderr: "sleep: invalid time", exitCode: 1 };
|
|
17
|
-
await new Promise((r) => setTimeout(r, secs * 1000));
|
|
18
|
-
return { exitCode: 0 };
|
|
19
|
-
},
|
|
20
|
-
};
|