/** * Test-runner guard for the evidence tool runner (T13203). * * `runToolCached` starts a project's own toolchain (`pnpm run test`, the build, * lint, typecheck). Called from inside a test runner, the most likely cause is a * mock that no longer intercepts. A stale barrel mock once let a vitest worker * run `pnpm run test`, and every worker of that suite did the same: whole-suite * runs multiplied across the machine (the T13121 saturation class). * * Inside a test runner the runner therefore refuses to spawn a tool, with * {@link ToolSpawnInTestRunnerError}, unless the test injected a process * runner through {@link injectToolProcessRunner}. A test that really wants a * child process (the tool-cache suites run tiny `node -e` commands) opts in by * injecting the real runner on purpose; nothing reaches it by accident. * * @module * @task T13203 */ import { CleoError } from '../errors.js'; /** Captured output of one tool process. */ export interface ToolProcessResult { /** Exit code, or `null` when the process never started or died by a signal. */ exitCode: number | null; /** * POSIX signal name that terminated the child, or `null` when it exited * normally (or never started). * * gh#1381: Node's `close` event is `(code, signal)` and exactly one of them * is non-null. Binding only `code` collapses "killed after running" and * "never started" into the same `exitCode: null`. */ signal: NodeJS.Signals | null; /** Captured stdout (tail-bounded by the runner). */ stdout: string; /** Captured stderr (tail-bounded by the runner). */ stderr: string; /** `true` when the wall-clock deadline was exceeded and the process was force-killed. */ timedOut: boolean; /** * Node's spawn-error message (`ENOENT`, `EACCES`, `EAGAIN`, …) when the child * could not be started at all, else `null` (gh#1397). */ spawnError: string | null; /** * The first resource-kill marker seen anywhere in stdout or stderr while the * run streamed (T13012), even one that later output pushed out of the * captured tail. Absent when the runner does not scan. */ killMarker?: string | null; } /** * Starts one tool process and resolves with its captured output. * * @param cmd - Executable. * @param args - Arguments. * @param cwd - Working directory. * @param spawnTimeoutMs - Kill the process tree after this many ms. * @param envOverlay - Variables layered over `process.env`. */ export type ToolProcessRunner = (cmd: string, args: string[], cwd: string, spawnTimeoutMs?: number, envOverlay?: Readonly>) => Promise; /** Environment variables that mean "this process is a test runner or its worker". */ export declare const TEST_RUNNER_ENV_MARKERS: readonly ["VITEST", "VITEST_WORKER_ID", "JEST_WORKER_ID"]; /** * Name the test-runner marker present in `env`, if any. * * @param env - Environment to inspect (injectable for tests). * `NODE_ENV=test` is deliberately not a marker: developer shells and tools set * it, and refusing a human's `cleo verify` would be a regression. Every vitest * and jest worker sets one of the markers above. * * @returns The marker (`VITEST`, `VITEST_WORKER_ID` or `JEST_WORKER_ID`), or * `null` outside a test runner. */ export declare function detectTestRunner(env?: NodeJS.ProcessEnv): string | null; /** * The evidence tool runner refused to start a tool inside a test runner. * * @example * ```ts * try { await runToolCached(cmd, root); } * catch (e) { if (e instanceof ToolSpawnInTestRunnerError) console.error(e.codeName); } * ``` */ export declare class ToolSpawnInTestRunnerError extends CleoError { /** Stable machine-readable error code. */ readonly codeName = "E_TOOL_SPAWN_IN_TEST_RUNNER"; /** * @param tool - Canonical tool name (`test`, `build`, …). * @param command - The command line that was refused. * @param marker - The test-runner marker that was detected. */ constructor(tool: string, command: string, marker: string); } /** * `cleo run` (`runGoverned`) refused to start a command inside a test runner * (T13236): the same recursion class as {@link ToolSpawnInTestRunnerError}, * reached through the CLI's front door instead of the evidence runner — a * test that spawns `cleo run`, or calls `runGoverned` with the real spawner, * would start the suite again from inside one of its own workers. * * @example * ```ts * try { await runGoverned(opts); } * catch (e) { if (e instanceof GovernedRunInTestRunnerError) console.error(e.codeName); } * ``` */ export declare class GovernedRunInTestRunnerError extends CleoError { /** Stable machine-readable error code. */ readonly codeName = "E_RUN_SPAWN_IN_TEST_RUNNER"; /** * @param command - The command line that was refused (redacted). * @param marker - The test-runner marker that was detected. */ constructor(command: string, marker: string); } /** * Inject the process runner the evidence tool runner uses (tests only). * * Passing the real runner (`spawnToolProcess` from `tool-cache.ts`) is the * explicit opt-in for a test that means to start a child process. Pass `null` * to restore the default. * * @param runner - Runner to use, or `null` for the default. */ export declare function injectToolProcessRunner(runner: ToolProcessRunner | null): void; /** * Pick the process runner for one tool run. * * @param tool - Canonical tool name, for the refusal message. * @param command - The command line, for the refusal message. * @param fallback - The real runner, used outside a test runner. * @param env - Environment to inspect (injectable for tests). * @returns The injected runner, else `fallback` outside a test runner. * @throws ToolSpawnInTestRunnerError inside a test runner with no injected runner. */ export declare function resolveToolProcessRunner(tool: string, command: string, fallback: ToolProcessRunner, env?: NodeJS.ProcessEnv): ToolProcessRunner; //# sourceMappingURL=tool-runner-guard.d.ts.map