Skip to content

Commit 5201f07

Browse files
mitchdennydavidfowlCopilot
authored andcommitted
Introduce version selector for Aspire templates (#8625)
* Introduce version selecto to aspire new * Update src/Aspire.Cli/Commands/NewCommand.cs Co-authored-by: Copilot <[email protected]> * Update src/Aspire.Cli/Commands/NewCommand.cs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: David Fowler <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 4ba7d9a commit 5201f07

File tree

4 files changed

+52
-23
lines changed

4 files changed

+52
-23
lines changed

src/Aspire.Cli/Commands/AddCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
6666

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

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

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

src/Aspire.Cli/Commands/NewCommand.cs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.CommandLine;
55
using System.Diagnostics;
66
using Aspire.Cli.Utils;
7-
using Semver;
87
using Spectre.Console;
98

109
namespace Aspire.Cli.Commands;
@@ -43,6 +42,10 @@ public NewCommand(DotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
4342
var templateVersionOption = new Option<string?>("--version", "-v");
4443
templateVersionOption.Description = "The version of the project templates to use.";
4544
Options.Add(templateVersionOption);
45+
46+
var prereleaseOption = new Option<bool>("--prerelease");
47+
prereleaseOption.Description = "Include prerelease versions when searching for project templates.";
48+
Options.Add(prereleaseOption);
4649
}
4750

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

109-
private static async Task<string> GetProjectTemplatesVersionAsync(ParseResult parseResult, CancellationToken cancellationToken)
112+
private async Task<string> GetProjectTemplatesVersionAsync(ParseResult parseResult, bool prerelease, string? source, CancellationToken cancellationToken)
110113
{
111114
if (parseResult.GetValue<string>("--version") is { } version)
112115
{
113116
return version;
114117
}
115118
else
116119
{
117-
version = await InteractionUtils.PromptForStringAsync(
118-
"Project templates version:",
119-
defaultValue: VersionHelper.GetDefaultTemplateVersion(),
120-
validator: (string value) => {
121-
if (SemVersion.TryParse(value, out var parsedVersion))
122-
{
123-
return ValidationResult.Success();
124-
}
125-
126-
return ValidationResult.Error("Invalid version format. Please enter a valid version.");
127-
},
128-
cancellationToken);
120+
var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);
129121

130-
return version;
122+
var candidatePackages = await InteractionUtils.ShowStatusAsync(
123+
"Searching for available project template versions...",
124+
() => _nuGetPackageCache.GetTemplatePackagesAsync(workingDirectory, prerelease, source, cancellationToken)
125+
);
126+
127+
var selectedPackage = await InteractionUtils.PromptForTemplatesVersionAsync(candidatePackages, cancellationToken);
128+
return selectedPackage.Version;
131129
}
132130
}
133131

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

144143
var templateInstallResult = await AnsiConsole.Status()
145144
.Spinner(Spinner.Known.Dots3)

src/Aspire.Cli/NuGetPackageCache.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ namespace Aspire.Cli;
88

99
internal interface INuGetPackageCache
1010
{
11-
Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
11+
Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
12+
Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken);
1213
}
1314

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

1819
private const int SearchPageSize = 100;
1920

20-
public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
21+
public async Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
22+
{
23+
return await GetPackagesAsync(workingDirectory, "Aspire.ProjectTemplates", prerelease, source, cancellationToken);
24+
}
25+
26+
public async Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
27+
{
28+
return await GetPackagesAsync(workingDirectory, "Aspire.Hosting", prerelease, source, cancellationToken);
29+
}
30+
31+
internal async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo workingDirectory, string query, bool prerelease, string? source, CancellationToken cancellationToken)
2132
{
2233
using var activity = _activitySource.StartActivity();
2334

@@ -32,7 +43,7 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo work
3243
// This search should pick up Aspire.Hosting.* and CommunityToolkit.Aspire.Hosting.*
3344
var result = await cliRunner.SearchPackagesAsync(
3445
workingDirectory,
35-
"Aspire.Hosting",
46+
query,
3647
prerelease,
3748
SearchPageSize,
3849
skip,
@@ -69,8 +80,9 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(DirectoryInfo work
6980

7081
static bool IsOfficialOrCommunityToolkitPackage(string packageName)
7182
{
72-
var isHostingOrCommunityToolkitNamespaced = packageName.StartsWith("Aspire.Hosting.", StringComparison.OrdinalIgnoreCase) ||
73-
packageName.StartsWith("CommunityToolkit.Aspire.Hosting.", StringComparison.OrdinalIgnoreCase);
83+
var isHostingOrCommunityToolkitNamespaced = packageName.StartsWith("Aspire.Hosting.", StringComparison.Ordinal) ||
84+
packageName.StartsWith("CommunityToolkit.Aspire.Hosting.", StringComparison.Ordinal) ||
85+
packageName.Equals("Aspire.ProjectTemplates", StringComparison.Ordinal);
7486

7587
var isExcluded = packageName.StartsWith("Aspire.Hosting.AppHost") ||
7688
packageName.StartsWith("Aspire.Hosting.Sdk") ||

src/Aspire.Cli/Utils/InteractionUtils.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ namespace Aspire.Cli.Utils;
88

99
internal static class InteractionUtils
1010
{
11+
public static async Task<T> ShowStatusAsync<T>(string statusText, Func<Task<T>> action)
12+
{
13+
return await AnsiConsole.Status()
14+
.Spinner(Spinner.Known.Dots3)
15+
.SpinnerStyle(Style.Parse("purple"))
16+
.StartAsync(statusText, (context) => action());
17+
}
18+
19+
public static async Task<NuGetPackage> PromptForTemplatesVersionAsync(IEnumerable<NuGetPackage> candidatePackages, CancellationToken cancellationToken)
20+
{
21+
return await PromptForSelectionAsync(
22+
"Select a template version:",
23+
candidatePackages,
24+
(p) => $"{p.Version} ({p.Source})",
25+
cancellationToken
26+
);
27+
}
28+
1129
public static async Task<string> PromptForStringAsync(string promptText, string? defaultValue = null, Func<string, ValidationResult>? validator = null, CancellationToken cancellationToken = default)
1230
{
1331
ArgumentNullException.ThrowIfNull(promptText, nameof(promptText));

0 commit comments

Comments
 (0)