8000 Introduce version selector for Aspire templates by mitchdenny · Pull Request #8625 · dotnet/aspire · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Introduce version selector for Aspire templates #8625

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 8, 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
4 changes: 2 additions & 2 deletions src/Aspire.Cli/Commands/AddCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell

var source = parseResult.GetValue<string?>("--source");

var packages = await AnsiConsole.Status().StartAsync(
var packages = await InteractionUtils.ShowStatusAsync(
"Searching for Aspire packages...",
context => _nuGetPackageCache.GetPackagesAsync(effectiveAppHostProjectFile.Directory!, prerelease, source, cancellationToken)
() => _nuGetPackageCache.GetIntegrationPackagesAsync(effectiveAppHostProjectFile.Directory!, prerelease, source, cancellationToken)
);

var version = parseResult.GetValue<string?>("--version");
Expand Down
31 changes: 15 additions & 16 deletions src/Aspire.Cli/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.CommandLine;
using System.Diagnostics;
using Aspire.Cli.Utils;
using Semver;
using Spectre.Console;

namespace Aspire.Cli.Commands;
Expand Down Expand Up @@ -43,6 +42,10 @@ public NewCommand(DotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
var templateVersionOption = new Option<string?>("--version", "-v");
templateVersionOption.Description = "The version of the project templates to use.";
Options.Add(templateVersionOption);

var prereleaseOption = new Option<bool>("--prerelease");
prereleaseOption.Description = "Include prerelease versions when searching for project templates.";
Options.Add(prereleaseOption);
}

private static async Task<(string TemplateName, string TemplateDescription, string? PathAppendage)> GetProjectTemplateAsync(ParseResult parseResult, CancellationToken cancellationToken)
Expand Down Expand Up @@ -106,28 +109,23 @@ private static async Task<string> GetOutputPathAsync(ParseResult parseResult, st
return Path.GetFullPath(outputPath);
}

private static async Task<string> GetProjectTemplatesVersionAsync(ParseResult parseResult, CancellationToken cancellationToken)
private async Task<string> GetProjectTemplatesVersionAsync(ParseResult parseResult, bool prerelease, string? source, CancellationToken cancellationToken)
{
if (parseResult.GetValue<string>("--version") is { } version)
{
return version;
}
else
{
version = await InteractionUtils.PromptForStringAsync(
"Project templates version:",
defaultValue: VersionHelper.GetDefaultTemplateVersion(),
validator: (string value) => {
if (SemVersion.TryParse(value, out var parsedVersion))
{
return ValidationResult.Success();
}

return ValidationResult.Error("Invalid version format. Please enter a valid version.");
},
cancellationToken);
var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);

return version;
var candidatePackages = await InteractionUtils.ShowStatusAsync(
"Searching for available project template versions...",
() => _nuGetPackageCache.GetTemplatePackagesAsync(workingDirectory, prerelease, source, cancellationToken)
);

var selectedPackage = await InteractionUtils.PromptForTemplatesVersionAsync(candidatePackages, cancellationToken);
return selectedPackage.Version;
}
}

Expand All @@ -138,8 +136,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
var template = await GetProjectTemplateAsync(parseResult, cancellationToken);
var name = await GetProjectNameAsync(parseResult, cancellationToken);
var outputPath = await GetOutputPathAsync(parseResult, template.PathAppendage, cancellationToken);
var version = await GetProjectTemplatesVersionAsync(parseResult, cancellationToken);
var prerelease = parseResult.GetValue<bool>("--prerelease");
var source = parseResult.GetValue<string?>("--source");
var version = await GetProjectTemplatesVersionAsync(parseResult, prerelease, source, cancellationToken);

var templateInstallResult = await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
Expand Down
22 changes: 17 additions & 5 deletions src/Aspire.Cli/NuGetPackageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace Aspire.Cli;

internal interface INuGetPackageCache
{
Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
}

internal sealed class NuGetPackageCache(ILogger<NuGetPackageCache> logger, DotNetCliRunner cliRunner) : INuGetPackageCache
Expand All @@ -17,7 +18,17 @@ internal sealed class NuGetPackageCache(ILogger<NuGetPackageCache> logger, DotNe

private const int SearchPageSize = 100;

public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
public async Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
{
return await GetPackagesAsync(workingDirectory, "Aspire.ProjectTemplates", prerelease, source, cancellationToken);
}

public async Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
{
return await GetPackagesAsync(workingDirectory, "Aspire.Hosting", prerelease, source, cancellationToken);
}

internal async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo workingDirectory, string query, bool prerelease, string? source, CancellationToken cancellationToken)
{
using var activity = _activitySource.StartActivity();

Expand All @@ -32,7 +43,7 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo work
// This search should pick up Aspire.Hosting.* and CommunityToolkit.Aspire.Hosting.*
var result = await cliRunner.SearchPackagesAsync(
workingDirectory,
"Aspire.Hosting",
query,
prerelease,
SearchPageSize,
skip,
Expand Down Expand Up @@ -69,8 +80,9 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo work

static bool IsOfficialOrCommunityToolkitPackage(string packageName)
{
var isHostingOrCommunityToolkitNamespaced = packageName.StartsWith("Aspire.Hosting.", StringComparison.OrdinalIgnoreCase) ||
packageName.StartsWith("CommunityToolkit.Aspire.Hosting.", StringComparison.OrdinalIgnoreCase);
var isHostingOrCommunityToolkitNamespaced = packageName.StartsWith("Aspire.Hosting.", StringComparison.Ordinal) ||
packageName.StartsWith("CommunityToolkit.Aspire.Hosting.", StringComparison.Ordinal) ||
packageName.Equals("Aspire.ProjectTemplates", StringComparison.Ordinal);

var isExcluded = packageName.StartsWith("Aspire.Hosting.AppHost") ||
packageName.StartsWith("Aspire.Hosting.Sdk") ||
Expand Down
18 changes: 18 additions & 0 deletions A92C src/Aspire.Cli/Utils/InteractionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ namespace Aspire.Cli.Utils;

internal static class InteractionUtils
{
public static async Task<T> ShowStatusAsync<T>(string statusText, Func<Task<T>> action)
{
return await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots3)
.SpinnerStyle(Style.Parse("purple"))
.StartAsync(statusText, (context) => action());
}

public static async Task<NuGetPackage> PromptForTemplatesVersionAsync(IEnumerable<NuGetPackage> candidatePackages, CancellationToken cancellationToken)
{
return await PromptForSelectionAsync(
"Select a template version:",
candidatePackages,
(p) => $"{p.Version} ({p.Source})",
cancellationToken
);
}

public static async Task<string> PromptForStringAsync(string promptText, string? defaultValue = null, Func<string, ValidationResult>? validator = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(promptText, nameof(promptText));
Expand Down
Loading
0