@oh-my-pi/pi-utils 9.6.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-utils",
3
- "version": "9.6.3",
3
+ "version": "9.7.0",
4
4
  "description": "Shared utilities for pi packages",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
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 group (Windows only) */
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 (group) {
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 (IS_WINDOWS) {
299
- const taskkill = Bun.spawn({
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
- process.kill(-pid, "SIGKILL");
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, group: child.isProcessGroup });
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 { detached = false, timeout = -1, signal, ...rest } = options ?? {};
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, detached);
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;