procband 0.2.3 → 0.3.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/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ChildProcess } from "node:child_process";
2
- import { Writable } from "node:stream";
2
+ import { Readable, Writable } from "node:stream";
3
3
 
4
4
  //#region src/types.d.ts
5
5
  /**
@@ -67,6 +67,23 @@ interface ProcessConfig {
67
67
  * `process.stderr`.
68
68
  */
69
69
  stderr?: Writable;
70
+ /**
71
+ * Child stdin behavior.
72
+ *
73
+ * Defaults to `false`, which connects the child stdin to the null device.
74
+ * Use `true` to expose a writable `proc.stdin`, or pass a readable stream to
75
+ * pipe bytes into the child automatically.
76
+ */
77
+ stdin?: boolean | Readable;
78
+ /**
79
+ * Signal to send when the parent process is shutting down.
80
+ *
81
+ * When provided, `procband` uses this signal for parent-driven cleanup on
82
+ * `SIGINT`, `SIGTERM`, and `exit` instead of mirroring the parent signal or
83
+ * relying on the platform default. This only affects parent-driven cleanup;
84
+ * `proc.stop()` and `proc.kill()` still use their own explicit signals.
85
+ */
86
+ parentExitSignal?: KillSignal;
70
87
  /**
71
88
  * Automatic restart behavior for terminal child exits.
72
89
  *
package/dist/index.mjs CHANGED
@@ -494,13 +494,13 @@ var ProcbandProcessImpl = class extends EventEmitter {
494
494
  this.restartDisabled = true;
495
495
  this.restart.cancelDelay();
496
496
  const child = this.currentChild;
497
- if (child) killTreeBestEffort(child);
497
+ if (child) killTreeBestEffort(child, this.getParentCleanupSignal());
498
498
  }
499
499
  async cleanupFromSignal(signal) {
500
500
  this.shutdownRequested = true;
501
501
  this.restartDisabled = true;
502
502
  this.restart.cancelDelay();
503
- await this.stopActiveTree(signal, 1e3);
503
+ await this.stopActiveTree(this.getParentCleanupSignal(signal), 1e3);
504
504
  }
505
505
  async stopActiveTree(signal, killAfterMs) {
506
506
  const attempt = this.attempt;
@@ -510,13 +510,16 @@ var ProcbandProcessImpl = class extends EventEmitter {
510
510
  }
511
511
  await stopChildTree(attempt.child, attempt.close, () => attempt.closed, signal, killAfterMs);
512
512
  }
513
+ getParentCleanupSignal(fallback) {
514
+ return this.config.parentExitSignal ?? fallback;
515
+ }
513
516
  spawnAttempt() {
514
517
  this.generation += 1;
515
518
  const child = spawn(this.config.command, this.config.args ?? [], {
516
519
  cwd: this.config.cwd,
517
520
  env: this.config.env,
518
521
  stdio: [
519
- "pipe",
522
+ this.config.stdin ? "pipe" : "ignore",
520
523
  "pipe",
521
524
  "pipe"
522
525
  ]
@@ -537,13 +540,15 @@ var ProcbandProcessImpl = class extends EventEmitter {
537
540
  text: "",
538
541
  flushed: false
539
542
  },
540
- closed: false
543
+ closed: false,
544
+ stdinCleanup: null
541
545
  };
542
546
  this.attempt = attempt;
543
547
  this.attachAttempt(attempt);
544
548
  }
545
549
  attachAttempt(attempt) {
546
550
  const { child } = attempt;
551
+ attempt.stdinCleanup = this.bindAttemptStdin(attempt);
547
552
  child.on("spawn", () => {
548
553
  if (this.attempt === attempt) this.emit("spawn");
549
554
  });
@@ -556,6 +561,8 @@ var ProcbandProcessImpl = class extends EventEmitter {
556
561
  child.on("close", (code, signal) => {
557
562
  if (this.attempt !== attempt) return;
558
563
  attempt.closed = true;
564
+ attempt.stdinCleanup?.();
565
+ attempt.stdinCleanup = null;
559
566
  this.flushLineBuffer(attempt, "stdout");
560
567
  this.flushLineBuffer(attempt, "stderr");
561
568
  attempt.settleClose({
@@ -682,6 +689,15 @@ var ProcbandProcessImpl = class extends EventEmitter {
682
689
  buffer.text = "";
683
690
  this.handleLine(stream, line, false);
684
691
  }
692
+ bindAttemptStdin(attempt) {
693
+ const source = this.config.stdin;
694
+ const destination = attempt.child.stdin;
695
+ if (!destination || typeof source !== "object") return null;
696
+ source.pipe(destination);
697
+ return () => {
698
+ source.unpipe(destination);
699
+ };
700
+ }
685
701
  };
686
702
  const liveProcesses = /* @__PURE__ */ new Set();
687
703
  let propagatedFailure = false;
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
@@ -35,8 +35,9 @@ Supervision adds four behaviors on top of raw `spawn()`:
35
35
  # Core Abstractions
36
36
 
37
37
  - `ProcessConfig`
38
- Declares one supervised subprocess plus its label, color, restart policy, and
39
- optional raw `stderr` tee.
38
+ Declares one supervised subprocess plus its label, color, restart policy,
39
+ optional stdin behavior, optional parent-exit signal override, and optional
40
+ 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.
@@ -52,14 +53,17 @@ Supervision adds four behaviors on top of raw `spawn()`:
52
53
  3. Each line is prefixed and written to the parent `process.stdout` or
53
54
  `process.stderr`.
54
55
  4. `stderr` can also be tee'd as raw bytes to `ProcessConfig.stderr`.
55
- 5. Future matching lines are delivered through `match()` callbacks or
56
+ 5. `stdin` is disconnected by default. Set `ProcessConfig.stdin` to `true` for
57
+ a writable child stdin, or pass a readable stream to pipe input into the
58
+ child automatically.
59
+ 6. Future matching lines are delivered through `match()` callbacks or
56
60
  `waitFor()`.
57
- 6. When a child exits, `procband` either finalizes or starts a new attempt,
61
+ 7. When a child exits, `procband` either finalizes or starts a new attempt,
58
62
  depending on the restart policy.
59
- 7. A terminal failed exit that nobody observed through `await proc` or
63
+ 8. A terminal failed exit that nobody observed through `await proc` or
60
64
  `proc.wait()` sets `process.exitCode` and begins
61
65
  stopping any other live `procband` processes in the same parent script.
62
- 8. `await proc` or `await proc.wait()` resolves only after the process is
66
+ 9. `await proc` or `await proc.wait()` resolves only after the process is
63
67
  terminal and no further restart will happen.
64
68
 
65
69
  # Common Tasks -> Recommended APIs
@@ -78,10 +82,34 @@ Supervision adds four behaviors on top of raw `spawn()`:
78
82
  Do not call `wait()` or await the thenable result
79
83
  - Capture raw child `stderr` in a file or custom stream:
80
84
  `ProcessConfig.stderr`
85
+ - Write to child stdin manually:
86
+ `stdin: true`, then `proc.stdin?.write(...)`
87
+ - Pipe a custom input stream into the child:
88
+ `stdin: readable`
81
89
  - Retry failed exits with sane defaults:
82
90
  `restart: true`
83
91
  - Use explicit retry rules:
84
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.stop()` 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 `StopOptions.signal`.
107
+ The former only changes parent-driven cleanup; the latter controls
108
+ explicit `proc.stop()` calls.
109
+ - Do not rely on `kill()` for full shutdown when descendants may still be
110
+ running. `kill()` only targets the current direct child.
111
+ - Do not expect historical log replay from `match()` or `waitFor()`. Matching
112
+ is future-only by design.
85
113
 
86
114
  # Invariants and Constraints
87
115
 
@@ -102,11 +130,13 @@ Supervision adds four behaviors on top of raw `spawn()`:
102
130
  processes in the same parent script.
103
131
  - The wrapper survives restarts, but inherited `pid`, `stdin`, `stdout`,
104
132
  `stderr`, and related `ChildProcess` fields always refer to the current active
105
- child attempt.
133
+ child attempt. `stdin` is `null` unless `ProcessConfig.stdin` is enabled.
106
134
  - `kill()` only signals the current direct child. `stop()` disables restart and
107
135
  kills the full process tree.
108
136
  - Parent cleanup installs both `SIGINT` and `SIGTERM` handlers while any live
109
- supervised process exists.
137
+ supervised process exists. Set `ProcessConfig.parentExitSignal` to override
138
+ which signal is sent to the child tree during parent-driven cleanup. This
139
+ does not change the signal used by explicit `proc.stop()` calls.
110
140
  - `stderr` prefixes always use the reserved red, even when a custom process
111
141
  color is configured.
112
142
  - `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.2.3",
3
+ "version": "0.3.1",
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",