procband 0.3.3 → 0.3.4

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/index.d.mts CHANGED
@@ -50,7 +50,10 @@ interface ProcessConfig {
50
50
  /**
51
51
  * Whether to spawn the child in a detached process group/session.
52
52
  *
53
- * This is passed through to Node.js `spawn()` unchanged.
53
+ * This is passed through to Node.js `spawn()` unchanged. On Unix-like
54
+ * platforms, shutdown also signals the detached child's process group so
55
+ * same-group descendants are cleaned up even if they are no longer reachable
56
+ * through the child process tree.
54
57
  */
55
58
  detached?: boolean;
56
59
  /**
@@ -263,7 +266,8 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
263
266
  * Disable future restarts and terminate the active process tree.
264
267
  *
265
268
  * For supervised processes, `kill()` targets the current child process and
266
- * any descendants it spawned instead of only the direct child.
269
+ * any descendants it spawned instead of only the direct child. Detached
270
+ * Unix-like children are also stopped by process group.
267
271
  * `kill(0)` preserves the normal `ChildProcess` existence-check behavior.
268
272
  */
269
273
  kill(signal?: KillSignal): boolean;
package/dist/index.mjs CHANGED
@@ -290,18 +290,23 @@ function unregisterCleanupTarget(target) {
290
290
  liveTargets.delete(target);
291
291
  if (liveTargets.size === 0) uninstallParentCleanup();
292
292
  }
293
- function killTreeBestEffort(child, signal) {
293
+ function killTreeBestEffort(child, signal, options = {}) {
294
+ if (options.detached) try {
295
+ killProcessGroup(child, signal);
296
+ } catch {}
294
297
  try {
295
298
  treeKillSync(child, signal);
296
299
  } catch {}
297
300
  }
298
- async function stopChildTree(child, close, isClosed, signal, killAfterMs) {
301
+ async function stopChildTree(child, close, isClosed, signal, killAfterMs, options = {}) {
302
+ if (options.detached) killProcessGroup(child, signal);
299
303
  try {
300
304
  await treeKill(child, signal);
301
305
  } catch (error) {
302
306
  if (!isMissingProcessError(error)) throw error;
303
307
  }
304
308
  if (!await Promise.race([close.then(() => true), setTimeout$1(killAfterMs, false)]) && !isClosed()) {
309
+ if (options.detached) killProcessGroup(child, "SIGKILL");
305
310
  try {
306
311
  await treeKill(child, "SIGKILL");
307
312
  } catch (error) {
@@ -310,6 +315,14 @@ async function stopChildTree(child, close, isClosed, signal, killAfterMs) {
310
315
  await close;
311
316
  }
312
317
  }
318
+ function killProcessGroup(child, signal) {
319
+ if (process.platform === "win32" || !child.pid) return;
320
+ try {
321
+ process.kill(-child.pid, signal);
322
+ } catch (error) {
323
+ if (!isMissingProcessError(error)) throw error;
324
+ }
325
+ }
313
326
  function installParentCleanup() {
314
327
  if (parentCleanupInstalled) return;
315
328
  parentCleanupInstalled = true;
@@ -508,7 +521,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
508
521
  cleanupFromExit() {
509
522
  this.prepareShutdown();
510
523
  const child = this.currentChild;
511
- if (child) killTreeBestEffort(child, this.getParentCleanupSignal());
524
+ if (child) killTreeBestEffort(child, this.getParentCleanupSignal(), { detached: this.config.detached });
512
525
  }
513
526
  async cleanupFromSignal(signal) {
514
527
  await this.beginShutdown(this.getParentCleanupSignal(signal), 1e3);
@@ -519,7 +532,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
519
532
  await this.finalPromise;
520
533
  return;
521
534
  }
522
- await stopChildTree(attempt.child, attempt.close, () => attempt.closed, signal, killAfterMs);
535
+ await stopChildTree(attempt.child, attempt.close, () => attempt.closed, signal, killAfterMs, { detached: this.config.detached });
523
536
  }
524
537
  getParentCleanupSignal(fallback) {
525
538
  return this.config.parentExitSignal ?? fallback;
package/docs/context.md CHANGED
@@ -12,7 +12,7 @@ Supervision adds five behaviors on top of raw `spawn()`:
12
12
  - prefixed `stdout` and `stderr`
13
13
  - line-based matching for future output
14
14
  - optional restart policy with failure suppression
15
- - tree-aware shutdown for the child and its descendants
15
+ - shutdown for the child and descendants, including detached Unix process groups
16
16
  - parent-exit propagation for unobserved terminal failures
17
17
 
18
18
  # When to Use
@@ -96,6 +96,9 @@ Supervision adds five behaviors on top of raw `spawn()`:
96
96
  # Recommended Patterns
97
97
 
98
98
  - Use `proc.kill()` for deliberate shutdown initiated by your own script.
99
+ - Use `detached: true` when a child must run in its own process group/session.
100
+ On Unix-like platforms, `procband` uses that process group during shutdown in
101
+ addition to process-tree cleanup.
99
102
  - Reserve `parentExitSignal` for children that expect a specific signal from
100
103
  their supervisor during parent-driven cleanup.
101
104
  - Await `proc` or call `proc.wait()` when your script intends to own failure
@@ -130,12 +133,15 @@ Supervision adds five behaviors on top of raw `spawn()`:
130
133
  - The wrapper survives restarts, but inherited `pid`, `stdin`, `stdout`,
131
134
  `stderr`, and related `ChildProcess` fields always refer to the current active
132
135
  child attempt. `stdin` is `null` unless `ProcessConfig.stdin` is enabled.
133
- - `kill()` disables future restarts and kills the full process tree, except for
134
- `kill(0)`, which only checks whether the current child attempt exists.
136
+ - `kill()` disables future restarts and kills the full process tree. For
137
+ `detached: true` children on Unix-like platforms, shutdown also signals the
138
+ detached child's process group so same-group descendants are cleaned up even
139
+ when they are no longer reachable by parent PID. `kill(0)` only checks
140
+ whether the current child attempt exists.
135
141
  - Parent cleanup installs both `SIGINT` and `SIGTERM` handlers while any live
136
142
  supervised process exists. Set `ProcessConfig.parentExitSignal` to override
137
- which signal is sent to the child tree during parent-driven cleanup. This
138
- does not change the signal used by explicit `proc.kill()` calls.
143
+ which signal is sent during parent-driven cleanup. This does not change the
144
+ signal used by explicit `proc.kill()` calls.
139
145
  - `stderr` prefixes always use the reserved red, even when a custom process
140
146
  color is configured.
141
147
  - `ProcessConfig.name` is optional. When omitted, it falls back to the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "procband",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Supervise subprocesses from TypeScript: prefix logs, wait for output patterns, and manage lifecycle/restarts.",
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",