/** * Canonical cross-platform subprocess helper. Owns: * - Windows cmd.exe wrapper for resolving .cmd/.bat shims (pnpm, tsx, etc.) * without enabling shell metacharacter interpretation. * - Bounded stderr ring buffer. * - Idempotent dispose (SIGINT → 2s grace → SIGKILL). * - withSpawned try/finally lifecycle for tests. * * The helper deliberately does not pass an `env` field to `child_process.spawn`, * so children inherit `process.env` — including `NODE_V8_COVERAGE` set by * coverage:node:tracked. This is what makes subprocess coverage capture * automatic. A drift-guard test (tests/unit/meta/spawn-coverage-inheritance.test.ts) * fails CI if any future commit adds an env override. * * Lives in `@czap/command/host` (CUT A1 capstone-1) — the canonical home for * Node host execution shared by the CLI and MCP adapters. `@czap/cli`'s * `lib/spawn.ts` / `spawn-helpers.ts` and `scripts/lib/spawn.ts` are thin * re-exports so existing import paths keep working; this is the one impl the * spawn drift-guard tests pin. * * @module */ import { type ChildProcess } from 'node:child_process'; /** Result of a one-shot spawnArgv invocation. */ export interface SpawnResult { readonly exitCode: number; readonly stderrTail: string; } /** Options for spawnArgv / withSpawned. */ export interface SpawnArgvOpts { /** Maximum stderr bytes retained in the returned tail. Defaults to 16 KiB. */ readonly stderrCapBytes?: number; /** Override stdio. Defaults to ['ignore', 'inherit', 'pipe']. */ readonly stdio?: 'inherit' | 'pipe' | readonly ('ignore' | 'inherit' | 'pipe')[]; /** Working directory for the spawned process. Defaults to process.cwd(). */ readonly cwd?: string; } /** Result of a one-shot spawnArgvCapture invocation. */ export interface SpawnCaptureResult { readonly exitCode: number; readonly stdout: string; readonly stderr: string; /** * True when the spawn was killed by {@link SpawnCaptureOpts.timeoutMs} before it * closed on its own — distinguishable from a normal nonzero exit. Absent (falsy) * for every spawn that completed or that was launched without `timeoutMs`. */ readonly timedOut?: boolean; } /** Options for spawnArgvCapture. */ export interface SpawnCaptureOpts { /** Working directory for the spawned process. Defaults to process.cwd(). */ readonly cwd?: string; /** Maximum bytes retained per stream. Defaults to 1 MiB. */ readonly captureBytes?: number; /** * If set, kill the child after this many ms and resolve with `timedOut: true` * (never rejects on timeout). For bounding short external probes (e.g. `czap * doctor`'s `pnpm --version`) so a slow/wedged tool can't hang the caller. * Omitted → existing behavior (resolve only on the child's `close`). */ readonly timeoutMs?: number; } /** Live handle on a running spawn — used by withSpawned. */ export interface SpawnHandle { readonly pid: number; readonly child: ChildProcess; /** Read stdout as a string stream. Only present when stdio[1] is 'pipe'. */ readline(): AsyncIterableIterator; /** Drain any retained stderr bytes accumulated so far. */ readonly stderrTail: () => string; /** Idempotent disposal. SIGINT → 2s grace → SIGKILL. No-op if already dead. */ dispose(): Promise; } /** * Quote a single argv token for safe inclusion in a Windows cmd.exe command * line. Tokens with no special characters round-trip as-is; everything else * is double-quoted with internal quotes backslash-escaped. Keeps shell * metacharacters (`;`, `&`, `|`, `<`, `>`, `^`, `(`, `)`) inside a quoted * string so cmd.exe treats them as literal bytes. * * Re-exported by packages/cli/src/spawn-helpers.ts and * scripts/support/pnpm-process.ts; tests/unit/spawn-quoting-drift.test.ts * enforces byte-equivalence across all three call sites. */ export declare function quoteWindowsArg(arg: string): string; /** * Run a subprocess with an argv array (`shell: false`). stderr is captured * with a bounded ring buffer; stdout inherits the parent. Resolves once the * subprocess exits — never throws on nonzero exit (callers branch on * `exitCode`). */ export declare function spawnArgv(command: string, args: readonly string[], opts?: SpawnArgvOpts): Promise; /** * Run a subprocess whose progress should remain visible to humans, but whose * stdout must NOT pollute our own stdout. Child stdout is piped to our * stderr; child stderr inherits to our stderr; child stdin is closed. * * Use this for commands like `czap doctor --fix` whose stdout contract is * JSON-only (the doctor receipt is written to stdout AFTER the fixes run, * and would otherwise be preceded by the build's tsc output line by line). * * The stderrTail field of the returned SpawnResult is empty — both streams * went through to the user, none of them are buffered for postmortem. */ export declare function spawnArgvVisible(command: string, args: readonly string[], opts?: { readonly cwd?: string; }): Promise; /** * Run a subprocess and fully capture stdout + stderr to strings, with an * optional `cwd`. Used by ship/verify where the publisher needs the * subprocess output (pnpm pack's tarball path, pnpm publish --dry-run's * notice block) as a byte sequence to hash. Like spawnArgv, never throws * on nonzero exit — callers branch on `exitCode`. */ export declare function spawnArgvCapture(command: string, args: readonly string[], opts?: SpawnCaptureOpts): Promise; /** * Lifecycle-managed spawn for tests. Spawns, runs the callback, disposes the * child in `finally` (idempotent: SIGINT → 2s grace → SIGKILL → no-op). * * Tests never write `try/finally proc.kill()` themselves — a single * implementation handles cleanup uniformly on Linux and Windows. */ export declare function withSpawned(command: string, args: readonly string[], fn: (handle: SpawnHandle) => Promise, opts?: SpawnArgvOpts): Promise; /** * Start a long-lived subprocess and return a live handle. Caller owns * disposal. Used by `withSpawned` (auto-disposes in finally) and by * Vitest browser commands that need to keep the child alive across * multiple browser-side calls. */ export declare function startSpawnHandle(command: string, args: readonly string[], opts?: SpawnArgvOpts): SpawnHandle; //# sourceMappingURL=spawn.d.ts.map