import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { spawn, execFile } from 'child_process'; import { promisify } from 'util'; const execFileAsync = promisify(execFile); const gpgconfCandidates = ['gpgconf', '/opt/homebrew/bin/gpgconf', '/usr/local/bin/gpgconf']; const isSocketPath = async (candidate: string): Promise => { if (!candidate) { return false; } try { const stat = await fs.promises.stat(candidate); return typeof stat.isSocket === 'function' && stat.isSocket(); } catch { return false; } }; const resolveSshAuthSock = async (): Promise => { const existing = (process.env.SSH_AUTH_SOCK || '').trim(); if (existing) { return existing; } if (process.platform === 'win32') { return undefined; } const gpgSock = path.join(os.homedir(), '.gnupg', 'S.gpg-agent.ssh'); if (await isSocketPath(gpgSock)) { return gpgSock; } const runGpgconf = async (args: string[]): Promise => { for (const candidate of gpgconfCandidates) { try { const { stdout } = await execFileAsync(candidate, args); return String(stdout || ''); } catch { continue; } } return ''; }; const candidate = (await runGpgconf(['--list-dirs', 'agent-ssh-socket'])).trim(); if (candidate && await isSocketPath(candidate)) { return candidate; } if (candidate) { await runGpgconf(['--launch', 'gpg-agent']); const retried = (await runGpgconf(['--list-dirs', 'agent-ssh-socket'])).trim(); if (retried && await isSocketPath(retried)) { return retried; } } return undefined; }; const buildGitEnv = async (): Promise => { const env: NodeJS.ProcessEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' }; if (!env.SSH_AUTH_SOCK || !env.SSH_AUTH_SOCK.trim()) { const resolved = await resolveSshAuthSock(); if (resolved) { env.SSH_AUTH_SOCK = resolved; } } return env; }; export const execGit = async (args: string[], cwd: string): Promise<{ stdout: string; stderr: string; exitCode: number }> => { const env = await buildGitEnv(); return new Promise((resolve) => { const proc = spawn('git', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], env, windowsHide: true, }); let stdout = ''; let stderr = ''; proc.stdout?.on('data', (data) => { stdout += data.toString(); }); proc.stderr?.on('data', (data) => { stderr += data.toString(); }); proc.on('close', (code) => { resolve({ stdout, stderr, exitCode: code ?? 0 }); }); proc.on('error', (error) => { resolve({ stdout: '', stderr: error instanceof Error ? error.message : String(error), exitCode: 1 }); }); }); };