procband 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +13 -24
- package/dist/index.mjs +37 -20
- package/docs/context.md +12 -13
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -7,7 +7,7 @@ import { Readable, Writable } from "node:stream";
|
|
|
7
7
|
*/
|
|
8
8
|
type Signals = NodeJS.Signals;
|
|
9
9
|
/**
|
|
10
|
-
* Signal values accepted by `kill()
|
|
10
|
+
* Signal values accepted by `kill()`.
|
|
11
11
|
*/
|
|
12
12
|
type KillSignal = number | Signals;
|
|
13
13
|
/**
|
|
@@ -47,6 +47,12 @@ interface ProcessConfig {
|
|
|
47
47
|
* Environment variables for the child process.
|
|
48
48
|
*/
|
|
49
49
|
env?: Record<string, string | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* Whether to spawn the child in a detached process group/session.
|
|
52
|
+
*
|
|
53
|
+
* This is passed through to Node.js `spawn()` unchanged.
|
|
54
|
+
*/
|
|
55
|
+
detached?: boolean;
|
|
50
56
|
/**
|
|
51
57
|
* Human-facing label used in prefixed output.
|
|
52
58
|
*
|
|
@@ -81,7 +87,7 @@ interface ProcessConfig {
|
|
|
81
87
|
* When provided, `procband` uses this signal for parent-driven cleanup on
|
|
82
88
|
* `SIGINT`, `SIGTERM`, and `exit` instead of mirroring the parent signal or
|
|
83
89
|
* relying on the platform default. This only affects parent-driven cleanup;
|
|
84
|
-
* `proc.
|
|
90
|
+
* `proc.kill()` still uses its own explicit signal argument.
|
|
85
91
|
*/
|
|
86
92
|
parentExitSignal?: KillSignal;
|
|
87
93
|
/**
|
|
@@ -180,23 +186,6 @@ type MatchCallback = (event: MatchEvent) => void;
|
|
|
180
186
|
* Stops a callback subscription created by `match()`.
|
|
181
187
|
*/
|
|
182
188
|
type Unsubscribe = () => void;
|
|
183
|
-
/**
|
|
184
|
-
* Options for `stop()`.
|
|
185
|
-
*/
|
|
186
|
-
interface StopOptions {
|
|
187
|
-
/**
|
|
188
|
-
* Signal used for the initial process-tree shutdown attempt.
|
|
189
|
-
*
|
|
190
|
-
* Defaults to `"SIGTERM"`.
|
|
191
|
-
*/
|
|
192
|
-
signal?: KillSignal;
|
|
193
|
-
/**
|
|
194
|
-
* Delay before escalating to `SIGKILL`.
|
|
195
|
-
*
|
|
196
|
-
* Defaults to `5000`.
|
|
197
|
-
*/
|
|
198
|
-
killAfterMs?: number;
|
|
199
|
-
}
|
|
200
189
|
/**
|
|
201
190
|
* Final state for a supervised process after all restart attempts are done.
|
|
202
191
|
*/
|
|
@@ -273,11 +262,11 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
|
|
|
273
262
|
/**
|
|
274
263
|
* Disable future restarts and terminate the active process tree.
|
|
275
264
|
*
|
|
276
|
-
* `
|
|
277
|
-
*
|
|
278
|
-
* `
|
|
265
|
+
* For supervised processes, `kill()` targets the current child process and
|
|
266
|
+
* any descendants it spawned instead of only the direct child.
|
|
267
|
+
* `kill(0)` preserves the normal `ChildProcess` existence-check behavior.
|
|
279
268
|
*/
|
|
280
|
-
|
|
269
|
+
kill(signal?: KillSignal): boolean;
|
|
281
270
|
}
|
|
282
271
|
//#endregion
|
|
283
272
|
//#region src/process.d.ts
|
|
@@ -310,4 +299,4 @@ interface ProcbandProcess extends ChildProcess, PromiseLike<ProcessResult> {
|
|
|
310
299
|
*/
|
|
311
300
|
declare function supervise(config: ProcessConfig): ProcbandProcess;
|
|
312
301
|
//#endregion
|
|
313
|
-
export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, type ProcessResult, type RestartPolicy, type RgbColor, type
|
|
302
|
+
export { type MatchCallback, type MatchEvent, type MatchOptions, type ProcbandProcess, type ProcessConfig, type ProcessResult, type RestartPolicy, type RgbColor, type Unsubscribe, type WaitForOptions, supervise };
|
package/dist/index.mjs
CHANGED
|
@@ -111,7 +111,7 @@ var MatchRegistry = class {
|
|
|
111
111
|
}
|
|
112
112
|
waitFor(pattern, options) {
|
|
113
113
|
if (this.closedError) return Promise.reject(this.closedError);
|
|
114
|
-
|
|
114
|
+
const promise = new Promise((resolve, reject) => {
|
|
115
115
|
const subscription = {
|
|
116
116
|
active: true,
|
|
117
117
|
kind: "promise",
|
|
@@ -125,6 +125,9 @@ var MatchRegistry = class {
|
|
|
125
125
|
this.unsubscribe(subscription);
|
|
126
126
|
reject(error);
|
|
127
127
|
},
|
|
128
|
+
suppressUnhandledRejection: () => {
|
|
129
|
+
promise.catch(() => {});
|
|
130
|
+
},
|
|
128
131
|
timer: null
|
|
129
132
|
};
|
|
130
133
|
if (options?.timeoutMs != null) subscription.timer = setTimeout(() => {
|
|
@@ -132,6 +135,7 @@ var MatchRegistry = class {
|
|
|
132
135
|
}, options.timeoutMs);
|
|
133
136
|
this.subscriptions.add(subscription);
|
|
134
137
|
});
|
|
138
|
+
return promise;
|
|
135
139
|
}
|
|
136
140
|
emit(stream, line) {
|
|
137
141
|
if (this.closedError) return;
|
|
@@ -155,11 +159,17 @@ var MatchRegistry = class {
|
|
|
155
159
|
subscription.resolve(event);
|
|
156
160
|
}
|
|
157
161
|
}
|
|
162
|
+
hasPendingWait() {
|
|
163
|
+
for (const subscription of this.subscriptions) if (subscription.kind === "promise") return true;
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
158
166
|
close(error) {
|
|
159
167
|
if (this.closedError) return;
|
|
160
168
|
this.closedError = error;
|
|
161
|
-
for (const subscription of [...this.subscriptions]) if (subscription.kind === "promise")
|
|
162
|
-
|
|
169
|
+
for (const subscription of [...this.subscriptions]) if (subscription.kind === "promise") {
|
|
170
|
+
subscription.suppressUnhandledRejection();
|
|
171
|
+
subscription.reject(error);
|
|
172
|
+
} else this.unsubscribe(subscription);
|
|
163
173
|
}
|
|
164
174
|
unsubscribe(subscription) {
|
|
165
175
|
if (!subscription.active) return;
|
|
@@ -387,7 +397,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
|
|
|
387
397
|
stderrSink;
|
|
388
398
|
stderrSinkActive = true;
|
|
389
399
|
stderrSinkCleanup = null;
|
|
390
|
-
|
|
400
|
+
shutdownPromise = null;
|
|
391
401
|
shutdownRequested = false;
|
|
392
402
|
restartDisabled = false;
|
|
393
403
|
finalized = false;
|
|
@@ -466,9 +476,12 @@ var ProcbandProcessImpl = class extends EventEmitter {
|
|
|
466
476
|
return this.finalPromise.then(onfulfilled, onrejected);
|
|
467
477
|
}
|
|
468
478
|
kill(signal) {
|
|
469
|
-
|
|
470
|
-
if (!
|
|
471
|
-
|
|
479
|
+
if (signal === 0) return this.currentChild?.kill(0) ?? false;
|
|
480
|
+
if (!this.attempt || this.finalized) return false;
|
|
481
|
+
this.beginShutdown(signal ?? "SIGTERM", 5e3).catch((error) => {
|
|
482
|
+
this.emit("error", error);
|
|
483
|
+
});
|
|
484
|
+
return true;
|
|
472
485
|
}
|
|
473
486
|
ref() {
|
|
474
487
|
this.currentChild?.ref();
|
|
@@ -481,26 +494,24 @@ var ProcbandProcessImpl = class extends EventEmitter {
|
|
|
481
494
|
disconnect() {
|
|
482
495
|
this.currentChild?.disconnect();
|
|
483
496
|
}
|
|
484
|
-
|
|
485
|
-
if (this.stopPromise) return this.stopPromise;
|
|
497
|
+
prepareShutdown() {
|
|
486
498
|
this.shutdownRequested = true;
|
|
487
499
|
this.restartDisabled = true;
|
|
488
500
|
this.restart.cancelDelay();
|
|
489
|
-
|
|
490
|
-
|
|
501
|
+
}
|
|
502
|
+
beginShutdown(signal, killAfterMs) {
|
|
503
|
+
this.prepareShutdown();
|
|
504
|
+
if (this.shutdownPromise) return this.shutdownPromise;
|
|
505
|
+
this.shutdownPromise = this.stopActiveTree(signal, killAfterMs);
|
|
506
|
+
return this.shutdownPromise;
|
|
491
507
|
}
|
|
492
508
|
cleanupFromExit() {
|
|
493
|
-
this.
|
|
494
|
-
this.restartDisabled = true;
|
|
495
|
-
this.restart.cancelDelay();
|
|
509
|
+
this.prepareShutdown();
|
|
496
510
|
const child = this.currentChild;
|
|
497
511
|
if (child) killTreeBestEffort(child, this.getParentCleanupSignal());
|
|
498
512
|
}
|
|
499
513
|
async cleanupFromSignal(signal) {
|
|
500
|
-
this.
|
|
501
|
-
this.restartDisabled = true;
|
|
502
|
-
this.restart.cancelDelay();
|
|
503
|
-
await this.stopActiveTree(this.getParentCleanupSignal(signal), 1e3);
|
|
514
|
+
await this.beginShutdown(this.getParentCleanupSignal(signal), 1e3);
|
|
504
515
|
}
|
|
505
516
|
async stopActiveTree(signal, killAfterMs) {
|
|
506
517
|
const attempt = this.attempt;
|
|
@@ -517,6 +528,7 @@ var ProcbandProcessImpl = class extends EventEmitter {
|
|
|
517
528
|
this.generation += 1;
|
|
518
529
|
const child = spawn(this.config.command, this.config.args ?? [], {
|
|
519
530
|
cwd: this.config.cwd,
|
|
531
|
+
detached: this.config.detached,
|
|
520
532
|
env: this.config.env,
|
|
521
533
|
stdio: [
|
|
522
534
|
this.config.stdin ? "pipe" : "ignore",
|
|
@@ -637,11 +649,16 @@ var ProcbandProcessImpl = class extends EventEmitter {
|
|
|
637
649
|
liveProcesses.delete(this);
|
|
638
650
|
if (liveProcesses.size === 0) propagatedFailure = false;
|
|
639
651
|
if (this.shouldPropagateFailure(result)) propagateFailure(result.exitCode);
|
|
640
|
-
this.
|
|
652
|
+
this.closeMatches();
|
|
641
653
|
this.unbindStderrSink();
|
|
642
654
|
unregisterCleanupTarget(this);
|
|
643
655
|
this.finalResolve(result);
|
|
644
656
|
}
|
|
657
|
+
closeMatches() {
|
|
658
|
+
const error = /* @__PURE__ */ new Error(`Process "${this.name}" exited before a matching line was observed`);
|
|
659
|
+
if (this.matches.hasPendingWait()) writePrefixedLine(process.stderr, this.label, stderrColor, error.message, true);
|
|
660
|
+
this.matches.close(error);
|
|
661
|
+
}
|
|
645
662
|
markTerminalResultObserved() {
|
|
646
663
|
this.terminalResultObserved = true;
|
|
647
664
|
}
|
|
@@ -720,7 +737,7 @@ function propagateFailure(exitCode) {
|
|
|
720
737
|
if (propagatedFailure) return;
|
|
721
738
|
propagatedFailure = true;
|
|
722
739
|
if (process.exitCode == null || process.exitCode === 0) process.exitCode = exitCode;
|
|
723
|
-
for (const proc of liveProcesses) proc.
|
|
740
|
+
for (const proc of liveProcesses) proc.kill();
|
|
724
741
|
}
|
|
725
742
|
//#endregion
|
|
726
743
|
export { supervise };
|
package/docs/context.md
CHANGED
|
@@ -36,8 +36,8 @@ Supervision adds five behaviors on top of raw `spawn()`:
|
|
|
36
36
|
|
|
37
37
|
- `ProcessConfig`
|
|
38
38
|
Declares one supervised subprocess plus its label, color, restart policy,
|
|
39
|
-
optional stdin behavior, optional
|
|
40
|
-
raw `stderr` tee.
|
|
39
|
+
optional stdin behavior, optional detached spawn mode, optional
|
|
40
|
+
parent-exit signal override, and optional raw `stderr` tee.
|
|
41
41
|
- `ProcbandProcess`
|
|
42
42
|
The live wrapper returned by `supervise()`. It is a `ChildProcess`-compatible
|
|
43
43
|
handle, a matching surface, a shutdown surface, and a thenable final result.
|
|
@@ -73,7 +73,7 @@ Supervision adds five behaviors on top of raw `spawn()`:
|
|
|
73
73
|
- React to repeated matching output:
|
|
74
74
|
`proc.match(pattern, callback, options)`
|
|
75
75
|
- Stop the process and its descendants:
|
|
76
|
-
`proc.
|
|
76
|
+
`proc.kill()`
|
|
77
77
|
- Inspect final exit state:
|
|
78
78
|
`await proc` or `await proc.wait()`
|
|
79
79
|
- Take ownership of a process failure:
|
|
@@ -95,7 +95,7 @@ Supervision adds five behaviors on top of raw `spawn()`:
|
|
|
95
95
|
|
|
96
96
|
# Recommended Patterns
|
|
97
97
|
|
|
98
|
-
- Use `proc.
|
|
98
|
+
- Use `proc.kill()` for deliberate shutdown initiated by your own script.
|
|
99
99
|
- Reserve `parentExitSignal` for children that expect a specific signal from
|
|
100
100
|
their supervisor during parent-driven cleanup.
|
|
101
101
|
- Await `proc` or call `proc.wait()` when your script intends to own failure
|
|
@@ -103,11 +103,10 @@ Supervision adds five behaviors on top of raw `spawn()`:
|
|
|
103
103
|
|
|
104
104
|
# Patterns to Avoid
|
|
105
105
|
|
|
106
|
-
- Do not treat `parentExitSignal` as a replacement for
|
|
107
|
-
The former only changes parent-driven cleanup
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
running. `kill()` only targets the current direct child.
|
|
106
|
+
- Do not treat `parentExitSignal` as a replacement for the signal passed to
|
|
107
|
+
`proc.kill(signal)`. The former only changes parent-driven cleanup.
|
|
108
|
+
- Do not expect `kill(0)` to stop supervision. `kill(0)` remains an existence
|
|
109
|
+
check for the current child attempt.
|
|
111
110
|
- Do not expect historical log replay from `match()` or `waitFor()`. Matching
|
|
112
111
|
is future-only by design.
|
|
113
112
|
|
|
@@ -131,12 +130,12 @@ Supervision adds five behaviors on top of raw `spawn()`:
|
|
|
131
130
|
- The wrapper survives restarts, but inherited `pid`, `stdin`, `stdout`,
|
|
132
131
|
`stderr`, and related `ChildProcess` fields always refer to the current active
|
|
133
132
|
child attempt. `stdin` is `null` unless `ProcessConfig.stdin` is enabled.
|
|
134
|
-
- `kill()`
|
|
135
|
-
|
|
133
|
+
- `kill()` disables future restarts and kills the full process tree, except for
|
|
134
|
+
`kill(0)`, which only checks whether the current child attempt exists.
|
|
136
135
|
- Parent cleanup installs both `SIGINT` and `SIGTERM` handlers while any live
|
|
137
136
|
supervised process exists. Set `ProcessConfig.parentExitSignal` to override
|
|
138
137
|
which signal is sent to the child tree during parent-driven cleanup. This
|
|
139
|
-
does not change the signal used by explicit `proc.
|
|
138
|
+
does not change the signal used by explicit `proc.kill()` calls.
|
|
140
139
|
- `stderr` prefixes always use the reserved red, even when a custom process
|
|
141
140
|
color is configured.
|
|
142
141
|
- `ProcessConfig.name` is optional. When omitted, it falls back to the
|
|
@@ -153,7 +152,7 @@ Supervision adds five behaviors on top of raw `spawn()`:
|
|
|
153
152
|
supervision.
|
|
154
153
|
- Unobserved terminal failures do not reject promises. They set the parent
|
|
155
154
|
`process.exitCode` and start stopping sibling `procband` processes.
|
|
156
|
-
- `
|
|
155
|
+
- `kill()` emits `error` if tree-kill fails with a non-`ESRCH` error.
|
|
157
156
|
|
|
158
157
|
# Terminology
|
|
159
158
|
|
package/package.json
CHANGED