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/README.md +97 -15
- package/dist/index.d.mts +81 -6
- package/dist/index.mjs +128 -10
- package/docs/context.md +262 -178
- package/docs/guides/foreground-commands.md +80 -0
- package/docs/guides/readiness.md +104 -0
- package/docs/guides/restarts.md +115 -0
- package/docs/index.md +61 -0
- package/package.json +14 -11
package/docs/context.md
CHANGED
|
@@ -1,184 +1,268 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
`procband` supervises one
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
-
|
|
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.
|