@gajae-code/utils 0.1.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/dist/types/abortable.d.ts +27 -0
- package/dist/types/async.d.ts +6 -0
- package/dist/types/cli.d.ts +117 -0
- package/dist/types/color.d.ts +82 -0
- package/dist/types/dirs.d.ts +147 -0
- package/dist/types/env.d.ts +55 -0
- package/dist/types/fetch-retry.d.ts +80 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +25 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/hook-fetch.d.ts +16 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/json.d.ts +4 -0
- package/dist/types/logger.d.ts +66 -0
- package/dist/types/mermaid-ascii.d.ts +11 -0
- package/dist/types/mime.d.ts +29 -0
- package/dist/types/peek-file.d.ts +9 -0
- package/dist/types/postmortem.d.ts +29 -0
- package/dist/types/procmgr.d.ts +35 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +108 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/sanitize-text.d.ts +14 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stream.d.ts +68 -0
- package/dist/types/tab-spacing.d.ts +9 -0
- package/dist/types/temp.d.ts +14 -0
- package/dist/types/type-guards.d.ts +3 -0
- package/dist/types/which.d.ts +37 -0
- package/package.json +61 -0
- package/src/abortable.ts +73 -0
- package/src/async.ts +50 -0
- package/src/cli.ts +432 -0
- package/src/color.ts +204 -0
- package/src/dirs.ts +487 -0
- package/src/env.ts +162 -0
- package/src/fetch-retry.ts +298 -0
- package/src/format.ts +112 -0
- package/src/frontmatter.ts +128 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/hook-fetch.ts +30 -0
- package/src/index.ts +49 -0
- package/src/json.ts +10 -0
- package/src/logger.ts +392 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mime.ts +159 -0
- package/src/peek-file.ts +114 -0
- package/src/postmortem.ts +196 -0
- package/src/procmgr.ts +209 -0
- package/src/prompt.ts +471 -0
- package/src/ptree.ts +390 -0
- package/src/ring.ts +169 -0
- package/src/sanitize-text.ts +38 -0
- package/src/snowflake.ts +136 -0
- package/src/stream.ts +403 -0
- package/src/tab-spacing.ts +312 -0
- package/src/temp.ts +77 -0
- package/src/type-guards.ts +11 -0
- package/src/which.ts +232 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup and postmortem handler utilities.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a system for registering and running cleanup callbacks
|
|
5
|
+
* in response to process exit, signals, or fatal exceptions. It is intended to
|
|
6
|
+
* allow reliably releasing resources or shutting down subprocesses, files, sockets, etc.
|
|
7
|
+
*/
|
|
8
|
+
import inspector from "node:inspector";
|
|
9
|
+
import { isMainThread } from "node:worker_threads";
|
|
10
|
+
import { logger } from ".";
|
|
11
|
+
|
|
12
|
+
// Cleanup reasons, in order of priority/meaning.
|
|
13
|
+
export enum Reason {
|
|
14
|
+
PRE_EXIT = "pre_exit", // Pre-exit phase (not used by default)
|
|
15
|
+
EXIT = "exit", // Normal process exit
|
|
16
|
+
SIGINT = "sigint", // Ctrl-C or SIGINT
|
|
17
|
+
SIGTERM = "sigterm", // SIGTERM
|
|
18
|
+
SIGHUP = "sighup", // SIGHUP
|
|
19
|
+
UNCAUGHT_EXCEPTION = "uncaught_exception", // Fatal exception
|
|
20
|
+
UNHANDLED_REJECTION = "unhandled_rejection", // Unhandled promise rejection
|
|
21
|
+
MANUAL = "manual", // Manual cleanup (not triggered by process)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Internal list of active cleanup callbacks (in registration order)
|
|
25
|
+
const callbackList: ((reason: Reason) => Promise<void> | void)[] = [];
|
|
26
|
+
// Tracks cleanup run state (to prevent recursion/reentry issues)
|
|
27
|
+
let cleanupStage: "idle" | "running" | "complete" = "idle";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Internal: runs all registered cleanup callbacks for the given reason.
|
|
31
|
+
* Ensures each callback is invoked at most once. Handles errors and prevents reentrancy.
|
|
32
|
+
*
|
|
33
|
+
* Returns a Promise that settles after all cleanups complete or error out.
|
|
34
|
+
*/
|
|
35
|
+
function runCleanup(reason: Reason): Promise<void> {
|
|
36
|
+
switch (cleanupStage) {
|
|
37
|
+
case "idle":
|
|
38
|
+
cleanupStage = "running";
|
|
39
|
+
break;
|
|
40
|
+
case "running":
|
|
41
|
+
logger.error("Cleanup invoked recursively", { stack: new Error().stack });
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
case "complete":
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Call .cleanup() for each callback that is still "armed".
|
|
48
|
+
// Use Promise.try to handle sync/async, but only those armed.
|
|
49
|
+
const promises = callbackList.toReversed().map(callback => {
|
|
50
|
+
return Promise.try(() => callback(reason));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return Promise.allSettled(promises).then(results => {
|
|
54
|
+
for (const result of results) {
|
|
55
|
+
if (result.status === "rejected") {
|
|
56
|
+
const err = result.reason instanceof Error ? result.reason : new Error(String(result.reason));
|
|
57
|
+
logger.error("Cleanup callback failed", { err, stack: err.stack });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
cleanupStage = "complete";
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Register signal and error event handlers to trigger cleanup before exit.
|
|
65
|
+
// Main thread: full signal handling (SIGINT, SIGTERM, SIGHUP) + exceptions + exit
|
|
66
|
+
// Worker thread: exit only (workers use self.addEventListener for exceptions)
|
|
67
|
+
let inspectorOpened = false;
|
|
68
|
+
|
|
69
|
+
function formatFatalError(label: string, err: Error): string {
|
|
70
|
+
const name = err.name || "Error";
|
|
71
|
+
const message = err.message || "(no message)";
|
|
72
|
+
const stack = err.stack || "";
|
|
73
|
+
const stackLines = stack.split("\n").slice(1);
|
|
74
|
+
const formattedStack = stackLines.length > 0 ? `\n${stackLines.join("\n")}` : "";
|
|
75
|
+
return `\n[${label}] ${name}: ${message}${formattedStack}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isMainThread) {
|
|
79
|
+
process
|
|
80
|
+
.on("SIGINT", async () => {
|
|
81
|
+
await runCleanup(Reason.SIGINT);
|
|
82
|
+
process.exit(130); // 128 + SIGINT (2)
|
|
83
|
+
})
|
|
84
|
+
.on("SIGUSR1", () => {
|
|
85
|
+
if (inspectorOpened) return;
|
|
86
|
+
inspectorOpened = true;
|
|
87
|
+
inspector.open(undefined, undefined, false);
|
|
88
|
+
const url = inspector.url();
|
|
89
|
+
process.stderr.write(`Inspector opened: ${url}\n`);
|
|
90
|
+
})
|
|
91
|
+
.on("uncaughtException", async err => {
|
|
92
|
+
process.stderr.write(formatFatalError("Uncaught Exception", err));
|
|
93
|
+
logger.error("Uncaught exception", { err, stack: err.stack });
|
|
94
|
+
await runCleanup(Reason.UNCAUGHT_EXCEPTION);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
})
|
|
97
|
+
.on("unhandledRejection", async reason => {
|
|
98
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
99
|
+
process.stderr.write(formatFatalError("Unhandled Rejection", err));
|
|
100
|
+
logger.error("Unhandled rejection", { err, stack: err.stack });
|
|
101
|
+
await runCleanup(Reason.UNHANDLED_REJECTION);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
})
|
|
104
|
+
.on("exit", async () => {
|
|
105
|
+
void runCleanup(Reason.EXIT); // fire and forget (exit imminent)
|
|
106
|
+
})
|
|
107
|
+
.on("SIGTERM", async () => {
|
|
108
|
+
await runCleanup(Reason.SIGTERM);
|
|
109
|
+
process.exit(143); // 128 + SIGTERM (15)
|
|
110
|
+
})
|
|
111
|
+
.on("SIGHUP", async () => {
|
|
112
|
+
await runCleanup(Reason.SIGHUP);
|
|
113
|
+
process.exit(129); // 128 + SIGHUP (1)
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
// Worker thread: only register exit handler for cleanup.
|
|
117
|
+
// DO NOT register uncaughtException/unhandledRejection handlers here -
|
|
118
|
+
// they would swallow errors before the worker's own handlers (self.addEventListener)
|
|
119
|
+
// can report failures back to the parent thread.
|
|
120
|
+
process.on("exit", () => {
|
|
121
|
+
void runCleanup(Reason.EXIT);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register a process cleanup callback, to be run on shutdown, signal, or fatal error.
|
|
127
|
+
*
|
|
128
|
+
* Returns a Callback instance that can be used to cancel (unregister) or manually clean up.
|
|
129
|
+
* If register is called after cleanup already began, invokes callback on a microtask.
|
|
130
|
+
*/
|
|
131
|
+
export function register(id: string, callback: (reason: Reason) => void | Promise<void>): () => void {
|
|
132
|
+
let done = false;
|
|
133
|
+
const exec = (reason: Reason) => {
|
|
134
|
+
if (done) return;
|
|
135
|
+
done = true;
|
|
136
|
+
try {
|
|
137
|
+
return callback(reason);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
140
|
+
logger.error("Cleanup callback failed", { err, id, stack: err.stack });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const cancel = () => {
|
|
145
|
+
const index = callbackList.indexOf(exec);
|
|
146
|
+
if (index >= 0) {
|
|
147
|
+
callbackList.splice(index, 1);
|
|
148
|
+
}
|
|
149
|
+
done = true;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (cleanupStage !== "idle") {
|
|
153
|
+
// If cleanup is already running/completed, warn and run on microtask.
|
|
154
|
+
logger.warn("Cleanup invoked recursively", { id });
|
|
155
|
+
try {
|
|
156
|
+
callback(Reason.MANUAL);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
159
|
+
logger.error("Cleanup callback failed", { err, id, stack: err.stack });
|
|
160
|
+
}
|
|
161
|
+
return () => {};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Register callback as "armed" (active).
|
|
165
|
+
callbackList.push(exec);
|
|
166
|
+
return cancel;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Runs all cleanup callbacks without exiting.
|
|
171
|
+
* Use this in workers or when you need to clean up but continue execution.
|
|
172
|
+
*/
|
|
173
|
+
export function cleanup(): Promise<void> {
|
|
174
|
+
return runCleanup(Reason.MANUAL);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Runs all cleanup callbacks and exits.
|
|
179
|
+
*
|
|
180
|
+
* In main thread: waits for stdout drain, then calls process.exit().
|
|
181
|
+
* In workers: runs cleanup only (process.exit would kill entire process).
|
|
182
|
+
*/
|
|
183
|
+
export async function quit(code: number = 0): Promise<void> {
|
|
184
|
+
await runCleanup(Reason.MANUAL);
|
|
185
|
+
|
|
186
|
+
if (!isMainThread) {
|
|
187
|
+
return; // Workers: cleanup done, let worker exit naturally
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (process.stdout.writableLength > 0) {
|
|
191
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
192
|
+
process.stdout.once("drain", resolve);
|
|
193
|
+
await Promise.race([promise, Bun.sleep(5000)]);
|
|
194
|
+
}
|
|
195
|
+
process.exit(code);
|
|
196
|
+
}
|
package/src/procmgr.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { Process, ProcessStatus } from "@gajae-code/natives";
|
|
4
|
+
import type { Subprocess } from "bun";
|
|
5
|
+
import { $env, filterProcessEnv } from "./env";
|
|
6
|
+
import { $which } from "./which";
|
|
7
|
+
|
|
8
|
+
export interface ShellConfig {
|
|
9
|
+
shell: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
env: Record<string, string>;
|
|
12
|
+
prefix: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
let cachedShellConfig: ShellConfig | null = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Strip disabled macOS malloc-stack-logging vars from `process.env` in place.
|
|
18
|
+
*
|
|
19
|
+
* macOS leaves `MallocStackLogging=0` (or similar) inherited by debug-attached
|
|
20
|
+
* shells. Bun's libc init then prints `MallocStackLogging: can't turn off
|
|
21
|
+
* malloc stack logging because it was not enabled.` to stderr for every
|
|
22
|
+
* subprocess. Scrubbing once at startup means every child we spawn — bash,
|
|
23
|
+
* bun subagents, plugin installs, ptree commands — inherits a clean env.
|
|
24
|
+
*/
|
|
25
|
+
export function scrubProcessEnv(): void {
|
|
26
|
+
delete process.env.MallocStackLogging;
|
|
27
|
+
delete process.env.MallocStackLoggingNoCompact;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if a shell binary is executable.
|
|
32
|
+
*/
|
|
33
|
+
function isExecutable(path: string): boolean {
|
|
34
|
+
try {
|
|
35
|
+
fs.accessSync(path, fs.constants.X_OK);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build the spawn environment (cached).
|
|
44
|
+
*/
|
|
45
|
+
function buildSpawnEnv(shell: string): Record<string, string> {
|
|
46
|
+
const noCI = $env.PI_BASH_NO_CI || $env.CLAUDE_BASH_NO_CI;
|
|
47
|
+
return {
|
|
48
|
+
...filterProcessEnv(Bun.env),
|
|
49
|
+
SHELL: shell,
|
|
50
|
+
GIT_EDITOR: "true",
|
|
51
|
+
GPG_TTY: "not a tty",
|
|
52
|
+
GJCCODE: "1",
|
|
53
|
+
CLAUDECODE: "1",
|
|
54
|
+
...(noCI ? {} : { CI: "true" }),
|
|
55
|
+
} as Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get shell args, optionally including login shell flag.
|
|
60
|
+
* Supports PI_BASH_NO_LOGIN and ANTHROPIC_MODEL_BASH_NO_LOGIN to skip -l.
|
|
61
|
+
*/
|
|
62
|
+
function getShellArgs(): string[] {
|
|
63
|
+
const noLogin = $env.PI_BASH_NO_LOGIN || $env.CLAUDE_BASH_NO_LOGIN;
|
|
64
|
+
return noLogin ? ["-c"] : ["-l", "-c"];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get shell prefix for wrapping commands (profilers, strace, etc.).
|
|
69
|
+
*/
|
|
70
|
+
function getShellPrefix(): string | undefined {
|
|
71
|
+
return $env.PI_SHELL_PREFIX || $env.CLAUDE_CODE_SHELL_PREFIX;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build full shell config from a shell path.
|
|
76
|
+
*/
|
|
77
|
+
function buildConfig(shell: string): ShellConfig {
|
|
78
|
+
return {
|
|
79
|
+
shell,
|
|
80
|
+
args: getShellArgs(),
|
|
81
|
+
env: buildSpawnEnv(shell),
|
|
82
|
+
prefix: getShellPrefix(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a basic shell (bash or sh) as fallback.
|
|
88
|
+
*/
|
|
89
|
+
export function resolveBasicShell(): string | undefined {
|
|
90
|
+
for (const name of ["bash", "bash.exe", "sh", "sh.exe"]) {
|
|
91
|
+
const resolved = $which(name);
|
|
92
|
+
if (resolved) return resolved;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (process.platform !== "win32") {
|
|
96
|
+
const searchPaths = ["/bin", "/usr/bin", "/usr/local/bin", "/opt/homebrew/bin"];
|
|
97
|
+
const candidates = ["bash", "sh"];
|
|
98
|
+
|
|
99
|
+
for (const name of candidates) {
|
|
100
|
+
for (const dir of searchPaths) {
|
|
101
|
+
const fullPath = path.join(dir, name);
|
|
102
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get shell configuration based on platform.
|
|
112
|
+
* Resolution order:
|
|
113
|
+
* 1. User-specified shellPath in settings.json
|
|
114
|
+
* 2. On Windows: Git Bash in known locations, then bash on PATH
|
|
115
|
+
* 3. On Unix: $SHELL if bash/zsh, then fallback paths
|
|
116
|
+
* 4. Fallback: sh
|
|
117
|
+
*/
|
|
118
|
+
export function getShellConfig(customShellPath?: string): ShellConfig {
|
|
119
|
+
if (cachedShellConfig) {
|
|
120
|
+
return cachedShellConfig;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 1. Check user-specified shell path
|
|
124
|
+
if (customShellPath) {
|
|
125
|
+
if (fs.existsSync(customShellPath)) {
|
|
126
|
+
cachedShellConfig = buildConfig(customShellPath);
|
|
127
|
+
return cachedShellConfig;
|
|
128
|
+
}
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Custom shell path not found: ${customShellPath}\nPlease update shellPath in ~/.gjc/agent/settings.json`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (process.platform === "win32") {
|
|
135
|
+
// 2. Try Git Bash in known locations
|
|
136
|
+
const paths: string[] = [];
|
|
137
|
+
const programFiles = Bun.env.ProgramFiles;
|
|
138
|
+
if (programFiles) {
|
|
139
|
+
paths.push(`${programFiles}\\Git\\bin\\bash.exe`);
|
|
140
|
+
}
|
|
141
|
+
const programFilesX86 = Bun.env["ProgramFiles(x86)"];
|
|
142
|
+
if (programFilesX86) {
|
|
143
|
+
paths.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const path of paths) {
|
|
147
|
+
if (fs.existsSync(path)) {
|
|
148
|
+
cachedShellConfig = buildConfig(path);
|
|
149
|
+
return cachedShellConfig;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)
|
|
154
|
+
const bashOnPath = $which("bash.exe");
|
|
155
|
+
if (bashOnPath) {
|
|
156
|
+
cachedShellConfig = buildConfig(bashOnPath);
|
|
157
|
+
return cachedShellConfig;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw new Error(
|
|
161
|
+
`No bash shell found. Options:\n` +
|
|
162
|
+
` 1. Install Git for Windows: https://git-scm.com/download/win\n` +
|
|
163
|
+
` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\n` +
|
|
164
|
+
` 3. Set shellPath in ~/.gjc/agent/settings.json\n\n` +
|
|
165
|
+
`Searched Git Bash in:\n${paths.map(p => ` ${p}`).join("\n")}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Unix: prefer user's shell from $SHELL if it's bash/zsh and executable
|
|
170
|
+
const userShell = Bun.env.SHELL;
|
|
171
|
+
const isValidShell = userShell && (userShell.includes("bash") || userShell.includes("zsh"));
|
|
172
|
+
if (isValidShell && isExecutable(userShell)) {
|
|
173
|
+
cachedShellConfig = buildConfig(userShell);
|
|
174
|
+
return cachedShellConfig;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 4. Fallback: use basic shell
|
|
178
|
+
const basicShell = resolveBasicShell();
|
|
179
|
+
if (basicShell) {
|
|
180
|
+
cachedShellConfig = buildConfig(basicShell);
|
|
181
|
+
return cachedShellConfig;
|
|
182
|
+
}
|
|
183
|
+
cachedShellConfig = buildConfig("sh");
|
|
184
|
+
return cachedShellConfig;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if a process is running.
|
|
189
|
+
*/
|
|
190
|
+
export function isPidRunning(pid: number | Subprocess): boolean {
|
|
191
|
+
if (typeof pid !== "number") {
|
|
192
|
+
if (pid.killed) return false;
|
|
193
|
+
if (pid.exitCode !== null) return false;
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Process.fromPid(pid)?.status() === ProcessStatus.Running;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function onProcessExit(proc: Subprocess | number, abortSignal?: AbortSignal): Promise<boolean> {
|
|
201
|
+
if (typeof proc !== "number") {
|
|
202
|
+
return proc.exited.then(
|
|
203
|
+
() => true,
|
|
204
|
+
() => true,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (await Process.fromPid(proc)?.waitForExit({ signal: abortSignal })) ?? true;
|
|
209
|
+
}
|