/** * Git command execution utilities */ import { spawnProcess } from '@automaker/platform'; /** * Execute git command with array arguments to prevent command injection. * Uses spawnProcess from @automaker/platform for secure, cross-platform execution. * * @param args - Array of git command arguments (e.g., ['worktree', 'add', path]) * @param cwd - Working directory to execute the command in * @param env - Optional additional environment variables to pass to the git process. * These are merged on top of the current process environment. Pass * `{ LC_ALL: 'C' }` to force git to emit English output regardless of the * system locale so that text-based output parsing remains reliable. * @param abortController - Optional AbortController to cancel the git process. * When the controller is aborted the underlying process is sent SIGTERM and * the returned promise rejects with an Error whose message is 'Process aborted'. * @returns Promise resolving to stdout output * @throws Error with stderr/stdout message if command fails. The thrown error * also has `stdout` and `stderr` string properties for structured access. * * @example * ```typescript * // Safe: no injection possible * await execGitCommand(['branch', '-D', branchName], projectPath); * * // Force English output for reliable text parsing: * await execGitCommand(['rebase', '--', 'main'], worktreePath, { LC_ALL: 'C' }); * * // With a process-level timeout: * const controller = new AbortController(); * const timerId = setTimeout(() => controller.abort(), 30_000); * try { * await execGitCommand(['fetch', '--all', '--quiet'], cwd, undefined, controller); * } finally { * clearTimeout(timerId); * } * * // Instead of unsafe: * // await execAsync(`git branch -D ${branchName}`, { cwd }); * ``` */ export async function execGitCommand( args: string[], cwd: string, env?: Record, abortController?: AbortController ): Promise { const result = await spawnProcess({ command: 'git', args, cwd, ...(env !== undefined ? { env } : {}), ...(abortController !== undefined ? { abortController } : {}), }); // spawnProcess returns { stdout, stderr, exitCode } if (result.exitCode === 0) { return result.stdout; } else { const errorMessage = result.stderr || result.stdout || `Git command failed with code ${result.exitCode}`; throw Object.assign(new Error(errorMessage), { stdout: result.stdout, stderr: result.stderr, }); } }