in src/Cli/Abstractions/Command/Command.cs [14:242]
public class Command(Process? process, bool trimTrailingNewlines = false) : ICommand
{
private readonly Process _process = process ?? throw new ArgumentNullException(nameof(process));
private readonly bool _trimTrailingNewlines = trimTrailingNewlines;
private StreamForwarder? _stdOut;
private StreamForwarder? _stdErr;
private bool _running = false;
public string CommandName => _process.StartInfo.FileName;
public string CommandArgs => _process.StartInfo.Arguments;
public CommandResult Execute()
{
return Execute(null, null);
}
public CommandResult Execute(Func<Process, Task>? processStarted, StreamWriter? fileWriter)
{
Reporter.Verbose.WriteLine(string.Format(
"Running {0} {1}",
_process.StartInfo.FileName,
_process.StartInfo.Arguments));
ThrowIfRunning();
_running = true;
_process.EnableRaisingEvents = true;
Stopwatch? sw = null;
if (CommandLoggingContext.IsVerbose)
{
sw = Stopwatch.StartNew();
Reporter.Verbose.WriteLine($"> {FormatProcessInfo(_process.StartInfo)}".White());
}
Task? processTask = null;
using (var reaper = new ProcessReaper(_process))
{
_process.Start();
if (processStarted != null)
{
processTask = Task.Run(async () =>
{
try
{
await processStarted(_process);
}
catch (Exception ex)
{
Reporter.Verbose.WriteLine(string.Format(
"Error in process started handler: ",
ex.Message));
}
});
}
reaper.NotifyProcessStarted();
Reporter.Verbose.WriteLine(string.Format(
"Process ID: {0}",
_process.Id));
var taskOut = _stdOut?.BeginRead(_process.StandardOutput);
var taskErr = _stdErr?.BeginRead(_process.StandardError);
_process.WaitForExit();
taskOut?.Wait();
taskErr?.Wait();
processTask?.Wait();
}
var exitCode = _process.ExitCode;
if (CommandLoggingContext.IsVerbose)
{
Debug.Assert(sw is not null);
var message = string.Format(
"{0} exited with {1} in {2} ms.",
FormatProcessInfo(_process.StartInfo),
exitCode,
sw.ElapsedMilliseconds);
if (exitCode == 0)
{
Reporter.Verbose.WriteLine(message.Green());
}
else
{
Reporter.Verbose.WriteLine(message.Red().Bold());
}
}
return new CommandResult(
_process.StartInfo,
exitCode,
_stdOut?.CapturedOutput,
_stdErr?.CapturedOutput);
}
public ICommand WorkingDirectory(string? projectDirectory)
{
_process.StartInfo.WorkingDirectory = projectDirectory;
return this;
}
public ICommand EnvironmentVariable(string name, string? value)
{
_process.StartInfo.Environment[name] = value;
return this;
}
public ICommand CaptureStdOut()
{
ThrowIfRunning();
EnsureStdOut();
_stdOut?.Capture(_trimTrailingNewlines);
return this;
}
public ICommand CaptureStdErr()
{
ThrowIfRunning();
EnsureStdErr();
_stdErr?.Capture(_trimTrailingNewlines);
return this;
}
public ICommand ForwardStdOut(TextWriter? to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
{
ThrowIfRunning();
if (!onlyIfVerbose || CommandLoggingContext.IsVerbose)
{
EnsureStdOut();
if (to == null)
{
_stdOut?.ForwardTo(writeLine: Reporter.Output.WriteLine);
EnvironmentVariable(CommandLoggingContext.Variables.AnsiPassThru, ansiPassThrough.ToString());
}
else
{
_stdOut?.ForwardTo(writeLine: to.WriteLine);
}
}
return this;
}
public ICommand ForwardStdErr(TextWriter? to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
{
ThrowIfRunning();
if (!onlyIfVerbose || CommandLoggingContext.IsVerbose)
{
EnsureStdErr();
if (to == null)
{
_stdErr?.ForwardTo(writeLine: Reporter.Error.WriteLine);
EnvironmentVariable(CommandLoggingContext.Variables.AnsiPassThru, ansiPassThrough.ToString());
}
else
{
_stdErr?.ForwardTo(writeLine: to.WriteLine);
}
}
return this;
}
public ICommand OnOutputLine(Action<string> handler)
{
ThrowIfRunning();
EnsureStdOut();
_stdOut?.ForwardTo(writeLine: handler);
return this;
}
public ICommand OnErrorLine(Action<string> handler)
{
ThrowIfRunning();
EnsureStdErr();
_stdErr?.ForwardTo(writeLine: handler);
return this;
}
public ICommand SetCommandArgs(string commandArgs)
{
_process.StartInfo.Arguments = commandArgs;
return this;
}
private static string FormatProcessInfo(ProcessStartInfo info)
{
if (string.IsNullOrWhiteSpace(info.Arguments))
{
return info.FileName;
}
return info.FileName + " " + info.Arguments;
}
private void EnsureStdOut()
{
_stdOut ??= new StreamForwarder();
_process.StartInfo.RedirectStandardOutput = true;
}
private void EnsureStdErr()
{
_stdErr ??= new StreamForwarder();
_process.StartInfo.RedirectStandardError = true;
}
private void ThrowIfRunning([CallerMemberName] string? memberName = null)
{
if (_running)
{
throw new InvalidOperationException($"Unable to invoke {memberName} after the command has been run");
}
}
}