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.
@@ -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.5",
3
+ "version": "0.3.7",
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
+ }