procband 0.3.5 → 0.3.7

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/docs/context.md CHANGED
@@ -1,184 +1,268 @@
1
- # Overview
2
-
3
- `procband` supervises one subprocess per `supervise()` call.
4
-
5
- The returned `ProcbandProcess` is both:
6
-
7
- - a `ChildProcess`-compatible handle for the current active child attempt
8
- - a thenable wrapper that resolves to a `ProcessResult` when supervision is done
9
-
10
- Supervision adds five behaviors on top of raw `spawn()`:
11
-
12
- - prefixed `stdout` and `stderr`
13
- - line-based matching for future output
14
- - optional restart policy with failure suppression
15
- - shutdown for the child and descendants, including detached Unix process groups
16
- - parent-exit propagation for unobserved terminal failures
17
-
18
- # When to Use
19
-
20
- - You are writing project-specific TypeScript scripts, not a CLI.
21
- - You need to wait for a subprocess to print a "ready" line.
22
- - You want readable prefixed logs from multiple long-lived child processes.
23
- - You need one shutdown API that kills descendant processes too.
24
- - You want automatic restart with a small built-in guard against tight failure
25
- loops.
26
-
27
- # When Not to Use
28
-
29
- - You need a standalone process manager or service supervisor.
30
- - You need buffered log history or replay for late subscribers.
31
- - You need shell pipelines, shell parsing, or a command-line tool.
32
- - You want one API that supervises many processes at once. `procband` keeps the
33
- unit of supervision to one process per call.
34
-
35
- # Core Abstractions
36
-
37
- - `ProcessConfig`
38
- Declares one supervised subprocess plus its label, color, restart policy,
39
- optional stdin behavior, optional detached spawn mode, optional
40
- parent-exit signal override, and optional raw `stderr` tee.
41
- - `ProcbandProcess`
42
- The live wrapper returned by `supervise()`. It is a `ChildProcess`-compatible
43
- handle, a matching surface, a shutdown surface, and a thenable final result.
44
- - `MatchEvent`
45
- A future matched line from `stdout` or `stderr`.
46
- - `RestartPolicy`
47
- Rules for restart timing and failed-exit suppression.
48
-
49
- # Data Flow / Lifecycle
50
-
51
- 1. `supervise(config)` spawns the first child process immediately.
52
- 2. Child `stdout` and `stderr` are read as text and split into lines.
53
- 3. Each line is prefixed and written to the parent `process.stdout` or
54
- `process.stderr`.
55
- 4. `stderr` can also be tee'd as raw bytes to `ProcessConfig.stderr`.
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
60
- `waitFor()`.
61
- 7. When a child exits, `procband` either finalizes or starts a new attempt,
62
- depending on the restart policy.
63
- 8. A terminal failed exit that nobody observed through `await proc` or
64
- `proc.wait()` sets `process.exitCode` and begins
65
- stopping any other live `procband` processes in the same parent script.
66
- 9. `await proc` or `await proc.wait()` resolves only after the process is
67
- terminal and no further restart will happen.
68
-
69
- # Common Tasks -> Recommended APIs
70
-
71
- - Wait for one readiness line:
72
- `proc.waitFor('ready')`
73
- - React to repeated matching output:
74
- `proc.match(pattern, callback, options)`
75
- - Stop the process and its descendants:
76
- `proc.kill()`
77
- - Inspect final exit state:
78
- `await proc` or `await proc.wait()`
79
- - Take ownership of a process failure:
80
- `await proc` or `await proc.wait()`
81
- - Let an unobserved terminal failure fail the parent script:
82
- Do not call `wait()` or await the thenable result
83
- - Capture raw child `stderr` in a file or custom stream:
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`
89
- - Retry failed exits with sane defaults:
90
- `restart: true`
91
- - Use explicit retry rules:
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
- - 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.
102
- - Reserve `parentExitSignal` for children that expect a specific signal from
103
- their supervisor during parent-driven cleanup.
104
- - Await `proc` or call `proc.wait()` when your script intends to own failure
105
- handling instead of inheriting procband's default parent-exit propagation.
106
-
107
- # Patterns to Avoid
108
-
109
- - Do not treat `parentExitSignal` as a replacement for the signal passed to
110
- `proc.kill(signal)`. The former only changes parent-driven cleanup.
111
- - Do not expect `kill(0)` to stop supervision. `kill(0)` remains an existence
112
- check for the current child attempt.
113
- - Do not expect historical log replay from `match()` or `waitFor()`. Matching
114
- is future-only by design.
115
-
116
- # Invariants and Constraints
117
-
118
- - Matching is line-based and future-only.
119
- - String patterns use substring matching.
120
- - RegExp patterns run against the full observed line.
121
- - `match()` subscriptions do not interfere with each other.
122
- - `waitFor()` rejects if the process becomes terminal before a future match is
123
- observed.
124
- - `await proc` resolves for both successful and failed exits. Inspect the
125
- returned `ProcessResult`.
126
- - `ProcessResult.exitCode` exposes the shell-style exit status for the final
127
- outcome, including signal exits.
128
- - Calling `proc.wait()` or awaiting the thenable process marks its terminal
129
- result as observed and suppresses default parent-exit propagation.
130
- - An unobserved terminal failure sets `process.exitCode` to the first failing
131
- process's `ProcessResult.exitCode` and starts stopping other live `procband`
132
- processes in the same parent script.
133
- - The wrapper survives restarts, but inherited `pid`, `stdin`, `stdout`,
134
- `stderr`, and related `ChildProcess` fields always refer to the current active
135
- child attempt. `stdin` is `null` unless `ProcessConfig.stdin` is enabled.
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.
141
- - Parent cleanup installs both `SIGINT` and `SIGTERM` handlers while any live
142
- supervised process exists. Set `ProcessConfig.parentExitSignal` to override
143
- which signal is sent during parent-driven cleanup. This does not change the
144
- signal used by explicit `proc.kill()` calls.
145
- - `stderr` prefixes always use the reserved red, even when a custom process
146
- color is configured.
147
- - `ProcessConfig.name` is optional. When omitted, it falls back to the
148
- trailing `/[-\w]+$/` match from `command`.
149
-
150
- # Error Model
151
-
152
- - `supervise()` throws synchronously for invalid config such as missing
153
- `command`, a `command` that does not produce a fallback `name`, or an
154
- invalid reserved color.
155
- - `waitFor()` rejects on timeout or terminal exit before a future match.
156
- - A thrown `match()` callback only unsubscribes that callback.
157
- - Errors from `ProcessConfig.stderr` stop teeing to that sink but do not stop
158
- supervision.
159
- - Unobserved terminal failures do not reject promises. They set the parent
160
- `process.exitCode` and start stopping sibling `procband` processes.
161
- - `kill()` emits `error` if tree-kill fails with a non-`ESRCH` error.
162
-
163
- # Terminology
164
-
165
- - Supervised process:
166
- A `ProcbandProcess` wrapper plus its current child attempt.
167
- - Child attempt:
168
- One concrete spawned process instance inside a supervision run.
169
- - Terminal:
170
- No child is running and no restart will be started.
171
- - Restart suppression:
172
- Automatic disabling of further restarts after too many failed exits inside the
173
- configured window.
174
- - Match:
175
- A future observed output line that satisfies a string or regex pattern.
176
-
177
- # Non-Goals
1
+ # Concepts and Lifecycle
2
+
3
+ > `procband` supervises one child process at a time; this page defines the
4
+ > wrapper lifecycle, restart boundary, shutdown behavior, and failure model that
5
+ > guide pages rely on.
6
+
7
+ ## Core Model
8
+
9
+ Each `supervise(config)` call creates one supervised process. The returned
10
+ `ProcbandProcess` is both:
11
+
12
+ | Surface | What it represents |
13
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
14
+ | Child-process handle | The current active child attempt. Inherited fields such as `pid`, `stdin`, `stdout`, and `stderr` update across restarts. |
15
+ | Matching surface | Future output lines observed by `match()` and `waitFor()`. |
16
+ | Shutdown surface | `kill()` disables future restarts and stops the current child process tree. |
17
+ | Thenable result | `await proc` is equivalent to `await proc.wait()` and resolves to the terminal `ProcessResult`. |
18
+
19
+ ```ts
20
+ import process from 'node:process'
21
+ import { supervise } from 'procband'
22
+
23
+ const proc = supervise({
24
+ name: 'api',
25
+ command: process.execPath,
26
+ args: ['-e', 'console.log("ready")'],
27
+ })
28
+
29
+ await proc.waitFor('ready')
30
+ const result = await proc
31
+ ```
32
+
33
+ After the readiness line is observed, `result` is available only when the
34
+ process is terminal and no restart delay or attempt remains.
35
+
36
+ ## Lifecycle
37
+
38
+ 1. `supervise(config)` validates the config and spawns the first child
39
+ immediately.
40
+ 2. Child `stdout` and `stderr` are decoded as UTF-8 text and split into lines.
41
+ 3. Each line is written to the parent `process.stdout` or `process.stderr` with
42
+ a process prefix unless `ProcessConfig.prefix` is `false`. `stderr` prefixes
43
+ always use the reserved red color.
44
+ 4. If `ProcessConfig.stderr` is provided, raw child `stderr` bytes are also
45
+ written to that sink.
46
+ 5. Matching subscribers receive future lines through `match()` callbacks or
47
+ `waitFor()` promises.
48
+ 6. When the child exits, `procband` either finalizes or schedules another child
49
+ attempt according to the restart policy.
50
+ 7. `await proc`, `await proc.wait()`, and `await proc.expectSuccess()` settle
51
+ only after the supervised process is terminal.
52
+
53
+ Use this shape when one process must become ready before another starts:
54
+
55
+ ```ts
56
+ import process from 'node:process'
57
+ import { supervise } from 'procband'
58
+
59
+ const api = supervise({
60
+ name: 'api',
61
+ command: process.execPath,
62
+ args: ['-e', 'setTimeout(() => console.log("ready"), 20)'],
63
+ })
64
+
65
+ await api.waitFor('ready')
66
+
67
+ supervise({
68
+ name: 'worker',
69
+ command: process.execPath,
70
+ args: ['-e', 'console.log("watching")'],
71
+ })
72
+ ```
73
+
74
+ The worker starts after the API prints a future line containing `ready`.
75
+
76
+ ## Output Prefixes
77
+
78
+ By default, each child output line is written to the parent terminal with the
79
+ process label:
80
+
81
+ ```ts
82
+ supervise({
83
+ name: 'server',
84
+ command: process.execPath,
85
+ args: ['-e', 'console.log("ready")'],
86
+ })
87
+ ```
88
+
89
+ Set `prefix: false` when a child should keep raw `stdout` and `stderr` output:
90
+
91
+ ```ts
92
+ supervise({
93
+ name: 'server',
94
+ prefix: false,
95
+ command: process.execPath,
96
+ args: ['-e', 'console.log("ready")'],
97
+ })
98
+ ```
99
+
100
+ Disabling the prefix changes only parent-visible output and procband
101
+ diagnostics. Matching events and final results still use the process `name`.
102
+
103
+ ## Matching
104
+
105
+ Matching is line-based and future-only. A subscription sees lines emitted after
106
+ the subscription is registered; it does not replay earlier output.
107
+
108
+ | Pattern | Behavior |
109
+ | -------- | ---------------------------------------------------------------------------- |
110
+ | String | Matches when the observed line includes the string. |
111
+ | `RegExp` | Runs against the full observed line. `lastIndex` is reset before each match. |
112
+
113
+ Use `waitFor()` for one required line:
114
+
115
+ ```ts
116
+ const event = await proc.waitFor(/^ready$/, {
117
+ stream: 'stdout',
118
+ timeoutMs: 5000,
119
+ })
120
+
121
+ console.log(event.process, event.line)
122
+ ```
123
+
124
+ Use `match()` for repeated lines:
125
+
126
+ ```ts
127
+ const unsubscribe = proc.match(
128
+ /^attempt \d+$/,
129
+ (event) => {
130
+ console.log(`observed ${event.line}`)
131
+ },
132
+ { stream: 'stdout' },
133
+ )
134
+
135
+ unsubscribe()
136
+ ```
137
+
138
+ `waitFor()` rejects when its timeout elapses or the process becomes terminal
139
+ before a matching future line appears. If a `match()` callback throws, only that
140
+ subscription is removed.
141
+
142
+ ## Restarts
143
+
144
+ `restart: true` uses the built-in policy:
145
+
146
+ | Field | Default | Meaning |
147
+ | ------------- | -------------- | -------------------------------------------------------- |
148
+ | `when` | `'on-failure'` | Restart only non-zero exits or signal exits. |
149
+ | `delayMs` | `1000` | Wait this long before the next attempt. |
150
+ | `maxFailures` | `3` | Suppress restart after more than this many failed exits. |
151
+ | `windowMs` | `30000` | Count failed exits inside this rolling window. |
152
+
153
+ Pass an explicit policy when the defaults are too slow or too permissive:
154
+
155
+ ```ts
156
+ const proc = supervise({
157
+ name: 'job',
158
+ command: process.execPath,
159
+ args: ['-e', 'process.exit(1)'],
160
+ restart: {
161
+ delayMs: 25,
162
+ maxFailures: 5,
163
+ windowMs: 1000,
164
+ },
165
+ })
166
+
167
+ const result = await proc
168
+ console.log(result.restarts, result.restartSuppressed)
169
+ ```
170
+
171
+ If the child keeps failing inside the configured window, `restartSuppressed`
172
+ becomes `true` and the final failed result is returned.
173
+
174
+ ## Shutdown
175
+
176
+ Use `proc.kill()` for deliberate shutdown from your own script:
177
+
178
+ ```ts
179
+ const stopped = proc.kill('SIGTERM')
180
+ ```
181
+
182
+ `kill()` disables future restarts and stops the active child process tree. For
183
+ `detached: true` children on Unix-like platforms, shutdown also signals the
184
+ detached child process group so same-group descendants are cleaned up even when
185
+ they are no longer reachable by parent PID.
186
+
187
+ > [!NOTE]
188
+ > `kill(0)` keeps the normal Node.js existence-check behavior. It does not stop
189
+ > supervision or disable restarts.
190
+
191
+ Parent cleanup installs `SIGINT` and `SIGTERM` handlers while live supervised
192
+ processes exist. Set `parentExitSignal` only when children require a specific
193
+ signal during parent-driven cleanup:
194
+
195
+ ```ts
196
+ supervise({
197
+ name: 'server',
198
+ command: process.execPath,
199
+ args: ['server.mjs'],
200
+ parentExitSignal: 'SIGHUP',
201
+ })
202
+ ```
203
+
204
+ `parentExitSignal` does not change the signal used by explicit `proc.kill()`
205
+ calls.
206
+
207
+ ## Failure Model
208
+
209
+ `procband` separates terminal process failure from promise rejection:
210
+
211
+ | API | Failed terminal exit |
212
+ | -------------------------------------- | ----------------------------------------------------------------------------------- |
213
+ | `await proc` | Resolves to `ProcessResult`. |
214
+ | `proc.wait()` | Resolves to `ProcessResult`. |
215
+ | `proc.wait({ rejectOnFailure: true })` | Rejects with `ProcessExitError`. |
216
+ | `proc.expectSuccess()` | Rejects with `ProcessExitError`. |
217
+ | Unobserved process | Sets parent `process.exitCode` and starts stopping other live supervised processes. |
218
+
219
+ `ProcessExitError` includes the original config, final result, command, args,
220
+ exit code, and signal:
221
+
222
+ ```ts
223
+ import { ProcessExitError } from 'procband'
224
+
225
+ try {
226
+ await proc.expectSuccess()
227
+ } catch (error) {
228
+ if (error instanceof ProcessExitError) {
229
+ console.error(error.command, error.exitCode, error.signal)
230
+ }
231
+ throw error
232
+ }
233
+ ```
234
+
235
+ ## Configuration Boundaries
236
+
237
+ | Field | Boundary |
238
+ | ---------- | ---------------------------------------------------------------------------------------------------------------- |
239
+ | `command` | Required shell-free executable or command name passed to `spawn()`. |
240
+ | `name` | Optional stable process identifier. Defaults to the trailing `/[-\w]+$/` match from `command`. |
241
+ | `label` | Optional human-facing output prefix. Defaults to `name`. |
242
+ | `prefix` | Defaults to `true`. Set to `false` to write output and diagnostics without the process label prefix. |
243
+ | `stdin` | Defaults to disconnected. Use `true` for writable `proc.stdin`, or pass a readable stream to pipe automatically. |
244
+ | `stderr` | Optional extra sink for raw child `stderr`; prefixed parent `stderr` output still happens. |
245
+ | `detached` | Passed through to `spawn()` and used during shutdown on Unix-like platforms. |
246
+ | `color` | Optional RGB prefix color for `stdout`; reserved `stderr` red cannot be used. |
247
+
248
+ Invalid config, such as a missing `command`, a command without an inferable
249
+ fallback `name`, or a reserved color, throws synchronously from `supervise()`.
250
+
251
+ ## Terminology
252
+
253
+ | Term | Meaning |
254
+ | ------------------- | ------------------------------------------------------------------------------------------------- |
255
+ | Supervised process | A `ProcbandProcess` wrapper plus its current child attempt. |
256
+ | Child attempt | One concrete spawned process instance inside a supervision run. |
257
+ | Terminal | No child is running and no restart will be started. |
258
+ | Restart suppression | Automatic disabling of further restarts after too many failed exits inside the configured window. |
259
+ | Match | A future observed output line that satisfies a string or regex pattern. |
260
+
261
+ ## Non-Goals
178
262
 
179
263
  - A standalone CLI
180
264
  - Historical log replay
181
265
  - Multi-process orchestration in one top-level API
182
266
  - Shell command parsing
183
- - Full service-management features such as persistence, cron scheduling, or host
267
+ - Service-management features such as persistence, cron scheduling, or host
184
268
  restarts
@@ -0,0 +1,80 @@
1
+ # Foreground Commands
2
+
3
+ > Use `expectSuccess()` when a supervised child process represents a script step
4
+ > that must succeed before the parent script can continue.
5
+
6
+ `procband` normally resolves failed exits to `ProcessResult` because background
7
+ supervision often needs to inspect failures instead of throwing immediately.
8
+ Foreground command-runner steps usually want the opposite behavior.
9
+
10
+ ## Reject Failed Exits
11
+
12
+ Use `expectSuccess()` when a non-zero exit or signal exit should reject:
13
+
14
+ ```ts
15
+ import process from 'node:process'
16
+ import { ProcessExitError, supervise } from 'procband'
17
+
18
+ try {
19
+ await supervise({
20
+ name: 'db',
21
+ command: 'pnpm',
22
+ args: ['db', 'ensure'],
23
+ }).expectSuccess()
24
+ } catch (error) {
25
+ if (error instanceof ProcessExitError) {
26
+ console.error(error.command, error.args, error.exitCode)
27
+ }
28
+ throw error
29
+ }
30
+ ```
31
+
32
+ When the command exits successfully, the promise resolves to `ProcessResult`.
33
+ When it fails, `ProcessExitError` exposes the command, args, exit code, signal,
34
+ original config, and terminal result.
35
+
36
+ ## Use `wait()` When the Choice Is Dynamic
37
+
38
+ `expectSuccess()` is equivalent to `wait({ rejectOnFailure: true })`. Use
39
+ `wait()` when a caller decides the behavior:
40
+
41
+ ```ts
42
+ import process from 'node:process'
43
+ import { supervise } from 'procband'
44
+
45
+ const proc = supervise({
46
+ name: 'check',
47
+ command: process.execPath,
48
+ args: ['-e', 'process.exit(1)'],
49
+ })
50
+
51
+ const result = await proc.wait({ rejectOnFailure: shouldThrow })
52
+ ```
53
+
54
+ If `shouldThrow` is `false`, the failed exit resolves to `ProcessResult`. If it
55
+ is `true`, the same exit rejects with `ProcessExitError`.
56
+
57
+ ## Let Background Failures Propagate
58
+
59
+ For background processes that should fail the parent script when nobody handles
60
+ their result, leave the process unobserved:
61
+
62
+ ```ts
63
+ import process from 'node:process'
64
+ import { supervise } from 'procband'
65
+
66
+ supervise({
67
+ name: 'worker',
68
+ command: process.execPath,
69
+ args: ['-e', 'setTimeout(() => process.exit(1), 60)'],
70
+ })
71
+ ```
72
+
73
+ If this process reaches a failed terminal state and no code has awaited it or
74
+ called `wait()`, `procband` sets the parent `process.exitCode` and starts
75
+ stopping other live `procband` processes in the same parent script.
76
+
77
+ > [!IMPORTANT]
78
+ > Awaiting `proc` or calling `proc.wait()` marks the terminal result as
79
+ > observed. Once observed, default parent-exit propagation is suppressed for
80
+ > that process.
@@ -0,0 +1,104 @@
1
+ # Readiness and Matching
2
+
3
+ > Use future line matching when a parent script must wait for a child process to
4
+ > announce readiness or react to repeated output without buffering log history.
5
+
6
+ Matching starts when you call `waitFor()` or `match()`. Earlier output is not
7
+ replayed, so create the supervised process and register the wait before the line
8
+ you need can be missed.
9
+
10
+ ## Wait for One Line
11
+
12
+ Use `waitFor()` when the next step cannot start until the child prints a known
13
+ line:
14
+
15
+ ```ts
16
+ import process from 'node:process'
17
+ import { supervise } from 'procband'
18
+
19
+ const api = supervise({
20
+ name: 'api',
21
+ command: process.execPath,
22
+ args: [
23
+ '-e',
24
+ [
25
+ 'console.log("booting")',
26
+ 'setTimeout(() => console.log("ready"), 20)',
27
+ 'setInterval(() => {}, 1000)',
28
+ ].join(';'),
29
+ ],
30
+ })
31
+
32
+ const event = await api.waitFor('ready', {
33
+ stream: 'stdout',
34
+ timeoutMs: 5000,
35
+ })
36
+
37
+ console.log(`api is ${event.line}`)
38
+ ```
39
+
40
+ The wait resolves with the first future `stdout` line that contains `ready`.
41
+ When `timeoutMs` elapses first, or the process exits before a matching line is
42
+ observed, the wait rejects.
43
+
44
+ ## Match Repeated Lines
45
+
46
+ Use `match()` when the child may print the same kind of line many times:
47
+
48
+ ```ts
49
+ const unsubscribe = api.match(
50
+ /^warn:/,
51
+ (event) => {
52
+ console.log(`observed ${event.stream}: ${event.line}`)
53
+ },
54
+ { stream: 'stderr' },
55
+ )
56
+
57
+ // Later, when this script no longer needs warning callbacks:
58
+ unsubscribe()
59
+ ```
60
+
61
+ Each subscription is independent. If one callback throws, that subscription is
62
+ removed and other subscriptions keep running.
63
+
64
+ ## Choose a Pattern
65
+
66
+ | Pattern | Best for | Result detail |
67
+ | ----------- | ------------------------------------ | -------------------------------------------------- |
68
+ | `'ready'` | Simple substring checks. | `event.match` is `null`. |
69
+ | `/^ready$/` | Exact line shape or captured values. | `event.match` contains the `RegExp.exec()` result. |
70
+
71
+ ```ts
72
+ const event = await api.waitFor(/^listening on (\d+)$/)
73
+ const port = Number(event.match?.[1])
74
+ ```
75
+
76
+ The regular expression runs against the full observed line without the trailing
77
+ newline.
78
+
79
+ ## Start Another Process After Readiness
80
+
81
+ This pattern keeps sequencing explicit: wait for the first process, then start
82
+ the dependent process.
83
+
84
+ ```ts
85
+ import process from 'node:process'
86
+ import { supervise } from 'procband'
87
+
88
+ const api = supervise({
89
+ name: 'api',
90
+ command: process.execPath,
91
+ args: ['-e', 'setTimeout(() => console.log("ready"), 20)'],
92
+ })
93
+
94
+ await api.waitFor('ready')
95
+
96
+ supervise({
97
+ name: 'worker',
98
+ command: process.execPath,
99
+ args: ['-e', 'console.log("watching")'],
100
+ })
101
+ ```
102
+
103
+ After the API prints `ready`, the worker starts and its output is prefixed
104
+ separately.