import { spawn as node_spawn_child_process, type SpawnOptions, type ChildProcess } from 'node:child_process'; /** * Spawn failed before the process could run. * * Note: `child` is still present because `node:child_process.spawn` returns a * `ChildProcess` synchronously and then emits `'error'` asynchronously — the * handle exists but the OS process never started. * * @example ENOENT when command not found */ export interface SpawnResultError { kind: 'error'; ok: false; child: ChildProcess; error: Error; } /** * Process ran and exited with a code. * `ok` is true when `code` is 0. */ export interface SpawnResultExited { kind: 'exited'; ok: boolean; child: ChildProcess; code: number; } /** * Process was terminated by a signal (e.g., SIGTERM, SIGKILL). */ export interface SpawnResultSignaled { kind: 'signaled'; ok: false; child: ChildProcess; signal: NodeJS.Signals; } /** * Discriminated union representing all possible spawn outcomes. * Narrow via `result.kind === 'error' | 'exited' | 'signaled'`. */ export type SpawnResult = SpawnResultError | SpawnResultExited | SpawnResultSignaled; /** * Options for spawning processes, extending Node's `SpawnOptions`. */ export interface SpawnProcessOptions extends SpawnOptions { /** * AbortSignal to cancel the process. * When aborted, sends SIGTERM to the child. */ signal?: AbortSignal; /** * Timeout in milliseconds. Must be non-negative. * Sends SIGTERM when exceeded. A value of 0 triggers immediate SIGTERM. */ timeout_ms?: number; /** * Custom spawn function for testing. Defaults to `node:child_process` spawn. */ spawn_child_process?: typeof node_spawn_child_process; } /** * Options for killing processes. */ export interface DespawnOptions { /** * Signal to send. * @default 'SIGTERM' */ signal?: NodeJS.Signals; /** * Timeout in ms before escalating to SIGKILL. Must be non-negative. * Useful for processes that may ignore SIGTERM. A value of 0 triggers immediate SIGKILL escalation. */ timeout_ms?: number; } /** * Result of spawning a detached process. */ export type SpawnDetachedResult = { kind: 'spawned'; child: ChildProcess; } | { kind: 'error'; message: string; }; /** * Handle for a spawned process with access to the child and completion promise. */ export interface SpawnedProcess { /** The underlying Node.js ChildProcess */ child: ChildProcess; /** Resolves when the process exits */ closed: Promise; } /** * Result of `spawn_out` with captured output streams. */ export interface SpawnedOut { result: SpawnResult; /** Captured stdout, or null if stream unavailable */ stdout: string | null; /** Captured stderr, or null if stream unavailable */ stderr: string | null; } /** * Manages a collection of spawned processes for lifecycle tracking and cleanup. * * The default instance `process_registry_default` is used by module-level functions. * Create separate instances for isolated process groups or testing. * * @example * ```ts * // Use default registry via module functions * const result = await spawn('echo', ['hello']); * * // Or create isolated registry for testing * const registry = new ProcessRegistry(); * const {child, closed} = registry.spawn('node', ['server.js']); * await registry.despawn_all(); * ``` */ export declare class ProcessRegistry { #private; /** All currently tracked child processes */ readonly processes: Set; /** * Spawns a process and tracks it in this registry. * The process is automatically unregistered when it exits. * * @param options - spawn options including `signal` and `timeout_ms` * @mutates this.processes - adds the spawned child; removed automatically when it exits * @throws Error if `timeout_ms` is negative */ spawn(command: string, args?: ReadonlyArray, options?: SpawnProcessOptions): SpawnedProcess; /** * Spawns a process and captures stdout/stderr as strings. * Sets `stdio: 'pipe'` automatically. * * @returns result with captured `stdout` and `stderr` * - `null` means spawn failed (ENOENT, etc.) or stream was unavailable * - `''` (empty string) means process ran but produced no output * - non-empty string contains the captured output * @throws Error if `timeout_ms` is negative */ spawn_out(command: string, args?: ReadonlyArray, options?: SpawnProcessOptions): Promise; /** * Kills a child process and waits for it to exit. * * @param options - kill options including signal and timeout * @throws Error if `timeout_ms` is negative */ despawn(child: ChildProcess, options?: DespawnOptions): Promise; /** * Kills all processes in this registry. * * @param options - kill options applied to all processes */ despawn_all(options?: DespawnOptions): Promise>; /** * Attaches an `uncaughtException` handler that kills all processes before exiting. * Prevents zombie processes when the parent crashes. * * By default uses SIGKILL for immediate termination. Set `graceful_timeout_ms` * to attempt SIGTERM first (allowing processes to clean up) before escalating * to SIGKILL after the timeout. * * Note: Node's uncaughtException handler cannot await async operations, so * graceful shutdown uses a blocking busy-wait. This may not be sufficient * for processes that need significant cleanup time. * * @param options - configuration options * @param options.to_error_label - Customize error label, return `null` for default * @param options.map_error_text - Customize error text, return `''` to silence * @param options.handle_error - Called after cleanup, defaults to `process.exit(1)` * @param options.graceful_timeout_ms - If set, sends SIGTERM first and waits this * many ms before SIGKILL. Recommended: 100-500ms. If null/undefined, uses * immediate SIGKILL (default). * @returns cleanup function to remove the handler * @mutates this.#error_handler - sets the handler; the returned cleanup function clears it and unsubscribes from `process.uncaughtException` * @throws Error if a handler is already attached to this registry */ attach_error_handler(options?: { to_error_label?: (err: Error, origin: NodeJS.UncaughtExceptionOrigin) => string | null; map_error_text?: (err: Error, origin: NodeJS.UncaughtExceptionOrigin) => string | null; handle_error?: (err: Error, origin: NodeJS.UncaughtExceptionOrigin) => void; graceful_timeout_ms?: number | null; }): () => void; } /** * Default process registry used by module-level spawn functions. * For testing or isolated process groups, create a new `ProcessRegistry` instance. */ export declare const process_registry_default: ProcessRegistry; /** * Spawns a process with graceful shutdown behavior. * Returns a handle with access to the `child` process and `closed` promise. * * @example * ```ts * const {child, closed} = spawn_process('node', ['server.js']); * // Later... * child.kill(); * const result = await closed; * ``` */ export declare const spawn_process: (command: string, args?: ReadonlyArray, options?: SpawnProcessOptions) => SpawnedProcess; /** * Spawns a process and returns a promise that resolves when it exits. * Use this for commands that complete (not long-running processes). * * @example * ```ts * const result = await spawn('npm', ['install']); * if (!result.ok) console.error('Install failed'); * ``` */ export declare const spawn: (command: string, args?: ReadonlyArray, options?: SpawnProcessOptions) => Promise; /** * Spawns a process and captures stdout/stderr as strings. * * @example * ```ts * const {result, stdout} = await spawn_out('git', ['status', '--porcelain']); * if (result.ok && stdout) console.log(stdout); * ``` */ export declare const spawn_out: (command: string, args?: ReadonlyArray, options?: SpawnProcessOptions) => Promise; /** * Kills a child process and returns the result. * * @example * ```ts * const result = await despawn(child, {timeout_ms: 5000}); * // If process ignores SIGTERM, SIGKILL sent after 5s * ``` */ export declare const despawn: (child: ChildProcess, options?: DespawnOptions) => Promise; /** * Kills all processes in the default registry. */ export declare const despawn_all: (options?: DespawnOptions) => Promise>; /** * Attaches an `uncaughtException` handler to the default registry. * * @see `ProcessRegistry.attach_error_handler` */ export declare const attach_process_error_handler: (options?: Parameters[0]) => (() => void); /** * Spawns a detached process that continues after parent exits. * * Unlike other spawn functions, this is NOT tracked in any `ProcessRegistry`. * The spawned process is meant to outlive the parent (e.g., daemon processes). * * @param options - spawn options (use `stdio` to redirect output to file descriptors) * @returns result with pid on success, or error message on failure * * @example * ```ts * // Simple detached process * const result = spawn_detached('node', ['daemon.js'], {cwd: '/app'}); * * // With log file (caller handles file opening) * import {openSync, closeSync} from 'node:fs'; * const log_fd = openSync('/var/log/daemon.log', 'a'); * const result = spawn_detached('node', ['daemon.js'], { * cwd: '/app', * stdio: ['ignore', log_fd, log_fd], * }); * closeSync(log_fd); * ``` */ export declare const spawn_detached: (command: string, args?: ReadonlyArray, options?: SpawnOptions) => SpawnDetachedResult; /** * Formats a child process for display. * * @example * ```ts * `pid(1234) <- node server.js` * ``` */ export declare const print_child_process: (child: ChildProcess) => string; /** * Formats a spawn result for display. * Returns `'ok'` for success, or the error/signal/code for failures. */ export declare const print_spawn_result: (result: SpawnResult) => string; /** * Formats a spawn result for use in error messages. */ export declare const spawn_result_to_message: (result: SpawnResult) => string; /** * Handle for a process that can be restarted. * Exposes `closed` promise for observing exits and implementing restart policies. */ export interface RestartableProcess { /** * Restart the process, killing the current one if active. * Concurrent calls are coalesced - multiple calls before the first completes * will share the same restart operation. */ restart: () => Promise; /** Kill the process and set `active` to false */ kill: () => Promise; /** * Whether this handle is managing a process. * * Note: This reflects handle state, not whether the underlying OS process is executing. * Remains `true` after a process exits naturally until `kill()` or `restart()` is called. * To check if the process actually exited, await `closed`. */ readonly active: boolean; /** The current child process, or null if not active */ readonly child: ChildProcess | null; /** Promise that resolves when the current process exits */ readonly closed: Promise; /** * Promise that resolves when the initial `spawn_process()` call completes. * * Note: This resolves when the spawn syscall returns, NOT when the process * is "ready" or has produced output. For commands that fail immediately * (e.g., ENOENT), `spawned` still resolves - check `closed` for errors. * * @example * ```ts * const rp = spawn_restartable_process('node', ['server.js']); * await rp.spawned; // Safe to access rp.child now * ``` */ readonly spawned: Promise; } /** * Spawns a process that can be restarted. * Handles concurrent restart calls gracefully. * * Note: The `signal` and `timeout_ms` options are reapplied on each restart. * If the AbortSignal is already aborted when `restart()` is called, the new * process will be killed immediately. * * @example Simple restart on crash * ```ts * const rp = spawn_restartable_process('node', ['server.js']); * * while (rp.active) { * const result = await rp.closed; * if (result.ok) break; // Clean exit * await rp.restart(); * } * ``` * * @example Restart with backoff * ```ts * const rp = spawn_restartable_process('node', ['server.js']); * let failures = 0; * * while (rp.active) { * const result = await rp.closed; * if (result.ok || ++failures > 5) break; * await new Promise((r) => setTimeout(r, 1000 * failures)); * await rp.restart(); * } * ``` */ export declare const spawn_restartable_process: (command: string, args?: ReadonlyArray, options?: SpawnProcessOptions) => RestartableProcess; /** * Checks if a process with the given PID is running. * Uses signal 0 which checks existence without sending a signal. * * @param pid - the process ID to check (must be a positive integer) * @returns `true` if the process exists (even without permission to signal it), * `false` if the process doesn't exist or if pid is invalid (non-positive, non-integer, NaN, Infinity) */ export declare const process_is_pid_running: (pid: number) => boolean; //# sourceMappingURL=process.d.ts.map