8000 Minimal changes to improve CLI testability. by mitchdenny · Pull Request #8657 · dotnet/aspire · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Minimal changes to improve CLI testability. #8657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 10, 2025
Merged
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
8 changes: 4 additions & 4 deletions src/Aspire.Cli/Commands/AddCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ namespace Aspire.Cli.Commands;
internal sealed class AddCommand : BaseCommand
{
private readonly ActivitySource _activitySource = new ActivitySource(nameof(AddCommand));
private readonly DotNetCliRunner _runner;
private readonly IDotNetCliRunner _runner;
private readonly INuGetPackageCache _nuGetPackageCache;

public AddCommand(DotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
public AddCommand(IDotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
: base("add", "Add an integration to the Aspire project.")
{
ArgumentNullException.ThrowIfNull(runner, nameof(runner));
Expand Down Expand Up @@ -105,9 +105,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
_ => throw new InvalidOperationException("Unexpected number of packages found.")
};

var addPackageResult = await AnsiConsole.Status().StartAsync(
var addPackageResult = await InteractionUtils.ShowStatusAsync(
"Adding Aspire integration...",
async context => {
async () => {
var addPackageResult = await _runner.AddPackageAsync(
effectiveAppHostProjectFile,
selectedNuGetPackage.Package.Id,
Expand Down
28 changes: 9 additions & 19 deletions src/Aspire.Cli/Commands/NewCommand.cs
< 8000 td class="blob-code blob-code-inner blob-code-hunk">@@ -157,18 +152,13 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ namespace Aspire.Cli.Commands;
internal sealed class NewCommand : BaseCommand
{
private readonly ActivitySource _activitySource = new ActivitySource(nameof(NewCommand));
private readonly DotNetCliRunner _runner;
private readonly IDotNetCliRunner _runner;
private readonly INuGetPackageCache _nuGetPackageCache;

public NewCommand(DotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
public NewCommand(IDotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
: base("new", "Create a new Aspire sample project.")
{
ArgumentNullException.ThrowIfNull(runner, nameof(runner));
Expand Down Expand Up @@ -140,14 +140,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
var source = parseResult.GetValue<string?>("--source");
var version = await GetProjectTemplatesVersionAsync(parseResult, prerelease, source, cancellationToken);

var templateInstallResult = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
.SpinnerStyle(Style.Parse("purple"))
.StartAsync(
":ice: Getting latest templates...",
async context => {
return await _runner.InstallTemplateAsync("Aspire.ProjectTemplates", version, source, true, cancellationToken);
});
var templateInstallResult = await InteractionUtils.ShowStatusAsync(
":ice: Getting latest templates...",
() => _runner.InstallTemplateAsync("Aspire.ProjectTemplates", version, source, true, cancellationToken));

if (templateInstallResult.ExitCode != 0)
{
Expand All

AnsiConsole.MarkupLine($":package: Using project templates version: {templateInstallResult.TemplateVersion}");

int newProjectExitCode = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
.SpinnerStyle(Style.Parse("purple"))
.StartAsync(
":rocket: Creating new Aspire project...",
async context => {
return await _runner.NewProjectAsync(
var newProjectExitCode = await InteractionUtils.ShowStatusAsync(
":rocket: Creating new Aspire project...",
() => _runner.NewProjectAsync(
template.TemplateName,
name,
outputPath,
cancellationToken);
});
cancellationToken));

if (newProjectExitCode != 0)
{
Expand Down
64 changes: 30 additions & 34 deletions src/Aspire.Cli/Commands/PublishCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ namespace Aspire.Cli.Commands;
internal sealed class PublishCommand : BaseCommand
{
private readonly ActivitySource _activitySource = new ActivitySource(nameof(PublishCommand));
private readonly DotNetCliRunner _runner;
private readonly IDotNetCliRunner _runner;

public PublishCommand(DotNetCliRunner runner)
public PublishCommand(IDotNetCliRunner runner)
: base("publish", "Generates deployment artifacts for an Aspire app host project.")
{
ArgumentNullException.ThrowIfNull(runner, nameof(runner));
Expand All @@ -38,7 +38,7 @@ public PublishCommand(DotNetCliRunner runner)

protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
{
(bool IsCompatableAppHost, bool SupportsBackchannel, string? AspireHostingSdkVersion)? appHostCompatabilityCheck = null;
(bool IsCompatibleAppHost, bool SupportsBackchannel, string? AspireHostingSdkVersion)? appHostCompatibilityCheck = null;

try
{
Expand All @@ -59,9 +59,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
env[KnownConfigNames.WaitForDebugger] = "true";
}

appHostCompatabilityCheck = await AppHostHelper.CheckAppHostCompatabilityAsync(_runner, effectiveAppHostProjectFile, cancellationToken);
appHostCompatibilityCheck = await AppHostHelper.CheckAppHostCompatibilityAsync(_runner, effectiveAppHostProjectFile, cancellationToken);

if (!appHostCompatabilityCheck?.IsCompatableAppHost ?? throw new InvalidOperationException("IsCompatableAppHost is null"))
if (!appHostCompatibilityCheck?.IsCompatibleAppHost ?? throw new InvalidOperationException("IsCompatibleAppHost is null"))
{
return ExitCodeConstants.FailedToDotnetRunAppHost;
}
Expand All @@ -78,36 +78,32 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
var outputPath = parseResult.GetValue<string>("--output-path");
var fullyQualifiedOutputPath = Path.GetFullPath(outputPath ?? ".");

var publishersResult = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
.SpinnerStyle(Style.Parse("purple"))
.StartAsync<(int ExitCode, string[]? Publishers)>(
publisher is { } ? ":package: Getting publisher..." : ":package: Getting publishers...",
async context => {

using var getPublishersActivity = _activitySource.StartActivity(
$"{nameof(ExecuteAsync)}-Action-GetPublishers",
ActivityKind.Client);

var backchannelCompletionSource = new TaskCompletionSource<AppHostBackchannel>();
var pendingInspectRun = _runner.RunAsync(
effectiveAppHostProjectFile,
false,
true,
["--operation", "inspect"],
null,
backchannelCompletionSource,
cancellationToken).ConfigureAwait(false);

var backchannel = await backchannelCompletionSource.Task.ConfigureAwait(false);
var publishers = await backchannel.GetPublishersAsync(cancellationToken).ConfigureAwait(false);

await backchannel.RequestStopAsync(cancellationToken).ConfigureAwait(false);
var exitCode = await pendingInspectRun;
var publishersResult = await InteractionUtils.ShowStatusAsync<(int ExitCode, string[] Publishers)>(
publisher is { } ? ":package: Getting publisher..." : ":package: Getting publishers...",
async () => {
using var getPublishersActivity = _activitySource.StartActivity(
$"{nameof(ExecuteAsync)}-Action-GetPublishers",
ActivityKind.Client);

var backchannelCompletionSource = new TaskCompletionSource<AppHostBackchannel>();
var pendingInspectRun = _runner.RunAsync(
effectiveAppHostProjectFile,
false,
true,
["--operation", "inspect"],
null,
backchannelCompletionSource,
cancellationToken).ConfigureAwait(false);

return (exitCode, publishers);
var backchannel = await backchannelCompletionSource.Task.ConfigureAwait(false);
var publishers = await backchannel.GetPublishersAsync(cancellationToken).ConfigureAwait(false);

await backchannel.RequestStopAsync(cancellationToken).ConfigureAwait(false);
var exitCode = await pendingInspectRun;

}).ConfigureAwait(false);
return (exitCode, publishers);
}
);

if (publishersResult.ExitCode != 0)
{
Expand Down Expand Up @@ -255,7 +251,7 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
{
return InteractionUtils.DisplayIncompatibleVersionError(
ex,
appHostCompatabilityCheck?.AspireHostingSdkVersion ?? throw new InvalidOperationException("AspireHostingSdkVersion is null")
appHostCompatibilityCheck?.AspireHostingSdkVersion ?? throw new InvalidOperationException("AspireHostingSdkVersion is null")
);
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/Aspire.Cli/Commands/RootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#if DEBUG
using System.Diagnostics;
using Spectre.Console;
using Aspire.Cli.Utils;
#endif

using BaseRootCommand = System.CommandLine.RootCommand;
Expand Down Expand Up @@ -34,15 +34,14 @@ public RootCommand(NewCommand newCommand, RunCommand runCommand, AddCommand addC

if (waitForDebugger)
{
AnsiConsole.Status().Start(
InteractionUtils.ShowStatus(
$":bug: Waiting for debugger to attach to process ID: {Environment.ProcessId}",
context => {
() => {
while (!Debugger.IsAttached)
{
Thread.Sleep(1000);
}
}
);
});
}
});
#endif
Expand Down
30 changes: 12 additions & 18 deletions src/Aspire.Cli/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ namespace Aspire.Cli.Commands;
internal sealed class RunCommand : BaseCommand
{
private readonly ActivitySource _activitySource = new ActivitySource(nameof(RunCommand));
private readonly DotNetCliRunner _runner;
private readonly IDotNetCliRunner _runner;

public RunCommand(DotNetCliRunner runner)
public RunCommand(IDotNetCliRunner runner)
: base("run", "Run an Aspire app host in development mode.")
{
ArgumentNullException.ThrowIfNull(runner, nameof(runner));
Expand All @@ -36,7 +36,7 @@ public RunCommand(DotNetCliRunner runner)

protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
{
(bool IsCompatableAppHost, bool SupportsBackchannel, string? AspireHostingSdkVersion)? appHostCompatabilityCheck = null;
(bool IsCompatibleAppHost, bool SupportsBackchannel, string? AspireHostingSdkVersion)? appHostCompatibilityCheck = null;
try
{
using var activity = _activitySource.StartActivity();
Expand Down Expand Up @@ -87,9 +87,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
}
}

appHostCompatabilityCheck = await AppHostHelper.CheckAppHostCompatabilityAsync(_runner, effectiveAppHostProjectFile, cancellationToken);
appHostCompatibilityCheck = await AppHostHelper.CheckAppHostCompatibilityAsync(_runner, effectiveAppHostProjectFile, cancellationToken);

if (!appHostCompatabilityCheck?.IsCompatableAppHost ?? throw new InvalidOperationException("IsCompatableAppHost is null"))
if (!appHostCompatibilityCheck?.IsCompatibleAppHost ?? throw new InvalidOperationException("IsCompatibleAppHost is null"))
{
return ExitCodeConstants.FailedToDotnetRunAppHost;
}
Expand All @@ -109,20 +109,14 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
{
// We wait for the back channel to be created to signal that
// the AppHost is ready to accept requests.
var backchannel = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
.SpinnerStyle(Style.Parse("purple"))
.StartAsync(":linked_paperclips: Starting Aspire app host...", async context => {
return await backchannelCompletitionSource.Task;
});
var backchannel = await InteractionUtils.ShowStatusAsync(
":linked_paperclips: Starting Aspire app host...",
() => backchannelCompletitionSource.Task);

// We wait for the first update of the console model via RPC from the AppHost.
var dashboardUrls = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
.SpinnerStyle(Style.Parse("purple"))
.StartAsync(":chart_increasing: Starting Aspire dashboard...", async context => {
return await 10000 backchannel.GetDashboardUrlsAsync(cancellationToken);
});
var dashboardUrls = await InteractionUtils.ShowStatusAsync(
":chart_increasing: Starting Aspire dashboard...",
() => backchannel.GetDashboardUrlsAsync(cancellationToken));

AnsiConsole.WriteLine();
AnsiConsole.MarkupLine($"[green bold]Dashboard[/]:");
Expand Down Expand Up @@ -217,7 +211,7 @@ await AnsiConsole.Live(table).StartAsync(async context => {
{
return InteractionUtils.DisplayIncompatibleVersionError(
ex,
appHostCompatabilityCheck?.AspireHostingSdkVersion ?? throw new InvalidOperationException("AspireHostingSdkVersion is null")
appHostCompatibilityCheck?.AspireHostingSdkVersion ?? throw new InvalidOperationException("AspireHostingSdkVersion is null")
);
}
}
Expand Down
18 changes: 16 additions & 2 deletions src/Aspire.Cli/DotNetCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,21 @@

namespace Aspire.Cli;

internal sealed class DotNetCliRunner(ILogger<DotNetCliRunner> logger, IServiceProvider serviceProvider)
internal interface IDotNetCliRunner
{
Task<(int ExitCode, bool IsAspireHost, string? AspireHostingSdkVersion)> GetAppHostInformationAsync(FileInfo projectFile, CancellationToken cancellationToken);
Task<(int ExitCode, JsonDocument? Output)> GetProjectItemsAndPropertiesAsync(FileInfo projectFile, string[] items, string[] properties, CancellationToken cancellationToken);
Task<int> RunAsync(FileInfo projectFile, bool watch, bool noBuild, string[] args, IDictionary<string, string>? env, TaskCompletionSource<AppHostBackchannel>? backchannelCompletionSource, CancellationToken cancellationToken);
Task<int> CheckHttpCertificateAsync(CancellationToken cancellationToken);
Task<int> TrustHttpCertificateAsync(CancellationToken cancellationToken);
Task<(int ExitCode, string? TemplateVersion)> InstallTemplateAsync(string packageName, string version, string? nugetSource, bool force, CancellationToken cancellationToken);
Task<int> NewProjectAsync(string templateName, string name, string outputPath, CancellationToken cancellationToken);
Task<int> BuildAsync(FileInfo projectFilePath, CancellationToken cancellationToken);
Task<int> AddPackageAsync(FileInfo projectFilePath, string packageName, string packageVersion, CancellationToken cancellationToken);
Task<(int ExitCode, NuGetPackage[]? Packages)> SearchPackagesAsync(DirectoryInfo workingDirectory, string query, bool prerelease, int take, int skip, string? nugetSource, CancellationToken cancellationToken);
}

internal sealed class DotNetCliRunner(ILogger<DotNetCliRunner> logger, IServiceProvider serviceProvider) : IDotNetCliRunner
{
private readonly ActivitySource _activitySource = new ActivitySource(nameof(DotNetCliRunner));

Expand Down Expand Up @@ -478,7 +492,7 @@ private async Task StartBackchannelAsync(Process process, string socketPath, Tas
ex.RequiredCapability
);

// If the app host is incompatable then there is no point
// If the app host is incompatible then there is no point
// trying to reconnect, we should propogate the exception
// up to the code that needs to back channel so it can display
// and error message to the user.
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/NuGetPackageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal interface INuGetPackageCache
Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
}

internal sealed class NuGetPackageCache(ILogger<NuGetPackageCache> logger, DotNetCliRunner cliRunner) : INuGetPackageCache
internal sealed class NuGetPackageCache(ILogger<NuGetPackageCache> logger, IDotNetCliRunner cliRunner) : INuGetPackageCache
{
private readonly ActivitySource _activitySource = new(nameof(NuGetPackageCache));

Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private static IHost BuildApplication(string[] args)
}

// Shared services.
builder.Services.AddTransient<DotNetCliRunner>();
builder.Services.AddTransient<IDotNetCliRunner, DotNetCliRunner>();
builder.Services.AddTransient<AppHostBackchannel>();
builder.Services.AddSingleton<CliRpcTarget>();
builder.Services.AddTransient<INuGetPackageCache, NuGetPackageCache>();
Expand Down
Loading
Loading
0