@haemmid/pi-processes 0.9.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/CHANGELOG.md +63 -0
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/assets/demo-pi-web.gif +0 -0
- package/assets/hero.svg +141 -0
- package/package.json +73 -0
- package/src/config.ts +125 -0
- package/src/constants/index.ts +11 -0
- package/src/constants/types.ts +56 -0
- package/src/hooks/background-blocker.ts +309 -0
- package/src/hooks/cleanup.ts +8 -0
- package/src/hooks/index.ts +17 -0
- package/src/index.ts +23 -0
- package/src/manager.ts +504 -0
- package/src/tools/actions/clear.ts +20 -0
- package/src/tools/actions/index.ts +51 -0
- package/src/tools/actions/kill.ts +108 -0
- package/src/tools/actions/list.ts +48 -0
- package/src/tools/actions/logs.ts +86 -0
- package/src/tools/actions/output.ts +186 -0
- package/src/tools/actions/restart.ts +113 -0
- package/src/tools/actions/start.ts +87 -0
- package/src/tools/index.ts +82 -0
- package/src/utils/ansi.ts +67 -0
- package/src/utils/command-executor.ts +56 -0
- package/src/utils/format.ts +53 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/process-group.ts +22 -0
- package/src/utils/shell-utils.ts +133 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { isAbsolute } from "node:path";
|
|
4
|
+
|
|
5
|
+
interface ResolveShellExecutableOptions {
|
|
6
|
+
configuredShell?: string;
|
|
7
|
+
knownPaths: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DEFAULT_KNOWN_SHELL_PATHS = [
|
|
11
|
+
"/run/current-system/sw/bin/bash",
|
|
12
|
+
"/bin/bash",
|
|
13
|
+
"/usr/bin/bash",
|
|
14
|
+
"/usr/local/bin/bash",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function isExistingAbsolutePath(shell: string | undefined): shell is string {
|
|
18
|
+
return typeof shell === "string" && isAbsolute(shell) && existsSync(shell);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveShellExecutable({
|
|
22
|
+
configuredShell,
|
|
23
|
+
knownPaths,
|
|
24
|
+
}: ResolveShellExecutableOptions): string {
|
|
25
|
+
if (isExistingAbsolutePath(configuredShell)) {
|
|
26
|
+
return configuredShell;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const path of knownPaths) {
|
|
30
|
+
if (isExistingAbsolutePath(path)) {
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error(
|
|
36
|
+
"Unable to resolve shell executable. Checked configured shell and known shell paths.",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function spawnCommand(
|
|
41
|
+
command: string,
|
|
42
|
+
cwd: string,
|
|
43
|
+
configuredShell?: string,
|
|
44
|
+
): ChildProcess {
|
|
45
|
+
const shellExecutable = resolveShellExecutable({
|
|
46
|
+
configuredShell,
|
|
47
|
+
knownPaths: DEFAULT_KNOWN_SHELL_PATHS,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return spawn(shellExecutable, ["-lc", command], {
|
|
51
|
+
cwd,
|
|
52
|
+
env: process.env,
|
|
53
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
54
|
+
detached: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ProcessInfo } from "../constants";
|
|
2
|
+
|
|
3
|
+
export function formatTimestamp(timestamp: number): string {
|
|
4
|
+
const date = new Date(timestamp);
|
|
5
|
+
const datePart = `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
|
6
|
+
const timePart = `${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
|
|
7
|
+
return `${datePart} ${timePart}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function pad2(value: number): string {
|
|
11
|
+
return value.toString().padStart(2, "0");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function formatRuntime(
|
|
15
|
+
startTime: number,
|
|
16
|
+
endTime: number | null,
|
|
17
|
+
): string {
|
|
18
|
+
const end = endTime ?? Date.now();
|
|
19
|
+
const ms = end - startTime;
|
|
20
|
+
const seconds = Math.floor(ms / 1000);
|
|
21
|
+
const minutes = Math.floor(seconds / 60);
|
|
22
|
+
const hours = Math.floor(minutes / 60);
|
|
23
|
+
|
|
24
|
+
if (hours > 0) {
|
|
25
|
+
return `${hours}h ${minutes % 60}m`;
|
|
26
|
+
}
|
|
27
|
+
if (minutes > 0) {
|
|
28
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
29
|
+
}
|
|
30
|
+
return `${seconds}s`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatStatus(proc: ProcessInfo): string {
|
|
34
|
+
switch (proc.status) {
|
|
35
|
+
case "running":
|
|
36
|
+
return "running";
|
|
37
|
+
case "terminating":
|
|
38
|
+
return "terminating";
|
|
39
|
+
case "terminate_timeout":
|
|
40
|
+
return "terminate_timeout";
|
|
41
|
+
case "killed":
|
|
42
|
+
return "killed";
|
|
43
|
+
case "exited":
|
|
44
|
+
return proc.success ? "exit(0)" : `exit(${proc.exitCode ?? "?"})`;
|
|
45
|
+
default:
|
|
46
|
+
return proc.status;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function truncateCmd(cmd: string, max = 40): string {
|
|
51
|
+
if (cmd.length <= max) return cmd;
|
|
52
|
+
return `${cmd.slice(0, max - 3)}...`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a process group is still alive.
|
|
3
|
+
* Uses signal 0 to test existence without actually sending a signal.
|
|
4
|
+
*/
|
|
5
|
+
export function isProcessGroupAlive(pgid: number): boolean {
|
|
6
|
+
try {
|
|
7
|
+
process.kill(-pgid, 0);
|
|
8
|
+
return true;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
const err = error as NodeJS.ErrnoException;
|
|
11
|
+
// EPERM: exists, but we can't signal it
|
|
12
|
+
return err.code === "EPERM";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Send a signal to an entire process group.
|
|
18
|
+
* Negative PID targets the process group.
|
|
19
|
+
*/
|
|
20
|
+
export function killProcessGroup(pgid: number, signal: NodeJS.Signals): void {
|
|
21
|
+
process.kill(-pgid, signal);
|
|
22
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Shell AST helpers. Duplicated from pi-toolchain since cross-extension imports are not allowed.
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
Command,
|
|
5
|
+
Program,
|
|
6
|
+
SimpleCommand,
|
|
7
|
+
Statement,
|
|
8
|
+
Word,
|
|
9
|
+
WordPart,
|
|
10
|
+
} from "@aliou/sh";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a Word node to its literal string value.
|
|
14
|
+
* Concatenates Literal, SglQuoted, and simple DblQuoted parts.
|
|
15
|
+
* For parts containing parameter expansions, command substitutions, etc.,
|
|
16
|
+
* includes the raw text representation (e.g. `$VAR`).
|
|
17
|
+
*/
|
|
18
|
+
export function wordToString(word: Word): string {
|
|
19
|
+
return word.parts.map(partToString).join("");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function partToString(part: WordPart): string {
|
|
23
|
+
switch (part.type) {
|
|
24
|
+
case "Literal":
|
|
25
|
+
return part.value;
|
|
26
|
+
case "SglQuoted":
|
|
27
|
+
return part.value;
|
|
28
|
+
case "DblQuoted":
|
|
29
|
+
return part.parts.map(partToString).join("");
|
|
30
|
+
case "ParamExp":
|
|
31
|
+
return part.short
|
|
32
|
+
? `$${part.param.value}`
|
|
33
|
+
: `\${${part.param.value}${part.op ?? ""}${part.value ? wordToString(part.value) : ""}}`;
|
|
34
|
+
case "CmdSubst":
|
|
35
|
+
return "$(...)";
|
|
36
|
+
case "ArithExp":
|
|
37
|
+
return `$((${part.expr}))`;
|
|
38
|
+
case "ProcSubst":
|
|
39
|
+
return `${part.op}(...)`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Walk the AST and call `callback` for every SimpleCommand found at any
|
|
45
|
+
* nesting depth. Returns early if callback returns `true`.
|
|
46
|
+
*/
|
|
47
|
+
export function walkCommands(
|
|
48
|
+
node: Program,
|
|
49
|
+
callback: (cmd: SimpleCommand) => boolean | undefined,
|
|
50
|
+
): void {
|
|
51
|
+
for (const stmt of node.body) {
|
|
52
|
+
if (walkStatement(stmt, callback)) return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function walkStatement(
|
|
57
|
+
stmt: Statement,
|
|
58
|
+
callback: (cmd: SimpleCommand) => boolean | undefined,
|
|
59
|
+
): boolean {
|
|
60
|
+
return walkCommand(stmt.command, callback);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function walkStatements(
|
|
64
|
+
stmts: Statement[],
|
|
65
|
+
callback: (cmd: SimpleCommand) => boolean | undefined,
|
|
66
|
+
): boolean {
|
|
67
|
+
for (const stmt of stmts) {
|
|
68
|
+
if (walkStatement(stmt, callback)) return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function walkCommand(
|
|
74
|
+
cmd: Command,
|
|
75
|
+
callback: (cmd: SimpleCommand) => boolean | undefined,
|
|
76
|
+
): boolean {
|
|
77
|
+
switch (cmd.type) {
|
|
78
|
+
case "SimpleCommand":
|
|
79
|
+
return callback(cmd) === true;
|
|
80
|
+
|
|
81
|
+
case "Pipeline":
|
|
82
|
+
return walkStatements(cmd.commands, callback);
|
|
83
|
+
|
|
84
|
+
case "Logical":
|
|
85
|
+
return (
|
|
86
|
+
walkStatement(cmd.left, callback) || walkStatement(cmd.right, callback)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
case "Subshell":
|
|
90
|
+
case "Block":
|
|
91
|
+
return walkStatements(cmd.body, callback);
|
|
92
|
+
|
|
93
|
+
case "IfClause":
|
|
94
|
+
return (
|
|
95
|
+
walkStatements(cmd.cond, callback) ||
|
|
96
|
+
walkStatements(cmd.then, callback) ||
|
|
97
|
+
(cmd.else ? walkStatements(cmd.else, callback) : false)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
case "ForClause":
|
|
101
|
+
case "SelectClause":
|
|
102
|
+
case "WhileClause":
|
|
103
|
+
return (
|
|
104
|
+
("cond" in cmd && cmd.cond
|
|
105
|
+
? walkStatements(cmd.cond, callback)
|
|
106
|
+
: false) || walkStatements(cmd.body, callback)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
case "CaseClause":
|
|
110
|
+
for (const item of cmd.items) {
|
|
111
|
+
if (walkStatements(item.body, callback)) return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
|
|
115
|
+
case "FunctionDecl":
|
|
116
|
+
return walkStatements(cmd.body, callback);
|
|
117
|
+
|
|
118
|
+
case "TimeClause":
|
|
119
|
+
return walkStatement(cmd.command, callback);
|
|
120
|
+
|
|
121
|
+
case "CoprocClause":
|
|
122
|
+
return walkStatement(cmd.body, callback);
|
|
123
|
+
|
|
124
|
+
case "CStyleLoop":
|
|
125
|
+
return walkStatements(cmd.body, callback);
|
|
126
|
+
|
|
127
|
+
case "TestClause":
|
|
128
|
+
case "ArithCmd":
|
|
129
|
+
case "DeclClause":
|
|
130
|
+
case "LetClause":
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|