stack/packages/stack-cli/src/lib/child-process.ts
Bilal Godil a29d93ed92 refactor(cli): address review feedback on auto-update
Correctness:
- killLocalDashboard now waits for the process to actually exit (not just
  for /health to stop answering) before returning, so the replacement
  dashboard doesn't hit EADDRINUSE while the old socket is still held.
- Bail out without waiting/SIGKILL when the recorded pid is gone (ESRCH) or
  owned by another process (EPERM, i.e. pid was recycled), avoiding a hang
  and reducing the chance of signalling an unrelated process.

Quality / dedup:
- Extract shared helpers: lib/own-package.ts (single source for reading the
  CLI's own package.json + bin resolution; now used by index.ts, sentry.ts,
  self-update.ts — was duplicated 3x) and lib/child-process.ts
  (forwardSignals, used by dev.ts, emulator.ts, self-update.ts).
- Extract pure decision functions for testability: decideReexec() and
  shouldRestartDashboard()/processExists().
- Fix a stale comment that claimed jsdom is the test env (it's node).

Tests: +35 cases — decideReexec branches, resolveBinName/parseOwnPackage,
isVersionNewer edges (v-prefix, x.y, both-prerelease, large nums),
resolveLatestVersion (TTL boundary, malformed/non-string body, fetch URL,
npm_config_registry), buildNpxInvocation (windows, spaced/dashed args),
shouldRestartDashboard table, killLocalDashboard early returns,
dev-env-state back-compat + clobber. 167 pass.
2026-05-29 11:29:19 -07:00

23 lines
731 B
TypeScript

import type { ChildProcess } from "child_process";
// Forward SIGINT/SIGTERM from this process to a spawned child until the
// returned cleanup function is called (call it once the child has exited).
// Killing is best-effort: a child that already exited throws, which we ignore.
export function forwardSignals(child: ChildProcess): () => void {
const forward = (signal: NodeJS.Signals) => () => {
try {
child.kill(signal);
} catch {
// best-effort
}
};
const onSigint = forward("SIGINT");
const onSigterm = forward("SIGTERM");
process.on("SIGINT", onSigint);
process.on("SIGTERM", onSigterm);
return () => {
process.off("SIGINT", onSigint);
process.off("SIGTERM", onSigterm);
};
}