Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.201",
"version": "11.0.100-preview.4.26230.115",
"rollForward": "major",
"allowPrerelease": true,
"paths": [
Expand All @@ -10,7 +10,7 @@
"errorMessage": "The .NET SDK could not be found. Run ./restore.sh (Linux/macOS) or ./restore.cmd (Windows) to install the local SDK."
},
"tools": {
"dotnet": "10.0.201",
"dotnet": "11.0.100-preview.4.26230.115",
"runtimes": {
"dotnet/x64": [
"$(DotNetRuntimePreviousVersionForTesting)",
Expand Down
27 changes: 5 additions & 22 deletions src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,15 @@ internal sealed class ClaudeCodeCliRunner(ILogger<ClaudeCodeCliRunner> logger) :

try
{
var startInfo = new ProcessStartInfo(executablePath, "--version")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = new Process { StartInfo = startInfo };

process.Start();

var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
var result = await Process.RunAndCaptureTextAsync(executablePath, ["--version"], cancellationToken).ConfigureAwait(false);

if (process.ExitCode != 0)
if (result.ExitStatus.ExitCode != 0)
{
var errorOutput = await errorTask.ConfigureAwait(false);
logger.LogDebug("Claude Code CLI returned non-zero exit code {ExitCode}: {Error}", process.ExitCode, errorOutput.Trim());
logger.LogDebug("Claude Code CLI returned non-zero exit code {ExitCode}: {Error}", result.ExitStatus.ExitCode, result.StandardError.Trim());
return null;
}

var output = await outputTask.ConfigureAwait(false);
var versionString = output.Trim();
var versionString = result.StandardOutput.Trim();

if (string.IsNullOrEmpty(versionString))
{
Expand All @@ -79,7 +62,7 @@ internal sealed class ClaudeCodeCliRunner(ILogger<ClaudeCodeCliRunner> logger) :
return version;
}

logger.LogDebug("Could not parse Claude Code CLI version from output: {Output}", output.Trim());
logger.LogDebug("Could not parse Claude Code CLI version from output: {Output}", result.StandardOutput.Trim());
return null;
}
catch (Exception ex) when (ex is InvalidOperationException or System.ComponentModel.Win32Exception)
Expand Down
28 changes: 5 additions & 23 deletions src/Aspire.Cli/Agents/CopilotCli/CopilotCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,21 @@ internal sealed class CopilotCliRunner(ILogger<CopilotCliRunner> logger) : ICopi

try
{
var startInfo = new ProcessStartInfo(executablePath, "--version")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = new Process { StartInfo = startInfo };

process.Start();
var result = await Process.RunAndCaptureTextAsync(executablePath, ["--version"], cancellationToken).ConfigureAwait(false);

var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

if (process.ExitCode != 0)
if (result.ExitStatus.ExitCode != 0)
{
var errorOutput = await errorTask.ConfigureAwait(false);
logger.LogDebug("GitHub Copilot CLI returned non-zero exit code {ExitCode}: {Error}", process.ExitCode, errorOutput.Trim());
logger.LogDebug("GitHub Copilot CLI returned non-zero exit code {ExitCode}: {Error}", result.ExitStatus.ExitCode, result.StandardError.Trim());
return null;
}

var output = await outputTask.ConfigureAwait(false);

if (TryParseVersionOutput(output, out var version))
if (TryParseVersionOutput(result.StandardOutput, out var version))
{
logger.LogDebug("Found GitHub Copilot CLI version: {Version}", version);
return version;
}

logger.LogDebug("Could not parse GitHub Copilot CLI version from output: {Output}", output.Trim());
logger.LogDebug("Could not parse GitHub Copilot CLI version from output: {Output}", result.StandardOutput.Trim());
return null;
}
catch (Exception ex) when (ex is InvalidOperationException or System.ComponentModel.Win32Exception)
Expand Down
27 changes: 5 additions & 22 deletions src/Aspire.Cli/Agents/OpenCode/OpenCodeCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,15 @@ internal sealed class OpenCodeCliRunner(ILogger<OpenCodeCliRunner> logger) : IOp

try
{
var startInfo = new ProcessStartInfo(executablePath, "--version")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = new Process { StartInfo = startInfo };

process.Start();

var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
var result = await Process.RunAndCaptureTextAsync(executablePath, ["--version"], cancellationToken).ConfigureAwait(false);

if (process.ExitCode != 0)
if (result.ExitStatus.ExitCode != 0)
{
var errorOutput = await errorTask.ConfigureAwait(false);
logger.LogDebug("OpenCode CLI returned non-zero exit code {ExitCode}: {Error}", process.ExitCode, errorOutput.Trim());
logger.LogDebug("OpenCode CLI returned non-zero exit code {ExitCode}: {Error}", result.ExitStatus.ExitCode, result.StandardError.Trim());
return null;
}

var output = await outputTask.ConfigureAwait(false);
var versionString = output.Trim();
var versionString = result.StandardOutput.Trim();

if (string.IsNullOrEmpty(versionString))
{
Expand All @@ -72,7 +55,7 @@ internal sealed class OpenCodeCliRunner(ILogger<OpenCodeCliRunner> logger) : IOp
return version;
}

logger.LogDebug("Could not parse OpenCode CLI version from output: {Output}", output.Trim());
logger.LogDebug("Could not parse OpenCode CLI version from output: {Output}", result.StandardOutput.Trim());
return null;
}
catch (Exception ex) when (ex is InvalidOperationException or System.ComponentModel.Win32Exception)
Expand Down
40 changes: 8 additions & 32 deletions src/Aspire.Cli/Agents/Playwright/PlaywrightCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,15 @@ internal sealed class PlaywrightCliRunner(ILogger<PlaywrightCliRunner> logger) :

try
{
var startInfo = new ProcessStartInfo(executablePath, "--version")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
var result = await Process.RunAndCaptureTextAsync(executablePath, ["--version"], cancellationToken).ConfigureAwait(false);

using var process = new Process { StartInfo = startInfo };
process.Start();

var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

if (process.ExitCode != 0)
if (result.ExitStatus.ExitCode != 0)
{
var errorOutput = await errorTask.ConfigureAwait(false);
logger.LogDebug("playwright-cli --version returned non-zero exit code {ExitCode}: {Error}", process.ExitCode, errorOutput.Trim());
logger.LogDebug("playwright-cli --version returned non-zero exit code {ExitCode}: {Error}", result.ExitStatus.ExitCode, result.StandardError.Trim());
return null;
}

var output = await outputTask.ConfigureAwait(false);
var versionString = output.Trim().Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim();
var versionString = result.StandardOutput.Trim().Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim();

if (string.IsNullOrEmpty(versionString))
{
Expand Down Expand Up @@ -101,23 +85,15 @@ public async Task<bool> InstallSkillsAsync(string workingDirectory, Cancellation
startInfo.ArgumentList.Add("install");
startInfo.ArgumentList.Add("--skills");

using var process = new Process { StartInfo = startInfo };
process.Start();

var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
var result = await Process.RunAndCaptureTextAsync(startInfo, cancellationToken).ConfigureAwait(false);

if (process.ExitCode != 0)
if (result.ExitStatus.ExitCode != 0)
{
var errorOutput = await errorTask.ConfigureAwait(false);
logger.LogDebug("playwright-cli install --skills returned non-zero exit code {ExitCode}: {Error}", process.ExitCode, errorOutput.Trim());
logger.LogDebug("playwright-cli install --skills returned non-zero exit code {ExitCode}: {Error}", result.ExitStatus.ExitCode, result.StandardError.Trim());
return false;
}

var output = await outputTask.ConfigureAwait(false);
logger.LogDebug("playwright-cli install --skills output: {Output}", output.Trim());
logger.LogDebug("playwright-cli install --skills output: {Output}", result.StandardOutput.Trim());
return true;
}
catch (Exception ex) when (ex is InvalidOperationException or System.ComponentModel.Win32Exception)
Expand Down
24 changes: 4 additions & 20 deletions src/Aspire.Cli/Agents/VsCode/VsCodeCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,15 @@ internal sealed class VsCodeCliRunner(ILogger<VsCodeCliRunner> logger) : IVsCode

try
{
var startInfo = new ProcessStartInfo(executablePath, "--version")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = new Process { StartInfo = startInfo };

process.Start();

var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);

await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
var result = await Process.RunAndCaptureTextAsync(executablePath, ["--version"], cancellationToken).ConfigureAwait(false);

if (process.ExitCode != 0)
if (result.ExitStatus.ExitCode != 0)
{
var errorOutput = await errorTask.ConfigureAwait(false);
logger.LogDebug("VS Code CLI ({Command}) returned non-zero exit code {ExitCode}: {Error}", command, process.ExitCode, errorOutput.Trim());
logger.LogDebug("VS Code CLI ({Command}) returned non-zero exit code {ExitCode}: {Error}", command, result.ExitStatus.ExitCode, result.StandardError.Trim());
return null;
}

var output = await outputTask.ConfigureAwait(false);
var output = result.StandardOutput;

if (string.IsNullOrEmpty(output))
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Aspire.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;linux-musl-x64;osx-x64;osx-arm64</RuntimeIdentifiers>
<ToolPackageRuntimeIdentifiers>$(RuntimeIdentifiers)</ToolPackageRuntimeIdentifiers>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,17 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 publicCertif
{
Log.MacOSTrustCommandStart($"{MacOSTrustCertificateCommandLine} {s_macOSTrustCertificateCommandLineArguments}{tmpFile}");
}
using (var process = Process.Start(MacOSTrustCertificateCommandLine, s_macOSTrustCertificateCommandLineArguments + tmpFile))

// Process.Run doesn't support argument string, only ProcessStartInfo or IList<string>.
// The trust command arguments are built as a single string with embedded quotes, so we
// pass them through ProcessStartInfo.Arguments to preserve the exact quoting.
var status = Process.Run(new ProcessStartInfo(MacOSTrustCertificateCommandLine) { Arguments = s_macOSTrustCertificateCommandLineArguments + tmpFile });
if (status.ExitCode != 0)
{
process.WaitForExit();
if (process.ExitCode != 0)
{
Log.MacOSTrustCommandError(process.ExitCode);
throw new InvalidOperationException("There was an error trusting the certificate.");
}
Log.MacOSTrustCommandError(status.ExitCode);
throw new InvalidOperationException("There was an error trusting the certificate.");
}

Log.MacOSTrustCommandEnd();
}
finally
Expand Down Expand Up @@ -173,7 +175,7 @@ public override TrustLevel GetTrustLevel(X509Certificate2 certificate)
// We can't guarantee that the temp file is in a directory with sensible permissions, but we're not exporting the private key
ExportCertificate(certificate, tmpFile, includePrivateKey: false, password: null, CertificateKeyExportFormat.Pem);

using var checkTrustProcess = Process.Start(new ProcessStartInfo(
var status = Process.Run(new ProcessStartInfo(
MacOSVerifyCertificateCommandLine,
string.Format(CultureInfo.InvariantCulture, MacOSVerifyCertificateCommandLineArgumentsFormat, tmpFile))
{
Expand All @@ -182,8 +184,7 @@ public override TrustLevel GetTrustLevel(X509Certificate2 certificate)
// the cert and replicate the command to see details.
RedirectStandardError = true,
});
checkTrustProcess!.WaitForExit();
return checkTrustProcess.ExitCode == 0 ? TrustLevel.Full : TrustLevel.None;
return status.ExitCode == 0 ? TrustLevel.Full : TrustLevel.None;
}
finally
{
Expand Down Expand Up @@ -228,12 +229,11 @@ private void RemoveAdminTrustRule(X509Certificate2 certificate)
certificatePath
));

using var process = Process.Start(processInfo);
process!.WaitForExit();
var status = Process.Run(processInfo);

if (process.ExitCode != 0)
if (status.ExitCode != 0)
{
Log.MacOSRemoveCertificateTrustRuleError(process.ExitCode);
Log.MacOSRemoveCertificateTrustRuleError(status.ExitCode);
}

Log.MacOSRemoveCertificateTrustRuleEnd();
Expand Down Expand Up @@ -271,18 +271,14 @@ private void RemoveCertificateFromKeychain(string keychain, X509Certificate2 cer
Log.MacOSRemoveCertificateFromKeyChainStart(keychain, GetDescription(certificate));
}

using (var process = Process.Start(processInfo))
{
var output = process!.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd();
process.WaitForExit();
var result = Process.RunAndCaptureText(processInfo);

if (process.ExitCode != 0)
{
Log.MacOSRemoveCertificateFromKeyChainError(process.ExitCode);
throw new InvalidOperationException($@"There was an error removing the certificate with thumbprint '{certificate.Thumbprint}'.
if (result.ExitStatus.ExitCode != 0)
{
Log.MacOSRemoveCertificateFromKeyChainError(result.ExitStatus.ExitCode);
throw new InvalidOperationException($@"There was an error removing the certificate with thumbprint '{certificate.Thumbprint}'.

{output}");
}
{result.StandardOutput}{result.StandardError}");
}

Log.MacOSRemoveCertificateFromKeyChainEnd();
Expand All @@ -302,17 +298,14 @@ private static bool IsCertOnKeychain(string keychain, X509Certificate2 certifica
var subject = subjectMatch.Groups[1].Value;

// Run the find-certificate command, and look for the cert's hash in the output
using var findCertificateProcess = Process.Start(new ProcessStartInfo(
var result = Process.RunAndCaptureText(new ProcessStartInfo(
MacOSFindCertificateOnKeychainCommandLine,
string.Format(CultureInfo.InvariantCulture, MacOSFindCertificateOnKeychainCommandLineArgumentsFormat, subject, keychain))
{
RedirectStandardOutput = true
});

var output = findCertificateProcess!.StandardOutput.ReadToEnd();
findCertificateProcess.WaitForExit();

var matches = Regex.Matches(output, MacOSFindCertificateOutputRegex, RegexOptions.Multiline, maxRegexTimeout);
var matches = Regex.Matches(result.StandardOutput, MacOSFindCertificateOutputRegex, RegexOptions.Multiline, maxRegexTimeout);
var hashes = matches.OfType<Match>().Select(m => m.Groups[1].Value).ToList();

return hashes.Any(h => string.Equals(h, certificate.Thumbprint, StringComparison.Ordinal));
Expand Down Expand Up @@ -370,16 +363,12 @@ private void SaveCertificateToUserKeychain(X509Certificate2 certificate)
Log.MacOSAddCertificateToKeyChainStart(s_macOSUserKeychain, GetDescription(certificate));
}

using (var process = Process.Start(processInfo))
{
var output = process!.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd();
process.WaitForExit();
var result = Process.RunAndCaptureText(processInfo);

if (process.ExitCode != 0)
{
Log.MacOSAddCertificateToKeyChainError(process.ExitCode, output);
throw new InvalidOperationException("Failed to add the certificate to the keychain. Are you running in a non-interactive session perhaps?");
}
if (result.ExitStatus.ExitCode != 0)
{
Log.MacOSAddCertificateToKeyChainError(result.ExitStatus.ExitCode, result.StandardOutput + result.StandardError);
throw new InvalidOperationException("Failed to add the certificate to the keychain. Are you running in a non-interactive session perhaps?");
}

Log.MacOSAddCertificateToKeyChainEnd();
Expand Down
Loading
Loading