@cat-factory/cli 0.8.6 → 0.9.0

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 @@
1
+ {"version":3,"file":"supervise-k3s.js","sourceRoot":"","sources":["../src/supervise-k3s.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Effects half of `cat-factory supervise`: the health probe, the optional container dependency,
3
+ * the port reaper, the supervised child, and the loop that drives them from the pure decisions in
4
+ * `supervise.ts`.
5
+ *
6
+ * Everything the loop touches is behind a seam so `runSupervisor` can be driven by fakes — the
7
+ * same discipline `host-shell.ts` sets out for the k3s flow. Shell-outs go through {@link HostShell}
8
+ * rather than `node:child_process` directly; the one exception is the supervised child itself,
9
+ * which needs inherited stdio and a live handle, so it gets its own {@link ChildLauncher} seam.
10
+ */
11
+ import { type HostShell } from './host-shell.js';
12
+ import { type SuperviseConfig } from './supervise.js';
13
+ /** Probes whether the supervised service is actually SERVING (not merely booted). */
14
+ export interface HealthProbe {
15
+ serving(): Promise<boolean>;
16
+ }
17
+ /** A prerequisite the supervised service needs — a database container, a local cluster, … */
18
+ export interface ServiceDependency {
19
+ /** Human-readable name for logs. */
20
+ readonly label: string;
21
+ /**
22
+ * Bring it up if needed; resolve `true` once it is ready, `false` to retry on the next cycle.
23
+ * Throw {@link OperatorActionRequiredError} for a state no retry can clear.
24
+ */
25
+ ensure(): Promise<boolean>;
26
+ }
27
+ /**
28
+ * Thrown by a dependency that cannot be repaired without a human. The loop prints the message ONCE
29
+ * and stops treating the step as retryable noise — the alternative is a watchdog that repeats a
30
+ * hopeless action forever, which is the exact pathology (a restart loop that reads as progress)
31
+ * this supervisor exists to end.
32
+ */
33
+ export declare class OperatorActionRequiredError extends Error {
34
+ }
35
+ /** A running supervised child process. */
36
+ export interface SupervisedChild {
37
+ readonly pid: number | undefined;
38
+ /** Kill the child AND its descendants. Never throws. */
39
+ kill(): Promise<void>;
40
+ /** Resolves when the process exits, with its code/signal. */
41
+ readonly exited: Promise<{
42
+ code: number | null;
43
+ signal: string | null;
44
+ }>;
45
+ }
46
+ /** Starts the supervised command. */
47
+ export interface ChildLauncher {
48
+ start(): SupervisedChild;
49
+ }
50
+ /** Frees a port held by an orphaned listener, so a restart can bind it again. */
51
+ export interface PortReaper {
52
+ /** Resolves with the PIDs actually killed (empty when the port was already free). */
53
+ reap(): Promise<string[]>;
54
+ }
55
+ /** Injectable clock, so tests never wait in real time. */
56
+ export interface SuperviseClock {
57
+ now(): number;
58
+ /** Resolves after `ms`, or EARLY if `signal` aborts — so Ctrl-C isn't stuck behind a poll interval. */
59
+ sleep(ms: number, signal?: AbortSignal): Promise<void>;
60
+ }
61
+ /**
62
+ * NOTE the deliberately un-`unref`'d timers throughout this module. An `unref`'d timer does not keep
63
+ * the event loop alive, and while a spawned child DOES hold it open, that reference vanishes the
64
+ * moment the child dies — which is precisely when the supervisor must keep running. With `unref` the
65
+ * poll timer was then the only thing left, so Node exited 0 the instant its child was killed: a
66
+ * watchdog that died with its patient, silently and with a success code.
67
+ */
68
+ export declare const systemClock: SuperviseClock;
69
+ /**
70
+ * The real probe. "Serving" requires BOTH signals, because the two failure modes differ: a parked
71
+ * `node --watch` leaves nothing bound to the port, while a server that booted but wedged (or lost
72
+ * its DB pool) still holds the socket and only fails the HTTP check.
73
+ *
74
+ * Both address families are tried — a Node server on `0.0.0.0` answers on 127.0.0.1, but some dev
75
+ * servers bind IPv6 `::1` only, and probing one family would report a false outage.
76
+ */
77
+ export declare function createHealthProbe(opts: {
78
+ port: number;
79
+ healthPath: string;
80
+ }): HealthProbe;
81
+ /**
82
+ * A `docker compose` service the supervised process needs. This is the piece that makes recovery
83
+ * work after the container engine itself restarted: the example compose files set no restart
84
+ * policy on Postgres, so anything that stops the engine leaves the DB down, and relaunching the
85
+ * server against a missing (or still-initialising) database just crashes it again in `migrate`.
86
+ */
87
+ export declare function createComposeDependency(shell: HostShell, opts: {
88
+ /**
89
+ * Directory holding the `docker-compose.yml`. Passed as the shell-out's `cwd` on EVERY compose
90
+ * call, never merely stored: compose resolves its project file relative to the working
91
+ * directory, so a supervisor started from anywhere else would address no project at all and
92
+ * report a permanently un-ready database instead of restoring it.
93
+ */
94
+ dir: string;
95
+ service: string;
96
+ /** Overridable so tests don't wait out the real readiness budget. */
97
+ readyTimeoutMs?: number;
98
+ readyPollMs?: number;
99
+ }): ServiceDependency;
100
+ /**
101
+ * Frees the port before a restart. Killing the child tree usually suffices, but not always: a
102
+ * package-manager wrapper that is killed without its subtree leaves the real `node` orphaned and
103
+ * still holding the socket, and the relaunch then dies with `EADDRINUSE` — turning one outage into
104
+ * a restart loop. Reaping by PORT is the only check that covers an orphan we never had a handle on.
105
+ *
106
+ * It is also, unavoidably, the bluntest thing this supervisor does: reaping by port means SIGKILLing
107
+ * a process we were never handed. If `--port` names a port some unrelated service owns, that service
108
+ * is what dies. There is no portable way to prove descent from our own child, so the mitigation is
109
+ * disclosure rather than detection — every kill NAMES the pid and, where the platform will tell us,
110
+ * the command behind it, and `reap()` reports what it killed so the caller can log it. Callers only
111
+ * ever reap AFTER their own child is confirmed dead, so a healthy stack is never a candidate.
112
+ */
113
+ export declare function createPortReaper(shell: HostShell, port: number, opts?: {
114
+ platform?: string;
115
+ log?: (message: string) => void;
116
+ }): PortReaper;
117
+ /**
118
+ * Launches the supervised command through a shell, with stdio inherited so its logs stay visible —
119
+ * the supervisor is meant to be a transparent wrapper, not a log proxy.
120
+ *
121
+ * The command is passed as ONE string with `shell: true` (rather than a command plus an args array,
122
+ * which trips Node's DEP0190) so a package-manager entry point resolves through its Windows `.cmd`
123
+ * shim. On POSIX the child gets its own process group, so killing it takes the whole tree.
124
+ */
125
+ export declare function createChildLauncher(opts: {
126
+ command: string;
127
+ cwd: string;
128
+ }): ChildLauncher;
129
+ /** Everything `runSupervisor` needs. Every field is a seam; only `launcher` and `probe` are required. */
130
+ export interface SupervisorDeps {
131
+ config: SuperviseConfig;
132
+ probe: HealthProbe;
133
+ launcher: ChildLauncher;
134
+ /** Checked in order before each restart — e.g. the database, then the local cluster. */
135
+ dependencies?: ServiceDependency[];
136
+ reaper?: PortReaper;
137
+ clock?: SuperviseClock;
138
+ log?: (message: string) => void;
139
+ /**
140
+ * Aborting stops the loop and, before returning, kills the supervised child and reaps the port.
141
+ * The loop OWNS the child handle, so shutdown has to live here: a signal handler outside it can
142
+ * only reach the port, which on POSIX kills the inner listener while leaving the package-manager
143
+ * wrapper and its `node --watch` alive and parked — a Ctrl-C that orphans exactly the process tree
144
+ * this command exists to manage.
145
+ */
146
+ stopSignal?: AbortSignal;
147
+ /** Stop after this many ticks — tests only; production runs until the process is signalled. */
148
+ maxTicks?: number;
149
+ }
150
+ /** How a finished supervisor run turned out. Useful for tests and for the command's exit code. */
151
+ export interface SupervisorOutcome {
152
+ ticks: number;
153
+ repairs: number;
154
+ /** Dependencies that reported a state only an operator can clear, by label. */
155
+ blocked: string[];
156
+ /**
157
+ * Set when the supervisor STOPPED trying, with the reason. Restarting cannot fix a command that
158
+ * is simply broken, so `maxFailedStarts` consecutive restarts that never reached a serving state
159
+ * end the loop and report — the caller turns this into a non-zero exit.
160
+ */
161
+ gaveUp?: string;
162
+ }
163
+ /**
164
+ * Run the supervision loop: start the child, then probe on an interval and repair when the
165
+ * decisions in `supervise.ts` say so. Returns when `stopSignal` aborts, when the crash-loop budget
166
+ * is spent, or when `maxTicks` is reached (tests) — in production, otherwise never.
167
+ */
168
+ export declare function runSupervisor(deps: SupervisorDeps): Promise<SupervisorOutcome>;
169
+ //# sourceMappingURL=supervise-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervise-runtime.d.ts","sourceRoot":"","sources":["../src/supervise-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACnE,OAAO,EAAgB,KAAK,eAAe,EAAyB,MAAM,gBAAgB,CAAA;AAE1F,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5B;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAC3B;AAED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;CAAG;AAEzD,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,wDAAwD;IACxD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;CACzE;AAED,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,eAAe,CAAA;CACzB;AAED,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,qFAAqF;IACrF,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAC1B;AAED,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,GAAG,IAAI,MAAM,CAAA;IACb,uGAAuG;IACvG,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,cAoBzB,CAAA;AAKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAuCzF;AAKD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE;IACJ;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GACA,iBAAiB,CAqCnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAChE,UAAU,CAyDZ;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CA8CzF;AAED,yGAAyG;AACzG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,eAAe,CAAA;IACvB,KAAK,EAAE,WAAW,CAAA;IAClB,QAAQ,EAAE,aAAa,CAAA;IACvB,wFAAwF;IACxF,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAClC,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,WAAW,CAAA;IACxB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,+EAA+E;IAC/E,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAoCD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA6GpF"}
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Effects half of `cat-factory supervise`: the health probe, the optional container dependency,
3
+ * the port reaper, the supervised child, and the loop that drives them from the pure decisions in
4
+ * `supervise.ts`.
5
+ *
6
+ * Everything the loop touches is behind a seam so `runSupervisor` can be driven by fakes — the
7
+ * same discipline `host-shell.ts` sets out for the k3s flow. Shell-outs go through {@link HostShell}
8
+ * rather than `node:child_process` directly; the one exception is the supervised child itself,
9
+ * which needs inherited stdio and a live handle, so it gets its own {@link ChildLauncher} seam.
10
+ */
11
+ import { spawn } from 'node:child_process';
12
+ import http from 'node:http';
13
+ import net from 'node:net';
14
+ import { COMMAND_NOT_FOUND } from './host-shell.js';
15
+ import { initialState, stateAfterStart, step } from './supervise.js';
16
+ /**
17
+ * Thrown by a dependency that cannot be repaired without a human. The loop prints the message ONCE
18
+ * and stops treating the step as retryable noise — the alternative is a watchdog that repeats a
19
+ * hopeless action forever, which is the exact pathology (a restart loop that reads as progress)
20
+ * this supervisor exists to end.
21
+ */
22
+ export class OperatorActionRequiredError extends Error {
23
+ }
24
+ /**
25
+ * NOTE the deliberately un-`unref`'d timers throughout this module. An `unref`'d timer does not keep
26
+ * the event loop alive, and while a spawned child DOES hold it open, that reference vanishes the
27
+ * moment the child dies — which is precisely when the supervisor must keep running. With `unref` the
28
+ * poll timer was then the only thing left, so Node exited 0 the instant its child was killed: a
29
+ * watchdog that died with its patient, silently and with a success code.
30
+ */
31
+ export const systemClock = {
32
+ now: () => Date.now(),
33
+ sleep: (ms, signal) => new Promise((resolve) => {
34
+ if (signal?.aborted === true) {
35
+ resolve();
36
+ return;
37
+ }
38
+ const timer = setTimeout(() => {
39
+ signal?.removeEventListener('abort', onAbort);
40
+ resolve();
41
+ }, ms);
42
+ // Without this a Ctrl-C would sit out the remainder of the poll interval before the loop
43
+ // noticed, so the shutdown that is supposed to reap the child takes up to `--poll` seconds.
44
+ function onAbort() {
45
+ clearTimeout(timer);
46
+ resolve();
47
+ }
48
+ signal?.addEventListener('abort', onAbort, { once: true });
49
+ }),
50
+ };
51
+ const PROBE_CONNECT_TIMEOUT_MS = 2_000;
52
+ const PROBE_HTTP_TIMEOUT_MS = 3_000;
53
+ /**
54
+ * The real probe. "Serving" requires BOTH signals, because the two failure modes differ: a parked
55
+ * `node --watch` leaves nothing bound to the port, while a server that booted but wedged (or lost
56
+ * its DB pool) still holds the socket and only fails the HTTP check.
57
+ *
58
+ * Both address families are tried — a Node server on `0.0.0.0` answers on 127.0.0.1, but some dev
59
+ * servers bind IPv6 `::1` only, and probing one family would report a false outage.
60
+ */
61
+ export function createHealthProbe(opts) {
62
+ const hosts = ['127.0.0.1', '::1'];
63
+ const connect = (host) => new Promise((resolve) => {
64
+ const socket = net.connect({ host, port: opts.port, timeout: PROBE_CONNECT_TIMEOUT_MS });
65
+ const done = (result) => {
66
+ socket.destroy();
67
+ resolve(result);
68
+ };
69
+ socket.once('connect', () => done(true));
70
+ socket.once('error', () => done(false));
71
+ socket.once('timeout', () => done(false));
72
+ });
73
+ const healthy = (host) => new Promise((resolve) => {
74
+ const req = http.get({ host, port: opts.port, path: opts.healthPath, timeout: PROBE_HTTP_TIMEOUT_MS }, (res) => {
75
+ res.resume(); // drain, so the socket can close
76
+ resolve(res.statusCode === 200);
77
+ });
78
+ req.once('error', () => resolve(false));
79
+ req.once('timeout', () => {
80
+ req.destroy();
81
+ resolve(false);
82
+ });
83
+ });
84
+ return {
85
+ async serving() {
86
+ const listening = await Promise.all(hosts.map(connect));
87
+ if (!listening.some(Boolean))
88
+ return false;
89
+ const answers = await Promise.all(hosts.map(healthy));
90
+ return answers.some(Boolean);
91
+ },
92
+ };
93
+ }
94
+ const COMPOSE_READY_TIMEOUT_MS = 90_000;
95
+ const COMPOSE_POLL_MS = 2_000;
96
+ /**
97
+ * A `docker compose` service the supervised process needs. This is the piece that makes recovery
98
+ * work after the container engine itself restarted: the example compose files set no restart
99
+ * policy on Postgres, so anything that stops the engine leaves the DB down, and relaunching the
100
+ * server against a missing (or still-initialising) database just crashes it again in `migrate`.
101
+ */
102
+ export function createComposeDependency(shell, opts) {
103
+ const readyTimeoutMs = opts.readyTimeoutMs ?? COMPOSE_READY_TIMEOUT_MS;
104
+ const readyPollMs = opts.readyPollMs ?? COMPOSE_POLL_MS;
105
+ const inspectReady = async (id) => {
106
+ const format = '{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}';
107
+ const result = await shell.run('docker', ['inspect', id, '--format', format], { cwd: opts.dir });
108
+ if (result.code !== 0)
109
+ return false;
110
+ const [status, health] = result.stdout.trim().split('|');
111
+ // A service with no healthcheck configured can only be judged by `running`.
112
+ return status === 'running' && (health === 'healthy' || health === 'none');
113
+ };
114
+ return {
115
+ label: `${opts.service} (docker compose)`,
116
+ async ensure() {
117
+ const up = await shell.run('docker', ['compose', 'up', '-d', opts.service], {
118
+ timeoutMs: 60_000,
119
+ cwd: opts.dir,
120
+ });
121
+ if (up.code !== 0)
122
+ return false;
123
+ const deadline = Date.now() + readyTimeoutMs;
124
+ while (Date.now() < deadline) {
125
+ const ps = await shell.run('docker', ['compose', 'ps', '-q', opts.service], {
126
+ cwd: opts.dir,
127
+ });
128
+ const id = ps.stdout.trim().split(/\r?\n/).filter(Boolean)[0];
129
+ if (id && (await inspectReady(id)))
130
+ return true;
131
+ await new Promise((resolve) => {
132
+ setTimeout(resolve, readyPollMs);
133
+ });
134
+ }
135
+ return false;
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * Frees the port before a restart. Killing the child tree usually suffices, but not always: a
141
+ * package-manager wrapper that is killed without its subtree leaves the real `node` orphaned and
142
+ * still holding the socket, and the relaunch then dies with `EADDRINUSE` — turning one outage into
143
+ * a restart loop. Reaping by PORT is the only check that covers an orphan we never had a handle on.
144
+ *
145
+ * It is also, unavoidably, the bluntest thing this supervisor does: reaping by port means SIGKILLing
146
+ * a process we were never handed. If `--port` names a port some unrelated service owns, that service
147
+ * is what dies. There is no portable way to prove descent from our own child, so the mitigation is
148
+ * disclosure rather than detection — every kill NAMES the pid and, where the platform will tell us,
149
+ * the command behind it, and `reap()` reports what it killed so the caller can log it. Callers only
150
+ * ever reap AFTER their own child is confirmed dead, so a healthy stack is never a candidate.
151
+ */
152
+ export function createPortReaper(shell, port, opts = {}) {
153
+ const isWindows = (opts.platform ?? process.platform) === 'win32';
154
+ const log = opts.log ?? (() => { });
155
+ /** Best-effort "what IS this pid", so a surprising kill is at least explicable after the fact. */
156
+ const describe = async (pid) => {
157
+ const result = isWindows
158
+ ? await shell.run('tasklist', ['/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'])
159
+ : await shell.run('ps', ['-p', pid, '-o', 'command=']);
160
+ if (result.code !== 0)
161
+ return `pid ${pid}`;
162
+ const text = result.stdout.trim().split(/\r?\n/)[0]?.trim();
163
+ return text ? `pid ${pid} (${text})` : `pid ${pid}`;
164
+ };
165
+ const listenerPids = async () => {
166
+ if (isWindows) {
167
+ // Plain `netstat -ano` (not `-p tcp`, which is IPv4-only) so an IPv6-only listener is seen.
168
+ const result = await shell.run('netstat', ['-ano']);
169
+ if (result.code !== 0) {
170
+ log(`⚠ cannot check port ${port}: netstat failed — an orphaned listener will not be reaped`);
171
+ return [];
172
+ }
173
+ const pids = new Set();
174
+ for (const line of result.stdout.split(/\r?\n/)) {
175
+ const match = line.match(/^\s*TCP\s+\S+:(\d+)\s+\S+\s+LISTENING\s+(\d+)/i);
176
+ if (match && Number(match[1]) === port)
177
+ pids.add(match[2]);
178
+ }
179
+ return [...pids];
180
+ }
181
+ const result = await shell.run('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN']);
182
+ // `lsof` exits 1 for "nothing matched", which is the common case and not worth a word. A MISSING
183
+ // lsof is different: it is not installed by default on many Linux images, so the reaper silently
184
+ // becomes a no-op and the EADDRINUSE restart loop it exists to prevent comes back unexplained.
185
+ if (result.code === COMMAND_NOT_FOUND) {
186
+ log(`⚠ cannot check port ${port}: lsof is not installed — an orphaned listener holding the ` +
187
+ 'port will not be reaped, so a restart may fail with EADDRINUSE');
188
+ return [];
189
+ }
190
+ if (result.code !== 0)
191
+ return [];
192
+ return [...new Set(result.stdout.split(/\s+/).filter(Boolean))];
193
+ };
194
+ return {
195
+ async reap() {
196
+ const killed = [];
197
+ for (const pid of await listenerPids()) {
198
+ log(`↯ port ${port} is still held by ${await describe(pid)} — killing it`);
199
+ // `/T` (Windows) kills the descendants too; elsewhere the group is signalled by the caller.
200
+ if (isWindows)
201
+ await shell.run('taskkill', ['/PID', pid, '/F', '/T']);
202
+ else
203
+ await shell.run('kill', ['-9', pid]);
204
+ killed.push(pid);
205
+ }
206
+ return killed;
207
+ },
208
+ };
209
+ }
210
+ /**
211
+ * Launches the supervised command through a shell, with stdio inherited so its logs stay visible —
212
+ * the supervisor is meant to be a transparent wrapper, not a log proxy.
213
+ *
214
+ * The command is passed as ONE string with `shell: true` (rather than a command plus an args array,
215
+ * which trips Node's DEP0190) so a package-manager entry point resolves through its Windows `.cmd`
216
+ * shim. On POSIX the child gets its own process group, so killing it takes the whole tree.
217
+ */
218
+ export function createChildLauncher(opts) {
219
+ const isWindows = process.platform === 'win32';
220
+ return {
221
+ start() {
222
+ const child = spawn(opts.command, {
223
+ cwd: opts.cwd,
224
+ stdio: 'inherit',
225
+ shell: true,
226
+ detached: !isWindows,
227
+ });
228
+ const exited = new Promise((resolve) => {
229
+ child.once('exit', (code, signal) => resolve({ code, signal }));
230
+ child.once('error', () => resolve({ code: null, signal: null }));
231
+ });
232
+ return {
233
+ pid: child.pid,
234
+ exited,
235
+ async kill() {
236
+ if (child.pid === undefined || child.exitCode !== null)
237
+ return;
238
+ try {
239
+ if (isWindows) {
240
+ // No POSIX process groups on Windows; `taskkill /T` is what reaches the subtree.
241
+ spawn('taskkill', ['/PID', String(child.pid), '/F', '/T'], {
242
+ stdio: 'ignore',
243
+ }).unref();
244
+ }
245
+ else {
246
+ process.kill(-child.pid, 'SIGKILL');
247
+ }
248
+ }
249
+ catch (err) {
250
+ // silent-catch-ok: the child had already exited between the guard above and the signal
251
+ // (ESRCH/EPERM). That is the outcome kill() is asking for, so there is nothing to report.
252
+ void err;
253
+ }
254
+ await Promise.race([
255
+ exited,
256
+ new Promise((resolve) => {
257
+ setTimeout(resolve, 2_000);
258
+ }),
259
+ ]);
260
+ },
261
+ };
262
+ },
263
+ };
264
+ }
265
+ /**
266
+ * Run every dependency's `ensure`, logging each outcome. Returns the labels of any that need
267
+ * operator action, whose guidance is printed only the first time so a long run doesn't bury it in
268
+ * repeats. A blocked dependency does NOT abort the ladder: the supervised process is often still
269
+ * worth restarting (a dead cluster breaks environment provisioning, not the whole backend).
270
+ */
271
+ async function ensureDependencies(dependencies, log, warned) {
272
+ const blocked = [];
273
+ for (const dependency of dependencies) {
274
+ try {
275
+ const ready = await dependency.ensure();
276
+ log(ready
277
+ ? `✔ ${dependency.label} is ready`
278
+ : `✖ ${dependency.label} is not ready — will retry next cycle`);
279
+ }
280
+ catch (err) {
281
+ if (!(err instanceof OperatorActionRequiredError))
282
+ throw err;
283
+ blocked.push(dependency.label);
284
+ if (!warned.has(dependency.label)) {
285
+ warned.add(dependency.label);
286
+ log(`✖ ${dependency.label} NEEDS YOU: ${err.message}`);
287
+ }
288
+ }
289
+ }
290
+ return blocked;
291
+ }
292
+ const RESTART_SETTLE_MS = 1_500;
293
+ /**
294
+ * Run the supervision loop: start the child, then probe on an interval and repair when the
295
+ * decisions in `supervise.ts` say so. Returns when `stopSignal` aborts, when the crash-loop budget
296
+ * is spent, or when `maxTicks` is reached (tests) — in production, otherwise never.
297
+ */
298
+ export async function runSupervisor(deps) {
299
+ const clock = deps.clock ?? systemClock;
300
+ const log = deps.log ?? ((message) => process.stdout.write(`${message}\n`));
301
+ const { config, stopSignal } = deps;
302
+ let repairs = 0;
303
+ let ticks = 0;
304
+ let blocked = [];
305
+ let gaveUp;
306
+ const warned = new Set();
307
+ // A child that has exited is a fact the probe can only infer, slowly. Tracked per generation so a
308
+ // dead PREDECESSOR's late `exited` can never be read as the current child having died.
309
+ let generation = 0;
310
+ let childExited = false;
311
+ const startChild = () => {
312
+ const mine = ++generation;
313
+ childExited = false;
314
+ const started = deps.launcher.start();
315
+ void started.exited.then(() => {
316
+ if (mine === generation)
317
+ childExited = true;
318
+ });
319
+ return started;
320
+ };
321
+ let child = startChild();
322
+ let state = initialState(clock.now(), config);
323
+ // Restarts that have not yet produced a serving stack. Reset by any successful probe, so this
324
+ // counts a genuine crash loop rather than a long-lived stack that has been repaired often.
325
+ let failedStarts = 0;
326
+ const restart = async () => {
327
+ await child.kill();
328
+ await clock.sleep(RESTART_SETTLE_MS);
329
+ // Reap AFTER killing the tree: this only catches an orphan the tree kill could not reach. Our
330
+ // own child is dead by now, so anything still on the port is by definition not it.
331
+ if (deps.reaper)
332
+ await deps.reaper.reap();
333
+ child = startChild();
334
+ state = stateAfterStart(clock.now(), config);
335
+ };
336
+ // Read through a function, not inline: `signal.aborted` flips underneath us, and a direct
337
+ // comparison in the loop condition would let the compiler narrow it to `false` for the body.
338
+ const stopRequested = () => stopSignal?.aborted === true;
339
+ while (!stopRequested() &&
340
+ gaveUp === undefined &&
341
+ (deps.maxTicks === undefined || ticks < deps.maxTicks)) {
342
+ await clock.sleep(config.pollMs, stopSignal);
343
+ if (stopRequested())
344
+ break;
345
+ ticks += 1;
346
+ // Sampled BEFORE the probe: the probe can take seconds against a filtered port, and folding
347
+ // that into the drift would read as a suspend (see `clockJumpMs`).
348
+ const now = clock.now();
349
+ const serving = await deps.probe.serving();
350
+ if (serving)
351
+ failedStarts = 0;
352
+ const next = step(state, { now, serving, childExited }, config);
353
+ state = next.state;
354
+ const { action } = next;
355
+ switch (action.kind) {
356
+ case 'serving':
357
+ case 'grace':
358
+ break;
359
+ case 'resumed':
360
+ log(`✔ resumed after ${Math.round(action.driftMs / 1000)}s — the stack is still serving`);
361
+ break;
362
+ case 'recovered':
363
+ log(`✔ serving again (after ${action.afterFailures} failed probe(s))`);
364
+ break;
365
+ case 'counting':
366
+ log(`• health probe failed (${action.failures}/${action.threshold})`);
367
+ break;
368
+ case 'repair': {
369
+ repairs += 1;
370
+ failedStarts += 1;
371
+ log(`⚠ not serving — ${action.reason}; repair #${repairs}`);
372
+ if (failedStarts > config.maxFailedStarts) {
373
+ // Reported, not retried — the same rule the wedged-cgroup path follows. A command that has
374
+ // never once served is not going to start serving because we killed it again.
375
+ gaveUp =
376
+ `the supervised command has failed to serve ${failedStarts} starts in a row. ` +
377
+ 'Restarting cannot fix a command that is broken — run it directly to see why ' +
378
+ '(in deploy/local: `pnpm dev:raw`).';
379
+ log(`✖ GIVING UP: ${gaveUp}`);
380
+ break;
381
+ }
382
+ if (deps.dependencies?.length) {
383
+ blocked = await ensureDependencies(deps.dependencies, log, warned);
384
+ }
385
+ await restart();
386
+ log('↻ restarted the supervised command');
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ // Shutdown is the loop's job because the loop owns the child handle.
392
+ await child.kill();
393
+ if (deps.reaper) {
394
+ const killed = await deps.reaper.reap();
395
+ if (killed.length > 0)
396
+ log(`↯ reaped ${killed.length} orphaned listener(s) on shutdown`);
397
+ }
398
+ return { ticks, repairs, blocked, gaveUp };
399
+ }
400
+ //# sourceMappingURL=supervise-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervise-runtime.js","sourceRoot":"","sources":["../src/supervise-runtime.ts"],"names":[],"mappings":"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"}