using System.Diagnostics; using System.IO; using System.Text; namespace ClaudeDo.Installer.Core; public static class ProcessRunner { public static async Task<(int ExitCode, string Output)> RunAsync( string fileName, string arguments, string? workingDirectory, IProgress? progress, CancellationToken ct) { var output = new StringBuilder(); var outputLock = new object(); using var process = new Process(); process.StartInfo = new ProcessStartInfo { FileName = fileName, Arguments = arguments, WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, }; if (!process.Start()) return (-1, "Failed to start process"); var stdoutTask = ReadStreamAsync(process.StandardOutput, output, outputLock, progress); var stderrTask = ReadStreamAsync(process.StandardError, output, outputLock, progress); using var reg = ct.Register(() => { try { process.Kill(entireProcessTree: true); } catch { } }); await Task.WhenAll(stdoutTask, stderrTask); await process.WaitForExitAsync(ct); return (process.ExitCode, output.ToString()); } private static async Task ReadStreamAsync( StreamReader reader, StringBuilder output, object outputLock, IProgress? progress) { while (await reader.ReadLineAsync() is { } line) { lock (outputLock) { output.AppendLine(line); } progress?.Report(line); } } }