@aliou/pi-processes 0.4.5 → 0.4.6
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/package.json +1 -1
- package/src/commands/index.ts +334 -0
- package/src/commands/settings-command.ts +157 -0
- package/src/components/log-stream-component.ts +149 -0
- package/src/components/process-picker-component.ts +155 -0
- package/src/components/processes-component.ts +450 -0
- package/src/components/status-format.ts +38 -0
- package/src/config.ts +70 -0
- package/src/constants/index.ts +11 -0
- package/src/constants/types.ts +65 -0
- package/src/hooks/cleanup.ts +10 -0
- package/src/hooks/index.ts +18 -0
- package/src/hooks/message-renderer.ts +83 -0
- package/src/hooks/process-end.ts +92 -0
- package/src/hooks/widget.ts +146 -0
- package/src/index.ts +22 -20
- package/src/manager.ts +522 -0
- package/src/test/test-exit-crash.sh +19 -0
- package/src/test/test-exit-failure.sh +17 -0
- package/src/test/test-exit-success.sh +16 -0
- package/src/test/test-output.sh +28 -0
- package/src/tools/actions/clear.ts +20 -0
- package/src/tools/actions/index.ts +49 -0
- package/src/tools/actions/kill.ts +76 -0
- package/src/tools/actions/list.ts +37 -0
- package/src/tools/actions/logs.ts +59 -0
- package/src/tools/actions/output.ts +144 -0
- package/src/tools/actions/start.ts +55 -0
- package/src/tools/index.ts +280 -0
- package/src/utils/ansi.ts +36 -0
- package/src/utils/command-executor.test.ts +47 -0
- package/src/utils/command-executor.ts +56 -0
- package/src/utils/format.ts +42 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/process-group.ts +22 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { resolveShellExecutable } from "./command-executor";
|
|
4
|
+
|
|
5
|
+
vi.mock("node:fs", async (importOriginal) => {
|
|
6
|
+
const actual = await importOriginal<typeof import("node:fs")>();
|
|
7
|
+
return { ...actual, existsSync: vi.fn() };
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const existsSyncMock = vi.mocked(existsSync);
|
|
11
|
+
|
|
12
|
+
describe("resolveShellExecutable", () => {
|
|
13
|
+
it("prefers shell configured in settings when it is an existing absolute path", () => {
|
|
14
|
+
existsSyncMock.mockImplementation(
|
|
15
|
+
(path) => path === "/nix/store/abc-bash-5.3/bin/bash",
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const resolved = resolveShellExecutable({
|
|
19
|
+
configuredShell: "/nix/store/abc-bash-5.3/bin/bash",
|
|
20
|
+
knownPaths: ["/bin/bash", "/usr/bin/bash"],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(resolved).toBe("/nix/store/abc-bash-5.3/bin/bash");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("falls back to first existing known shell path", () => {
|
|
27
|
+
existsSyncMock.mockImplementation((path) => path === "/usr/bin/bash");
|
|
28
|
+
|
|
29
|
+
const resolved = resolveShellExecutable({
|
|
30
|
+
configuredShell: undefined,
|
|
31
|
+
knownPaths: ["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(resolved).toBe("/usr/bin/bash");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("throws when no configured/known shell path exists", () => {
|
|
38
|
+
existsSyncMock.mockReturnValue(false);
|
|
39
|
+
|
|
40
|
+
expect(() =>
|
|
41
|
+
resolveShellExecutable({
|
|
42
|
+
configuredShell: undefined,
|
|
43
|
+
knownPaths: ["/bin/bash", "/usr/bin/bash"],
|
|
44
|
+
}),
|
|
45
|
+
).toThrow(/shell/i);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -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,42 @@
|
|
|
1
|
+
import type { ProcessInfo } from "../constants";
|
|
2
|
+
|
|
3
|
+
export function formatRuntime(
|
|
4
|
+
startTime: number,
|
|
5
|
+
endTime: number | null,
|
|
6
|
+
): string {
|
|
7
|
+
const end = endTime ?? Date.now();
|
|
8
|
+
const ms = end - startTime;
|
|
9
|
+
const seconds = Math.floor(ms / 1000);
|
|
10
|
+
const minutes = Math.floor(seconds / 60);
|
|
11
|
+
const hours = Math.floor(minutes / 60);
|
|
12
|
+
|
|
13
|
+
if (hours > 0) {
|
|
14
|
+
return `${hours}h ${minutes % 60}m`;
|
|
15
|
+
}
|
|
16
|
+
if (minutes > 0) {
|
|
17
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
18
|
+
}
|
|
19
|
+
return `${seconds}s`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatStatus(proc: ProcessInfo): string {
|
|
23
|
+
switch (proc.status) {
|
|
24
|
+
case "running":
|
|
25
|
+
return "running";
|
|
26
|
+
case "terminating":
|
|
27
|
+
return "terminating";
|
|
28
|
+
case "terminate_timeout":
|
|
29
|
+
return "terminate_timeout";
|
|
30
|
+
case "killed":
|
|
31
|
+
return "killed";
|
|
32
|
+
case "exited":
|
|
33
|
+
return proc.success ? "exit(0)" : `exit(${proc.exitCode ?? "?"})`;
|
|
34
|
+
default:
|
|
35
|
+
return proc.status;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function truncateCmd(cmd: string, max = 40): string {
|
|
40
|
+
if (cmd.length <= max) return cmd;
|
|
41
|
+
return `${cmd.slice(0, max - 3)}...`;
|
|
42
|
+
}
|
|
@@ -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
|
+
}
|