procband 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -29,9 +29,6 @@ const result = await proc
29
29
  console.log(result)
30
30
  ```
31
31
 
32
- `stdin` is disabled by default. Set `stdin: true` to write to `proc.stdin`, or
33
- pass a readable stream to `stdin` to pipe input automatically.
34
-
35
32
  ## Documentation Map
36
33
 
37
34
  - Concepts and lifecycle: [docs/context.md](docs/context.md)
package/dist/index.d.mts CHANGED
@@ -7,7 +7,7 @@ import { Readable, Writable } from "node:stream";
7
7
  */
8
8
  type Signals = NodeJS.Signals;
9
9
  /**
10
- * Signal values accepted by `kill()` and `stop()`.
10
+ * Signal values accepted by `kill()`.
11
11
  */
12
12
  type KillSignal = number | Signals;
13
13
  /**
@@ -47,6 +47,12 @@ interface ProcessConfig {
47
47
  * Environment variables for the child process.
48
48
  */
49
49
  env?: Record<string, string | undefined>;
50
+ /**
51
+ * Whether to spawn the child in a detached process group/session.
52
+ *
53
+ * This is passed through to Node.js `spawn()` unchanged.
54
+ */
55
+ detached?: boolean;
50
56
  /**
51
57
  * Human-facing label used in prefixed output.
52
58
  *
@@ -75,6 +81,15 @@ interface ProcessConfig {
75
81
  * pipe bytes into the child automatically.
76
82
  */
77
83
  stdin?: boolean | Readable;
84
+ /**
85
+ * Signal to send when the parent process is shutting down.
86
+ *
87
+ * When provided, `procband` uses this signal for parent-driven cleanup on
88
+ * `SIGINT`, `SIGTERM`, and `exit` instead of mirroring the parent signal or
89
+ * relying on the platform default. This only affects parent-driven cleanup;
90
+ * `proc.kill()` still uses its own explicit signal argument.
91
+ */
92
+ parentExitSignal?: KillSignal;
78
93
  /**
79
94
  * Automatic restart behavior for terminal child exits.
80
95
  *
@@ -171,23 +186,6 @@ type MatchCallback = (event: MatchEvent) => void;
171
186
  * Stops a callback subscription created by `match()`.
172
187
  */
173
188
  type Unsubscribe = () => void;
174
- /**
175
- * Options for `stop()`.
176
- */
177
- interface StopOptions {
178
- /**
179
- * Signal used for the initial process-tree shutdown attempt.
180
- *
181
- * Defaults to `"SIGTERM"`.
182
- */
183
- signal?: KillSignal;
184
- /**
185
- * Delay before escalating to `SIGKILL`.
186
- *
187
- * Defaults to `5000`.
188
- */
189
- killAfterMs?: number;
190
- }
191
189
  /**
192
190
  * Final state for a supervised process after all restart attempts are done.
193
191
  */
@@ -264,11 +262,11 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
264
262
  /**
265
263
  * Disable future restarts and terminate the active process tree.
266
264
  *
267
- * `stop()` targets the current child process and any descendants it spawned.
268
- * It first uses the configured `signal`, then escalates to `SIGKILL` after
269
- * `killAfterMs` if the tree is still alive.
265
+ * For supervised processes, `kill()` targets the current child process and
266
+ * any descendants it spawned instead of only the direct child.
267
+ * `kill(0)` preserves the normal `ChildProcess` existence-check behavior.
270
268
  */
271
- stop(options?: StopOptions): Promise<void>;
269
+ kill(signal?: KillSignal): boolean;
272
270
  }
273
271
  //#endregion
274
272
  //#region src/process.d.ts
@@ -301,4 +299,4 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
301
299
  */
302
300
  declare function supervise(config: ProcessConfig): ProcbandProcess;
303
301
  //#endregion
304
- export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, type ProcessResult, type RestartPolicy, type RgbColor, type StopOptions, type Unsubscribe, type WaitForOptions, supervise };
302
+ export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, type ProcessResult, type RestartPolicy, type RgbColor, type Unsubscribe, type WaitForOptions, supervise };
package/dist/index.mjs CHANGED
@@ -387,7 +387,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
387
387
  stderrSink;
388
388
  stderrSinkActive = true;
389
389
  stderrSinkCleanup = null;
390
- stopPromise = null;
390
+ shutdownPromise = null;
391
391
  shutdownRequested = false;
392
392
  restartDisabled = false;
393
393
  finalized = false;
@@ -466,9 +466,12 @@ var ProcbandProcessImpl = class extends EventEmitter {
466
466
  return this.finalPromise.then(onfulfilled, onrejected);
467
467
  }
468
468
  kill(signal) {
469
- const child = this.currentChild;
470
- if (!child) return false;
471
- return child.kill(signal);
469
+ if (signal === 0) return this.currentChild?.kill(0) ?? false;
470
+ if (!this.attempt || this.finalized) return false;
471
+ this.beginShutdown(signal ?? "SIGTERM", 5e3).catch((error) => {
472
+ this.emit("error", error);
473
+ });
474
+ return true;
472
475
  }
473
476
  ref() {
474
477
  this.currentChild?.ref();
@@ -481,26 +484,24 @@ var ProcbandProcessImpl = class extends EventEmitter {
481
484
  disconnect() {
482
485
  this.currentChild?.disconnect();
483
486
  }
484
- async stop(options) {
485
- if (this.stopPromise) return this.stopPromise;
487
+ prepareShutdown() {
486
488
  this.shutdownRequested = true;
487
489
  this.restartDisabled = true;
488
490
  this.restart.cancelDelay();
489
- this.stopPromise = this.stopActiveTree(options?.signal ?? "SIGTERM", options?.killAfterMs ?? 5e3);
490
- return this.stopPromise;
491
+ }
492
+ beginShutdown(signal, killAfterMs) {
493
+ this.prepareShutdown();
494
+ if (this.shutdownPromise) return this.shutdownPromise;
495
+ this.shutdownPromise = this.stopActiveTree(signal, killAfterMs);
496
+ return this.shutdownPromise;
491
497
  }
492
498
  cleanupFromExit() {
493
- this.shutdownRequested = true;
494
- this.restartDisabled = true;
495
- this.restart.cancelDelay();
499
+ this.prepareShutdown();
496
500
  const child = this.currentChild;
497
- if (child) killTreeBestEffort(child);
501
+ if (child) killTreeBestEffort(child, this.getParentCleanupSignal());
498
502
  }
499
503
  async cleanupFromSignal(signal) {
500
- this.shutdownRequested = true;
501
- this.restartDisabled = true;
502
- this.restart.cancelDelay();
503
- await this.stopActiveTree(signal, 1e3);
504
+ await this.beginShutdown(this.getParentCleanupSignal(signal), 1e3);
504
505
  }
505
506
  async stopActiveTree(signal, killAfterMs) {
506
507
  const attempt = this.attempt;
@@ -510,10 +511,14 @@ var ProcbandProcessImpl = class extends EventEmitter {
510
511
  }
511
512
  await stopChildTree(attempt.child, attempt.close, () => attempt.closed, signal, killAfterMs);
512
513
  }
514
+ getParentCleanupSignal(fallback) {
515
+ return this.config.parentExitSignal ?? fallback;
516
+ }
513
517
  spawnAttempt() {
514
518
  this.generation += 1;
515
519
  const child = spawn(this.config.command, this.config.args ?? [], {
516
520
  cwd: this.config.cwd,
521
+ detached: this.config.detached,
517
522
  env: this.config.env,
518
523
  stdio: [
519
524
  this.config.stdin ? "pipe" : "ignore",
@@ -717,7 +722,7 @@ function propagateFailure(exitCode) {
717
722
  if (propagatedFailure) return;
718
723
  propagatedFailure = true;
719
724
  if (process.exitCode == null || process.exitCode === 0) process.exitCode = exitCode;
720
- for (const proc of liveProcesses) proc.stop().catch(() => {});
725
+ for (const proc of liveProcesses) proc.kill();
721
726
  }
722
727
  //#endregion
723
728
  export { supervise };
package/docs/context.md CHANGED
@@ -7,7 +7,7 @@ The returned `ProcbandProcess` is both:
7
7
  - a `ChildProcess`-compatible handle for the current active child attempt
8
8
  - a thenable wrapper that resolves to a `ProcessResult` when supervision is done
9
9
 
10
- Supervision adds four behaviors on top of raw `spawn()`:
10
+ Supervision adds five behaviors on top of raw `spawn()`:
11
11
 
12
12
  - prefixed `stdout` and `stderr`
13
13
  - line-based matching for future output
@@ -36,7 +36,8 @@ Supervision adds four behaviors on top of raw `spawn()`:
36
36
 
37
37
  - `ProcessConfig`
38
38
  Declares one supervised subprocess plus its label, color, restart policy,
39
- optional stdin behavior, and optional raw `stderr` tee.
39
+ optional stdin behavior, optional detached spawn mode, optional
40
+ parent-exit signal override, and optional raw `stderr` tee.
40
41
  - `ProcbandProcess`
41
42
  The live wrapper returned by `supervise()`. It is a `ChildProcess`-compatible
42
43
  handle, a matching surface, a shutdown surface, and a thenable final result.
@@ -72,7 +73,7 @@ Supervision adds four behaviors on top of raw `spawn()`:
72
73
  - React to repeated matching output:
73
74
  `proc.match(pattern, callback, options)`
74
75
  - Stop the process and its descendants:
75
- `proc.stop()`
76
+ `proc.kill()`
76
77
  - Inspect final exit state:
77
78
  `await proc` or `await proc.wait()`
78
79
  - Take ownership of a process failure:
@@ -89,6 +90,25 @@ Supervision adds four behaviors on top of raw `spawn()`:
89
90
  `restart: true`
90
91
  - Use explicit retry rules:
91
92
  `restart: { when, delayMs, maxFailures, windowMs }`
93
+ - Force a specific signal during parent shutdown:
94
+ `parentExitSignal: 'SIGHUP'`
95
+
96
+ # Recommended Patterns
97
+
98
+ - Use `proc.kill()` for deliberate shutdown initiated by your own script.
99
+ - Reserve `parentExitSignal` for children that expect a specific signal from
100
+ their supervisor during parent-driven cleanup.
101
+ - Await `proc` or call `proc.wait()` when your script intends to own failure
102
+ handling instead of inheriting procband's default parent-exit propagation.
103
+
104
+ # Patterns to Avoid
105
+
106
+ - Do not treat `parentExitSignal` as a replacement for the signal passed to
107
+ `proc.kill(signal)`. The former only changes parent-driven cleanup.
108
+ - Do not expect `kill(0)` to stop supervision. `kill(0)` remains an existence
109
+ check for the current child attempt.
110
+ - Do not expect historical log replay from `match()` or `waitFor()`. Matching
111
+ is future-only by design.
92
112
 
93
113
  # Invariants and Constraints
94
114
 
@@ -110,10 +130,12 @@ Supervision adds four behaviors on top of raw `spawn()`:
110
130
  - The wrapper survives restarts, but inherited `pid`, `stdin`, `stdout`,
111
131
  `stderr`, and related `ChildProcess` fields always refer to the current active
112
132
  child attempt. `stdin` is `null` unless `ProcessConfig.stdin` is enabled.
113
- - `kill()` only signals the current direct child. `stop()` disables restart and
114
- kills the full process tree.
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.
115
135
  - Parent cleanup installs both `SIGINT` and `SIGTERM` handlers while any live
116
- supervised process exists.
136
+ 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.
117
139
  - `stderr` prefixes always use the reserved red, even when a custom process
118
140
  color is configured.
119
141
  - `ProcessConfig.name` is optional. When omitted, it falls back to the
@@ -130,7 +152,7 @@ Supervision adds four behaviors on top of raw `spawn()`:
130
152
  supervision.
131
153
  - Unobserved terminal failures do not reject promises. They set the parent
132
154
  `process.exitCode` and start stopping sibling `procband` processes.
133
- - `stop()` may reject if tree-kill fails with a non-`ESRCH` error.
155
+ - `kill()` emits `error` if tree-kill fails with a non-`ESRCH` error.
134
156
 
135
157
  # Terminology
136
158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "procband",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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",