/** * Simplified execFileNoThrow using node:child_process. * Replaces the app's version that depends on execa. * * Only exposes the subset of options actually used by the ink rendering engine * (specifically osc.ts: input, useCwd, timeout). */ import { execFile as cpExecFile } from 'node:child_process' type ExecFileOptions = { abortSignal?: AbortSignal timeout?: number preserveOutputOnError?: boolean useCwd?: boolean env?: NodeJS.ProcessEnv stdin?: 'ignore' | 'inherit' | 'pipe' input?: string } const MS_IN_SECOND = 1000 const SECONDS_IN_MINUTE = 60 /** * execFile, but always resolves (never throws). * Returns { stdout, stderr, code, error? }. */ export function execFileNoThrow( file: string, args: string[], options: ExecFileOptions = { timeout: 10 * SECONDS_IN_MINUTE * MS_IN_SECOND, preserveOutputOnError: true, useCwd: true, }, ): Promise<{ stdout: string; stderr: string; code: number; error?: string }> { const { abortSignal, timeout = 10 * SECONDS_IN_MINUTE * MS_IN_SECOND, preserveOutputOnError = true, useCwd = true, env, input, } = options return new Promise((resolve) => { const child = cpExecFile( file, args, { timeout, cwd: useCwd ? process.cwd() : undefined, env: env ?? undefined, maxBuffer: 1_000_000, signal: abortSignal, }, (error, stdout, stderr) => { if (error) { const code = (error as any).code === 'ABORT_ERR' ? 1 : ((error as NodeJS.ErrnoException & { status?: number }).status ?? 1) if (preserveOutputOnError) { resolve({ stdout: stdout || '', stderr: stderr || '', code, error: error.message, }) } else { resolve({ stdout: '', stderr: '', code }) } } else { resolve({ stdout: stdout || '', stderr: stderr || '', code: 0 }) } }, ) // If input is provided, write it to stdin and close if (input !== undefined && child.stdin) { child.stdin.write(input) child.stdin.end() } }) }