@oh-my-pi/pi-utils 9.6.4 → 9.7.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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/procmgr.ts +31 -21
- package/src/ptree.ts +5 -23
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./glob";
|
|
|
5
5
|
export * as logger from "./logger";
|
|
6
6
|
export * as postmortem from "./postmortem";
|
|
7
7
|
export * as procmgr from "./procmgr";
|
|
8
|
+
export { setNativeKillTree } from "./procmgr";
|
|
8
9
|
export * as ptree from "./ptree";
|
|
9
10
|
export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
|
|
10
11
|
export * from "./stream";
|
package/src/procmgr.ts
CHANGED
|
@@ -184,13 +184,32 @@ export function getShellConfig(customShellPath?: string): ShellConfig {
|
|
|
184
184
|
return cachedShellConfig;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Function signature for native process tree killing.
|
|
189
|
+
* Returns the number of processes killed.
|
|
190
|
+
*/
|
|
191
|
+
export type KillTreeFn = (pid: number, signal: number) => number;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Global native kill tree function, injected by pi-natives when loaded.
|
|
195
|
+
* Falls back to platform-specific behavior if not set.
|
|
196
|
+
*/
|
|
197
|
+
export let nativeKillTree: KillTreeFn | undefined;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Set the native kill tree function. Called by pi-natives on load.
|
|
201
|
+
*/
|
|
202
|
+
export function setNativeKillTree(fn: KillTreeFn): void {
|
|
203
|
+
nativeKillTree = fn;
|
|
204
|
+
}
|
|
205
|
+
|
|
187
206
|
/**
|
|
188
207
|
* Options for terminating a process and all its descendants.
|
|
189
208
|
*/
|
|
190
209
|
export interface TerminateOptions {
|
|
191
210
|
/** The process to terminate */
|
|
192
211
|
target: Subprocess | number;
|
|
193
|
-
/** Whether to terminate the process
|
|
212
|
+
/** Whether to terminate the process tree (all descendants) */
|
|
194
213
|
group?: boolean;
|
|
195
214
|
/** Timeout in milliseconds */
|
|
196
215
|
timeout?: number;
|
|
@@ -293,31 +312,22 @@ export async function terminate(options: TerminateOptions): Promise<boolean> {
|
|
|
293
312
|
}
|
|
294
313
|
} catch {}
|
|
295
314
|
|
|
296
|
-
if (
|
|
315
|
+
if (nativeKillTree) {
|
|
316
|
+
nativeKillTree(pid, 9);
|
|
317
|
+
} else {
|
|
318
|
+
if (group && !IS_WINDOWS) {
|
|
319
|
+
try {
|
|
320
|
+
process.kill(-pid, "SIGKILL");
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
297
323
|
try {
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
cmd: ["taskkill", "/F", "/T", "/PID", pid.toString()],
|
|
301
|
-
stdin: "ignore",
|
|
302
|
-
stdout: "ignore",
|
|
303
|
-
stderr: "ignore",
|
|
304
|
-
timeout: 5000,
|
|
305
|
-
windowsHide: true,
|
|
306
|
-
});
|
|
307
|
-
void taskkill.exited.catch(() => {});
|
|
308
|
-
taskkill.unref();
|
|
324
|
+
if (typeof target === "number") {
|
|
325
|
+
process.kill(target, "SIGKILL");
|
|
309
326
|
} else {
|
|
310
|
-
|
|
327
|
+
target.kill("SIGKILL");
|
|
311
328
|
}
|
|
312
329
|
} catch {}
|
|
313
330
|
}
|
|
314
|
-
try {
|
|
315
|
-
if (typeof target === "number") {
|
|
316
|
-
process.kill(target, "SIGKILL");
|
|
317
|
-
} else {
|
|
318
|
-
target.kill("SIGKILL");
|
|
319
|
-
}
|
|
320
|
-
} catch {}
|
|
321
331
|
|
|
322
332
|
return await Promise.race([Bun.sleep(timeout).then(() => false), exitPromise]);
|
|
323
333
|
} finally {
|
package/src/ptree.ts
CHANGED
|
@@ -10,11 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Spawn, Subprocess } from "bun";
|
|
13
|
-
import { postmortem } from ".";
|
|
14
13
|
import { terminate } from "./procmgr";
|
|
15
14
|
|
|
16
|
-
const managedChildren = new Set<ChildProcess>();
|
|
17
|
-
|
|
18
15
|
/** A Bun subprocess with stdout/stderr always piped (stdin may vary). */
|
|
19
16
|
type PipedSubprocess<In extends InMask = InMask> = Subprocess<In, "pipe", "pipe">;
|
|
20
17
|
|
|
@@ -98,15 +95,9 @@ async function pump(
|
|
|
98
95
|
* - Unix: negative PID signals the process group
|
|
99
96
|
*/
|
|
100
97
|
async function killChild(child: ChildProcess) {
|
|
101
|
-
await terminate({ target: child.proc
|
|
98
|
+
await terminate({ target: child.proc });
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
postmortem.register("managed-children", async () => {
|
|
105
|
-
const children = Array.from(managedChildren);
|
|
106
|
-
managedChildren.clear();
|
|
107
|
-
await Promise.all(children.map(killChild));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
101
|
/**
|
|
111
102
|
* Options for waiting for process exit and capturing output.
|
|
112
103
|
*/
|
|
@@ -205,10 +196,7 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
205
196
|
#stderrDone: Promise<void>;
|
|
206
197
|
#exited: Promise<number>;
|
|
207
198
|
|
|
208
|
-
constructor(
|
|
209
|
-
public readonly proc: PipedSubprocess<In>,
|
|
210
|
-
public readonly isProcessGroup: boolean,
|
|
211
|
-
) {
|
|
199
|
+
constructor(public readonly proc: PipedSubprocess<In>) {
|
|
212
200
|
const { promise: stderrDone, resolve: resolveStderrDone } = Promise.withResolvers<void>();
|
|
213
201
|
this.#stderrDone = stderrDone;
|
|
214
202
|
|
|
@@ -245,8 +233,6 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
245
233
|
const { promise, resolve, reject } = Promise.withResolvers<number>();
|
|
246
234
|
this.#exited = promise;
|
|
247
235
|
|
|
248
|
-
if (this.proc.exitCode === null) managedChildren.add(this);
|
|
249
|
-
|
|
250
236
|
// Normalize Bun's exited promise into our "exitReason / exitedCleanly" model.
|
|
251
237
|
proc.exited
|
|
252
238
|
.catch(() => null)
|
|
@@ -279,9 +265,6 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
279
265
|
|
|
280
266
|
this.#exitReason = ex;
|
|
281
267
|
reject(ex);
|
|
282
|
-
})
|
|
283
|
-
.finally(() => {
|
|
284
|
-
managedChildren.delete(this);
|
|
285
268
|
});
|
|
286
269
|
}
|
|
287
270
|
|
|
@@ -426,7 +409,7 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
426
409
|
*/
|
|
427
410
|
type ChildSpawnOptions<In extends InMask = InMask> = Omit<
|
|
428
411
|
Spawn.SpawnOptions<In, "pipe", "pipe">,
|
|
429
|
-
"stdout" | "stderr"
|
|
412
|
+
"stdout" | "stderr" | "detached"
|
|
430
413
|
> & {
|
|
431
414
|
/** AbortSignal to cancel the process */
|
|
432
415
|
signal?: AbortSignal;
|
|
@@ -441,16 +424,15 @@ type ChildSpawnOptions<In extends InMask = InMask> = Omit<
|
|
|
441
424
|
* @returns A ChildProcess instance.
|
|
442
425
|
*/
|
|
443
426
|
export function spawn<In extends InMask = InMask>(cmd: string[], options?: ChildSpawnOptions<In>): ChildProcess<In> {
|
|
444
|
-
const {
|
|
427
|
+
const { timeout = -1, signal, ...rest } = options ?? {};
|
|
445
428
|
const child = Bun.spawn(cmd, {
|
|
446
429
|
stdin: "ignore",
|
|
447
430
|
stdout: "pipe",
|
|
448
431
|
stderr: "pipe",
|
|
449
|
-
detached,
|
|
450
432
|
windowsHide: true,
|
|
451
433
|
...rest,
|
|
452
434
|
});
|
|
453
|
-
const cproc = new ChildProcess(child
|
|
435
|
+
const cproc = new ChildProcess(child);
|
|
454
436
|
if (signal) cproc.attachSignal(signal);
|
|
455
437
|
if (timeout > 0) cproc.attachTimeout(timeout);
|
|
456
438
|
return cproc;
|