claude-code-best/src/utils/execFileNoThrowPortable.ts
2026-06-07 15:48:33 +08:00

111 lines
3.3 KiB
TypeScript

import {
type Options as ExecaOptions,
type ExecaSyncMethod,
execaSync,
} from 'execa'
import { getCwd } from '../utils/cwd.js'
import { slowLogging } from './slowOperations.js'
const MS_IN_SECOND = 1000
const SECONDS_IN_MINUTE = 60
type ExecSyncOptions = {
abortSignal?: AbortSignal
timeout?: number
input?: string
stdio?: ExecaOptions['stdio']
}
type ShellCommandExecaSync = (
command: string,
options: ExecaOptions,
) => ReturnType<ExecaSyncMethod>
/**
* @deprecated Use `execa` directly with `{ shell: true, reject: false }` for non-blocking execution.
* Sync exec calls block the event loop and cause performance issues.
*/
export function execSyncWithDefaults_DEPRECATED(command: string): string | null
/**
* @deprecated Use `execa` directly with `{ shell: true, reject: false }` for non-blocking execution.
* Sync exec calls block the event loop and cause performance issues.
*/
export function execSyncWithDefaults_DEPRECATED(
command: string,
options: ExecSyncOptions,
): string | null
/**
* @deprecated Use `execa` directly with `{ shell: true, reject: false }` for non-blocking execution.
* Sync exec calls block the event loop and cause performance issues.
*/
export function execSyncWithDefaults_DEPRECATED(
command: string,
abortSignal: AbortSignal,
timeout?: number,
): string | null
/**
* @deprecated Use `execa` directly with `{ shell: true, reject: false }` for non-blocking execution.
* Sync exec calls block the event loop and cause performance issues.
*/
export function execSyncWithDefaults_DEPRECATED(
command: string,
optionsOrAbortSignal?: ExecSyncOptions | AbortSignal,
timeout = 10 * SECONDS_IN_MINUTE * MS_IN_SECOND,
): string | null {
let options: ExecSyncOptions
if (optionsOrAbortSignal === undefined) {
// No second argument - use defaults
options = {}
} else if (optionsOrAbortSignal instanceof AbortSignal) {
// Old signature - second argument is AbortSignal
options = {
abortSignal: optionsOrAbortSignal,
timeout,
}
} else {
// New signature - second argument is options object
options = optionsOrAbortSignal
}
const {
abortSignal,
timeout: finalTimeout = 10 * SECONDS_IN_MINUTE * MS_IN_SECOND,
input,
stdio = ['ignore', 'pipe', 'pipe'],
} = options
abortSignal?.throwIfAborted()
using _ = slowLogging`exec: ${command.slice(0, 200)}`
try {
// DEPRECATED: caller is responsible for sanitizing shell metacharacters
// in the command argument. This function uses shell: true which can lead
// to command injection if command contains untrusted input.
const runShellCommand = execaSync as unknown as ShellCommandExecaSync
const result = runShellCommand(command, {
env: process.env,
maxBuffer: 1_000_000,
timeout: finalTimeout,
cwd: getCwd(),
stdio,
shell: true, // execSync typically runs shell commands
reject: false, // Don't throw on non-zero exit codes
input,
})
if (!result.stdout) {
return null
}
const stdout =
typeof result.stdout === 'string'
? result.stdout
: result.stdout instanceof Uint8Array
? new TextDecoder().decode(result.stdout)
: Array.isArray(result.stdout)
? result.stdout.join('')
: String(result.stdout)
return stdout.trim() || null
} catch {
return null
}
}