source dump of claude code
at main 150 lines 4.3 kB view raw
1// This file represents useful wrappers over node:child_process 2// These wrappers ease error handling and cross-platform compatbility 3// By using execa, Windows automatically gets shell escaping + BAT / CMD handling 4 5import { type ExecaError, execa } from 'execa' 6import { getCwd } from '../utils/cwd.js' 7import { logError } from './log.js' 8 9export { execSyncWithDefaults_DEPRECATED } from './execFileNoThrowPortable.js' 10 11const MS_IN_SECOND = 1000 12const SECONDS_IN_MINUTE = 60 13 14type ExecFileOptions = { 15 abortSignal?: AbortSignal 16 timeout?: number 17 preserveOutputOnError?: boolean 18 // Setting useCwd=false avoids circular dependencies during initialization 19 // getCwd() -> PersistentShell -> logEvent() -> execFileNoThrow 20 useCwd?: boolean 21 env?: NodeJS.ProcessEnv 22 stdin?: 'ignore' | 'inherit' | 'pipe' 23 input?: string 24} 25 26export function execFileNoThrow( 27 file: string, 28 args: string[], 29 options: ExecFileOptions = { 30 timeout: 10 * SECONDS_IN_MINUTE * MS_IN_SECOND, 31 preserveOutputOnError: true, 32 useCwd: true, 33 }, 34): Promise<{ stdout: string; stderr: string; code: number; error?: string }> { 35 return execFileNoThrowWithCwd(file, args, { 36 abortSignal: options.abortSignal, 37 timeout: options.timeout, 38 preserveOutputOnError: options.preserveOutputOnError, 39 cwd: options.useCwd ? getCwd() : undefined, 40 env: options.env, 41 stdin: options.stdin, 42 input: options.input, 43 }) 44} 45 46type ExecFileWithCwdOptions = { 47 abortSignal?: AbortSignal 48 timeout?: number 49 preserveOutputOnError?: boolean 50 maxBuffer?: number 51 cwd?: string 52 env?: NodeJS.ProcessEnv 53 shell?: boolean | string | undefined 54 stdin?: 'ignore' | 'inherit' | 'pipe' 55 input?: string 56} 57 58type ExecaResultWithError = { 59 shortMessage?: string 60 signal?: string 61} 62 63/** 64 * Extracts a human-readable error message from an execa result. 65 * 66 * Priority order: 67 * 1. shortMessage - execa's human-readable error (e.g., "Command failed with exit code 1: ...") 68 * This is preferred because it already includes signal info when a process is killed, 69 * making it more informative than just the signal name. 70 * 2. signal - the signal that killed the process (e.g., "SIGTERM") 71 * 3. errorCode - fallback to just the numeric exit code 72 */ 73function getErrorMessage( 74 result: ExecaResultWithError, 75 errorCode: number, 76): string { 77 if (result.shortMessage) { 78 return result.shortMessage 79 } 80 if (typeof result.signal === 'string') { 81 return result.signal 82 } 83 return String(errorCode) 84} 85 86/** 87 * execFile, but always resolves (never throws) 88 */ 89export function execFileNoThrowWithCwd( 90 file: string, 91 args: string[], 92 { 93 abortSignal, 94 timeout: finalTimeout = 10 * SECONDS_IN_MINUTE * MS_IN_SECOND, 95 preserveOutputOnError: finalPreserveOutput = true, 96 cwd: finalCwd, 97 env: finalEnv, 98 maxBuffer, 99 shell, 100 stdin: finalStdin, 101 input: finalInput, 102 }: ExecFileWithCwdOptions = { 103 timeout: 10 * SECONDS_IN_MINUTE * MS_IN_SECOND, 104 preserveOutputOnError: true, 105 maxBuffer: 1_000_000, 106 }, 107): Promise<{ stdout: string; stderr: string; code: number; error?: string }> { 108 return new Promise(resolve => { 109 // Use execa for cross-platform .bat/.cmd compatibility on Windows 110 execa(file, args, { 111 maxBuffer, 112 signal: abortSignal, 113 timeout: finalTimeout, 114 cwd: finalCwd, 115 env: finalEnv, 116 shell, 117 stdin: finalStdin, 118 input: finalInput, 119 reject: false, // Don't throw on non-zero exit codes 120 }) 121 .then(result => { 122 if (result.failed) { 123 if (finalPreserveOutput) { 124 const errorCode = result.exitCode ?? 1 125 void resolve({ 126 stdout: result.stdout || '', 127 stderr: result.stderr || '', 128 code: errorCode, 129 error: getErrorMessage( 130 result as unknown as ExecaResultWithError, 131 errorCode, 132 ), 133 }) 134 } else { 135 void resolve({ stdout: '', stderr: '', code: result.exitCode ?? 1 }) 136 } 137 } else { 138 void resolve({ 139 stdout: result.stdout, 140 stderr: result.stderr, 141 code: 0, 142 }) 143 } 144 }) 145 .catch((error: ExecaError) => { 146 logError(error) 147 void resolve({ stdout: '', stderr: '', code: 1 }) 148 }) 149 }) 150}