procband 0.3.4 → 0.3.6

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
@@ -1,38 +1,102 @@
1
1
  # procband
2
2
 
3
- ## Purpose
3
+ > Supervise subprocesses from TypeScript when a script needs prefixed output,
4
+ > readiness matching, restart policy, and process-tree shutdown without becoming
5
+ > a standalone process manager.
4
6
 
5
- `procband` supervises subprocesses from TypeScript. It prefixes child output,
6
- matches future log lines, waits for terminal exit, optionally restarts failed
7
- processes, tree-kills descendants during shutdown, and propagates unobserved
8
- terminal failures to the parent process.
7
+ `procband` wraps one child process per `supervise()` call. It starts the child
8
+ immediately, prefixes `stdout` and `stderr`, lets your script wait for future
9
+ log lines, and resolves to a final `ProcessResult` when no more restart attempt
10
+ will run.
9
11
 
10
- ## Installation
12
+ ## Install
11
13
 
12
14
  ```sh
13
15
  pnpm add procband
14
16
  ```
15
17
 
16
- ## Quick Example
18
+ ## First Process
19
+
20
+ Use `waitFor()` when the next step depends on output that the child will print
21
+ after supervision starts:
17
22
 
18
23
  ```ts
19
24
  import process from 'node:process'
20
25
  import { supervise } from 'procband'
21
26
 
22
27
  const proc = supervise({
28
+ name: 'worker',
23
29
  command: process.execPath,
24
30
  args: ['-e', 'console.log("ready")'],
25
31
  })
26
32
 
27
- await proc.waitFor('ready')
33
+ const ready = await proc.waitFor('ready')
34
+ console.log(`matched ${ready.line}`)
35
+
28
36
  const result = await proc
29
- console.log(result)
37
+ console.log(result.exitCode)
38
+ ```
39
+
40
+ The child output is written to the parent process with a `worker` prefix, the
41
+ readiness wait resolves when a future line contains `ready`, and `await proc`
42
+ returns the terminal result.
43
+
44
+ ## Choose the Failure Behavior
45
+
46
+ By default, a failed terminal exit resolves to `ProcessResult`. Use this when a
47
+ supervisor script needs to inspect the outcome:
48
+
49
+ ```ts
50
+ import process from 'node:process'
51
+ import { supervise } from 'procband'
52
+
53
+ const result = await supervise({
54
+ name: 'job',
55
+ command: process.execPath,
56
+ args: ['-e', 'process.exit(1)'],
57
+ })
58
+
59
+ if (result.exitCode !== 0) {
60
+ console.error(`job failed with ${result.exitCode}`)
61
+ }
30
62
  ```
31
63
 
32
- ## Documentation Map
64
+ Use `expectSuccess()` for foreground command-runner steps where a failed exit
65
+ should reject:
66
+
67
+ ```ts
68
+ import { ProcessExitError, supervise } from 'procband'
69
+
70
+ try {
71
+ await supervise({
72
+ name: 'db',
73
+ command: 'pnpm',
74
+ args: ['db', 'ensure'],
75
+ }).expectSuccess()
76
+ } catch (error) {
77
+ if (error instanceof ProcessExitError) {
78
+ console.error(error.command, error.args, error.exitCode)
79
+ }
80
+ throw error
81
+ }
82
+ ```
83
+
84
+ If your script never awaits the process or calls `wait()`, an unobserved failed
85
+ terminal exit sets the parent `process.exitCode` and starts stopping other live
86
+ `procband` processes in the same parent script.
87
+
88
+ ## Documentation
89
+
90
+ Start with the page that matches the decision in front of you:
91
+
92
+ | Page | Use it when |
93
+ | --------------------------------------------------------- | ---------------------------------------------------------------- |
94
+ | [Docs overview](docs/index.md) | You need the moving parts, boundaries, and next page to read. |
95
+ | [Readiness and matching](docs/guides/readiness.md) | A script must wait for output or react to repeated log lines. |
96
+ | [Restart failed processes](docs/guides/restarts.md) | A child can fail transiently and should be retried with limits. |
97
+ | [Foreground commands](docs/guides/foreground-commands.md) | A script step should reject when a command exits unsuccessfully. |
98
+ | [Concepts and lifecycle](docs/context.md) | You need lifecycle, shutdown, error, and invariant details. |
33
99
 
34
- - Concepts and lifecycle: [docs/context.md](docs/context.md)
35
- - Minimal usage example: [examples/basic-usage.ts](examples/basic-usage.ts)
36
- - Restart example: [examples/restart-on-failure.ts](examples/restart-on-failure.ts)
37
- - Sequencing example: [examples/sequenced-processes.ts](examples/sequenced-processes.ts)
38
- - Exact exported signatures: [dist/index.d.mts](dist/index.d.mts)
100
+ Runnable examples live in [`examples/`](examples/). Exact public signatures are
101
+ generated from the TypeScript source and published through
102
+ [`dist/index.d.mts`](dist/index.d.mts).
package/dist/index.d.mts CHANGED
@@ -62,6 +62,12 @@ interface ProcessConfig {
62
62
  * Defaults to `name`.
63
63
  */
64
64
  label?: string;
65
+ /**
66
+ * Whether child output should be prefixed with the process label.
67
+ *
68
+ * Defaults to `true`. Set to `false` for raw child output.
69
+ */
70
+ prefix?: boolean;
65
71
  /**
66
72
  * RGB color for this process's `stdout` prefix.
67
73
  *
@@ -221,6 +227,18 @@ interface ProcessResult {
221
227
  */
222
228
  restartSuppressed: boolean;
223
229
  }
230
+ /**
231
+ * Options for `wait()`.
232
+ */
233
+ interface WaitOptions {
234
+ /**
235
+ * Reject with `ProcessExitError` when the process exits unsuccessfully.
236
+ *
237
+ * Defaults to `false`, preserving procband's supervision-oriented behavior
238
+ * where terminal failures resolve to `ProcessResult`.
239
+ */
240
+ rejectOnFailure?: boolean;
241
+ }
224
242
  /**
225
243
  * A supervised child-process handle.
226
244
  *
@@ -257,11 +275,16 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
257
275
  /**
258
276
  * Wait for the process to become terminal with no further restart pending.
259
277
  *
260
- * Unlike many process helpers, this resolves for both success and failure.
261
- * Inspect the returned `ProcessResult` instead of relying on promise
262
- * rejection for non-zero exits.
278
+ * By default this resolves for both success and failure. Pass
279
+ * `rejectOnFailure: true` to reject failed exits with `ProcessExitError`.
280
+ */
281
+ wait(options?: WaitOptions): Promise<ProcessResult>;
282
+ /**
283
+ * Wait for the process to become terminal and reject if it fails.
284
+ *
285
+ * This is equivalent to `wait({ rejectOnFailure: true })`.
263
286
  */
264
- wait(): Promise<ProcessResult>;
287
+ expectSuccess(): Promise<ProcessResult>;
265
288
  /**
266
289
  * Disable future restarts and terminate the active process tree.
267
290
  *
@@ -273,6 +296,21 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
273
296
  kill(signal?: KillSignal): boolean;
274
297
  }
275
298
  //#endregion
299
+ //#region src/errors.d.ts
300
+ /**
301
+ * Error thrown when a process was expected to exit successfully but did not.
302
+ */
303
+ declare class ProcessExitError extends Error {
304
+ readonly config: ProcessConfig;
305
+ readonly result: ProcessResult;
306
+ readonly command: string;
307
+ readonly args: readonly string[];
308
+ readonly code: number | null;
309
+ readonly exitCode: number;
310
+ readonly signal: NodeJS.Signals | null;
311
+ constructor(config: ProcessConfig, result: ProcessResult);
312
+ }
313
+ //#endregion
276
314
  //#region src/process.d.ts
277
315
  /**
278
316
  * Start supervising a single subprocess.
@@ -303,4 +341,4 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
303
341
  */
304
342
  declare function supervise(config: ProcessConfig): ProcbandProcess;
305
343
  //#endregion
306
- export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, type ProcessResult, type RestartPolicy, type RgbColor, type Unsubscribe, type WaitForOptions, supervise };
344
+ export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, ProcessExitError, type ProcessResult, type RestartPolicy, type RgbColor, type Unsubscribe, type WaitForOptions, type WaitOptions, supervise };
package/dist/index.mjs CHANGED
@@ -5,6 +5,45 @@ import process from "node:process";
5
5
  import ansiStyles from "ansi-styles";
6
6
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
7
7
  import treeKill, { treeKillSync } from "@alloc/tree-kill";
8
+ //#region src/errors.ts
9
+ /**
10
+ * Error thrown when a process was expected to exit successfully but did not.
11
+ */
12
+ var ProcessExitError = class extends Error {
13
+ config;
14
+ result;
15
+ command;
16
+ args;
17
+ code;
18
+ exitCode;
19
+ signal;
20
+ constructor(config, result) {
21
+ super(formatProcessExitMessage(config, result));
22
+ this.name = "ProcessExitError";
23
+ this.config = config;
24
+ this.result = result;
25
+ this.command = config.command;
26
+ this.args = [...config.args ?? []];
27
+ this.code = result.code;
28
+ this.exitCode = result.exitCode;
29
+ this.signal = result.signal;
30
+ }
31
+ };
32
+ function formatProcessExitMessage(config, result) {
33
+ const command = formatCommand(config);
34
+ const prefix = `Process "${result.name}" failed: ${command}`;
35
+ if (result.signal) return `${prefix} exited by signal ${result.signal} (exit code ${result.exitCode})`;
36
+ if (result.code != null) return `${prefix} exited with code ${result.code}`;
37
+ return `${prefix} exited unsuccessfully (exit code ${result.exitCode})`;
38
+ }
39
+ function formatCommand(config) {
40
+ return [config.command, ...config.args ?? []].map(formatCommandPart).join(" ");
41
+ }
42
+ function formatCommandPart(value) {
43
+ if (/^[\w./:=@%+,-]+$/.test(value)) return value;
44
+ return JSON.stringify(value);
45
+ }
46
+ //#endregion
8
47
  //#region src/colors.ts
9
48
  const prefixPalette = [
10
49
  [
@@ -81,7 +120,8 @@ function validateProcessColor(color) {
81
120
  if (red === stderrColor[0] && green === stderrColor[1] && blue === stderrColor[2]) throw new Error("ProcessConfig.color cannot use the reserved stderr color");
82
121
  }
83
122
  function writePrefixedLine(target, label, color, line, appendNewline) {
84
- target.write(formatPrefix(label, color) + line + (appendNewline ? "\n" : ""));
123
+ const prefix = label == null ? "" : formatPrefix(label, color);
124
+ target.write(prefix + line + (appendNewline ? "\n" : ""));
85
125
  }
86
126
  function formatPrefix(label, color) {
87
127
  return `${ansiStyles.color.ansi16m(...color)}[${label}]${ansiStyles.color.close} `;
@@ -400,6 +440,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
400
440
  config;
401
441
  name;
402
442
  label;
443
+ prefix;
403
444
  color;
404
445
  matches;
405
446
  restart;
@@ -422,6 +463,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
422
463
  this.config = config;
423
464
  this.name = name;
424
465
  this.label = config.label ?? name;
466
+ this.prefix = config.name === "" ? false : config.prefix ?? true;
425
467
  this.color = resolveProcessColor(config.color);
426
468
  this.matches = new MatchRegistry(this.name);
427
469
  this.restart = new RestartController(config.restart);
@@ -480,10 +522,14 @@ var ProcbandProcessImpl = class extends EventEmitter {
480
522
  waitFor(pattern, options) {
481
523
  return this.matches.waitFor(pattern, options);
482
524
  }
483
- wait() {
525
+ wait(options) {
484
526
  this.markTerminalResultObserved();
527
+ if (options?.rejectOnFailure) return this.finalPromise.then((result) => this.expectSuccessfulResult(result));
485
528
  return this.finalPromise;
486
529
  }
530
+ expectSuccess() {
531
+ return this.wait({ rejectOnFailure: true });
532
+ }
487
533
  then(onfulfilled, onrejected) {
488
534
  this.markTerminalResultObserved();
489
535
  return this.finalPromise.then(onfulfilled, onrejected);
@@ -626,7 +672,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
626
672
  buffer.text = text;
627
673
  }
628
674
  handleLine(stream, line, appendNewline) {
629
- writePrefixedLine(stream === "stdout" ? process.stdout : process.stderr, this.label, stream === "stdout" ? this.color : stderrColor, line, appendNewline);
675
+ writePrefixedLine(stream === "stdout" ? process.stdout : process.stderr, this.prefix ? this.label : null, stream === "stdout" ? this.color : stderrColor, line, appendNewline);
630
676
  this.matches.emit(stream, line);
631
677
  }
632
678
  async handleAttemptClose(code, signal) {
@@ -669,12 +715,16 @@ var ProcbandProcessImpl = class extends EventEmitter {
669
715
  }
670
716
  closeMatches() {
671
717
  const error = /* @__PURE__ */ new Error(`Process "${this.name}" exited before a matching line was observed`);
672
- if (this.matches.hasPendingWait()) writePrefixedLine(process.stderr, this.label, stderrColor, error.message, true);
718
+ if (this.matches.hasPendingWait()) writePrefixedLine(process.stderr, this.prefix ? this.label : null, stderrColor, error.message, true);
673
719
  this.matches.close(error);
674
720
  }
675
721
  markTerminalResultObserved() {
676
722
  this.terminalResultObserved = true;
677
723
  }
724
+ expectSuccessfulResult(result) {
725
+ if (isFailedResult(result)) throw new ProcessExitError(this.config, result);
726
+ return result;
727
+ }
678
728
  shouldPropagateFailure(result) {
679
729
  return result.exitCode !== 0 && !this.shutdownRequested && !this.terminalResultObserved;
680
730
  }
@@ -734,7 +784,7 @@ let propagatedFailure = false;
734
784
  function validateProcessConfig(config) {
735
785
  if (!config.command) throw new Error("ProcessConfig.command is required");
736
786
  validateProcessColor(config.color);
737
- const name = config.name || inferProcessName(config.command);
787
+ const name = config.name === void 0 || config.name === "" ? inferProcessName(config.command) : config.name;
738
788
  if (!name) throw new Error("ProcessConfig.name is required when command does not match /[-\\w]+$/");
739
789
  return name;
740
790
  }
@@ -746,11 +796,17 @@ function getResultExitCode(code, signal) {
746
796
  if (signal) return 128 + (constants.signals[signal] ?? 0);
747
797
  return 1;
748
798
  }
799
+ function isFailedResult(result) {
800
+ return result.exitCode !== 0 || result.signal != null;
801
+ }
749
802
  function propagateFailure(exitCode) {
750
803
  if (propagatedFailure) return;
751
804
  propagatedFailure = true;
752
805
  if (process.exitCode == null || process.exitCode === 0) process.exitCode = exitCode;
753
806
  for (const proc of liveProcesses) proc.kill();
807
+ setImmediate(() => {
808
+ process.exit(process.exitCode ?? exitCode);
809
+ });
754
810
  }
755
811
  //#endregion
756
- export { supervise };
812
+ export { ProcessExitError, supervise };
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.
@@ -0,0 +1,115 @@
1
+ # Restart Failed Processes
2
+
3
+ > Add a restart policy when a child process may fail transiently and the parent
4
+ > script should retry before treating the supervision run as terminal.
5
+
6
+ Restarts are disabled by default. Enable them per process with `restart: true`
7
+ or an explicit `RestartPolicy`.
8
+
9
+ ## Use the Built-In Policy
10
+
11
+ Use `restart: true` when the defaults are acceptable:
12
+
13
+ ```ts
14
+ import process from 'node:process'
15
+ import { supervise } from 'procband'
16
+
17
+ const proc = supervise({
18
+ name: 'worker',
19
+ command: process.execPath,
20
+ args: ['-e', 'process.exit(1)'],
21
+ restart: true,
22
+ })
23
+
24
+ const result = await proc
25
+ console.log(result.restarts, result.restartSuppressed)
26
+ ```
27
+
28
+ The default policy restarts failed exits after `1000` milliseconds and suppresses
29
+ restart after more than `3` failed exits inside `30000` milliseconds.
30
+
31
+ ## Set an Explicit Policy
32
+
33
+ Use an explicit policy when tests, examples, or short-lived scripts need faster
34
+ feedback:
35
+
36
+ ```ts
37
+ import process from 'node:process'
38
+ import { supervise } from 'procband'
39
+
40
+ const proc = supervise({
41
+ name: 'job',
42
+ command: process.execPath,
43
+ args: ['-e', 'process.exit(1)'],
44
+ restart: {
45
+ when: 'on-failure',
46
+ delayMs: 25,
47
+ maxFailures: 5,
48
+ windowMs: 1000,
49
+ },
50
+ })
51
+ ```
52
+
53
+ With this policy, failed attempts are retried after `25` milliseconds. Once more
54
+ than `5` failures occur inside `1000` milliseconds, `procband` stops retrying
55
+ and resolves the terminal result with `restartSuppressed: true`.
56
+
57
+ ## Wait for a Later Attempt
58
+
59
+ `waitFor()` watches future output across restart attempts, so it can wait for a
60
+ line that appears only after earlier attempts fail:
61
+
62
+ ```ts
63
+ import { mkdtempSync, writeFileSync } from 'node:fs'
64
+ import process from 'node:process'
65
+ import { tmpdir } from 'node:os'
66
+ import { join } from 'node:path'
67
+ import { supervise } from 'procband'
68
+
69
+ const stateDir = mkdtempSync(join(tmpdir(), 'procband-example-'))
70
+ const attemptFile = join(stateDir, 'attempt.txt')
71
+ writeFileSync(attemptFile, '0')
72
+
73
+ const script = [
74
+ 'const fs = await import("node:fs")',
75
+ 'const file = process.argv[1]',
76
+ 'let attempt = Number(fs.readFileSync(file, "utf8"))',
77
+ 'attempt += 1',
78
+ 'fs.writeFileSync(file, String(attempt))',
79
+ 'console.log(`attempt ${attempt}`)',
80
+ 'if (attempt < 3) process.exit(1)',
81
+ 'console.log("ready")',
82
+ ].join(';')
83
+
84
+ const proc = supervise({
85
+ name: 'job',
86
+ command: process.execPath,
87
+ args: ['-e', script, attemptFile],
88
+ restart: {
89
+ delayMs: 25,
90
+ maxFailures: 5,
91
+ windowMs: 1000,
92
+ },
93
+ })
94
+
95
+ proc.match(/^attempt \d+$/, (event) => {
96
+ console.log(`observed ${event.line}`)
97
+ })
98
+
99
+ await proc.waitFor('ready')
100
+ const result = await proc
101
+ ```
102
+
103
+ The first two attempts can exit unsuccessfully, the third attempt can print
104
+ `ready`, and the final result includes the number of restart attempts that were
105
+ started.
106
+
107
+ ## Pick `when` Deliberately
108
+
109
+ | `when` | Restarts after | Use it when |
110
+ | -------------- | -------------------------------- | ------------------------------------------------------------------------------------ |
111
+ | `'on-failure'` | Non-zero exits and signal exits. | A clean exit means the work is done. |
112
+ | `'on-exit'` | Any exit. | The child is expected to be long-lived and should be relaunched even after code `0`. |
113
+
114
+ `proc.kill()` disables future restarts before stopping the active process tree,
115
+ so deliberate shutdown does not start another attempt.
package/docs/index.md ADDED
@@ -0,0 +1,61 @@
1
+ # Documentation Overview
2
+
3
+ > Choose the right `procband` behavior before reaching for an API: readiness
4
+ > matching, failure observation, restart policy, and shutdown each change how a
5
+ > script should supervise its child process.
6
+
7
+ `procband` is for TypeScript scripts that need to run child processes with a
8
+ small amount of supervision. It is not a daemon, service manager, CLI runner, or
9
+ multi-process orchestrator.
10
+
11
+ ## What It Adds
12
+
13
+ Each `supervise()` call wraps one child process and adds five behaviors on top
14
+ of Node.js `spawn()`:
15
+
16
+ | Behavior | Use it when |
17
+ | ------------------------------ | -------------------------------------------------------------------------- |
18
+ | Optional prefixed output | Multiple child processes write to the same parent terminal. |
19
+ | Future line matching | The parent script must wait for a readiness line or react to logs. |
20
+ | Optional restarts | A transient failure should start another child attempt. |
21
+ | Process-tree shutdown | Deliberate cleanup should stop descendants as well as the direct child. |
22
+ | Unobserved failure propagation | A background failure should fail the parent script when nobody awaited it. |
23
+
24
+ ```ts
25
+ import process from 'node:process'
26
+ import { supervise } from 'procband'
27
+
28
+ const proc = supervise({
29
+ name: 'api',
30
+ command: process.execPath,
31
+ args: ['-e', 'console.log("ready")'],
32
+ })
33
+
34
+ await proc.waitFor('ready')
35
+ ```
36
+
37
+ After `supervise()` runs, the child has already started and future output can be
38
+ matched.
39
+
40
+ ## First Decisions
41
+
42
+ | Decision | Default | Change it when |
43
+ | ---------------- | --------------------------------------------- | -------------------------------------------------------------------------- |
44
+ | Process identity | `name` is inferred from `command`. | The command path does not end in `/[-\w]+$/`, or logs need a stable label. |
45
+ | Output prefix | Child output is labeled. | Pass `prefix: false` when the child should write raw output. |
46
+ | Failure handling | Awaiting resolves to `ProcessResult`. | Use `expectSuccess()` for command-runner steps that should reject. |
47
+ | Matching scope | `waitFor()` and `match()` watch both streams. | Pass `{ stream: 'stdout' }` or `{ stream: 'stderr' }` to narrow matching. |
48
+ | Restart behavior | No restart. | Pass `restart: true` or a policy for transient child failures. |
49
+ | Stdin | Disconnected. | Pass `stdin: true` or a readable stream when the child needs input. |
50
+
51
+ ## Where to Go Next
52
+
53
+ | Page | Reader job |
54
+ | ---------------------------------------------------- | ------------------------------------------------------------------- |
55
+ | [Readiness and matching](guides/readiness.md) | Wait for future output or subscribe to repeated matching lines. |
56
+ | [Restart failed processes](guides/restarts.md) | Configure retries with a failure-suppression guard. |
57
+ | [Foreground commands](guides/foreground-commands.md) | Make a supervised command reject on failed exit. |
58
+ | [Concepts and lifecycle](context.md) | Understand terminal state, shutdown, config boundaries, and errors. |
59
+
60
+ Runnable scripts in [`../examples/`](../examples/) cover the same workflows with
61
+ project-realistic child processes that can be checked by `pnpm docs:check`.
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "procband",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Supervise subprocesses from TypeScript: prefix logs, wait for output patterns, and manage lifecycle/restarts.",
5
+ "packageManager": "pnpm@11.5.1",
5
6
  "license": "MIT",
6
7
  "author": "Alec Larson",
7
8
  "repository": {
@@ -18,8 +19,19 @@
18
19
  "types": "./dist/index.d.mts",
19
20
  "default": "./dist/index.mjs"
20
21
  },
22
+ "scripts": {
23
+ "dev": "tsdown --sourcemap --watch",
24
+ "build": "tsdown",
25
+ "docs:check": "pnpm build && tsc -p examples --noEmit && node examples/basic-usage.ts && node examples/restart-on-failure.ts",
26
+ "format": "oxfmt .",
27
+ "lint": "oxlint src",
28
+ "typecheck": "tsc --noEmit && tsc -p test --noEmit",
29
+ "test": "vitest",
30
+ "prepublishOnly": "pnpm build"
31
+ },
21
32
  "devDependencies": {
22
33
  "@types/node": "^25.5.2",
34
+ "lildocs": "^0.1.11",
23
35
  "oxfmt": "^0.43.0",
24
36
  "oxlint": "^1.58.0",
25
37
  "tsdown": "^0.21.7",
@@ -29,14 +41,5 @@
29
41
  "dependencies": {
30
42
  "@alloc/tree-kill": "^1.4.0",
31
43
  "ansi-styles": "^6.2.3"
32
- },
33
- "scripts": {
34
- "dev": "tsdown --sourcemap --watch",
35
- "build": "tsdown",
36
- "docs:check": "pnpm build && tsc -p examples --noEmit && node examples/basic-usage.ts && node examples/restart-on-failure.ts",
37
- "format": "oxfmt .",
38
- "lint": "oxlint src",
39
- "typecheck": "tsc --noEmit && tsc -p test --noEmit",
40
- "test": "vitest"
41
44
  }
42
- }
45
+ }