From 9ae2da0b3224af237d9416362bb005d448cfc0c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 00:30:14 +0000 Subject: [PATCH 1/9] Initial plan From a2df3c73b155d472d10ec275e6f47f903efd1cac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 00:46:03 +0000 Subject: [PATCH 2/9] Add resource strings and update interaction service for CLI update prompts Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- src/Aspire.Cli/Commands/UpdateCommand.cs | 35 +++++++++++++++++ .../Interaction/ConsoleInteractionService.cs | 8 +++- .../ExtensionInteractionService.cs | 4 +- .../Interaction/IInteractionService.cs | 2 +- .../InteractionServiceStrings.Designer.cs | 9 +++++ .../Resources/InteractionServiceStrings.resx | 4 ++ .../UpdateCommandStrings.Designer.cs | 2 + .../Resources/UpdateCommandStrings.resx | 6 +++ .../xlf/InteractionServiceStrings.cs.xlf | 5 +++ .../xlf/InteractionServiceStrings.de.xlf | 5 +++ .../xlf/InteractionServiceStrings.es.xlf | 5 +++ .../xlf/InteractionServiceStrings.fr.xlf | 5 +++ .../xlf/InteractionServiceStrings.it.xlf | 5 +++ .../xlf/InteractionServiceStrings.ja.xlf | 5 +++ .../xlf/InteractionServiceStrings.ko.xlf | 5 +++ .../xlf/InteractionServiceStrings.pl.xlf | 5 +++ .../xlf/InteractionServiceStrings.pt-BR.xlf | 5 +++ .../xlf/InteractionServiceStrings.ru.xlf | 5 +++ .../xlf/InteractionServiceStrings.tr.xlf | 5 +++ .../xlf/InteractionServiceStrings.zh-Hans.xlf | 5 +++ .../xlf/InteractionServiceStrings.zh-Hant.xlf | 5 +++ .../Resources/xlf/UpdateCommandStrings.cs.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.de.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.es.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.fr.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.it.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.ja.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.ko.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.pl.xlf | 10 +++++ .../xlf/UpdateCommandStrings.pt-BR.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.ru.xlf | 10 +++++ .../Resources/xlf/UpdateCommandStrings.tr.xlf | 10 +++++ .../xlf/UpdateCommandStrings.zh-Hans.xlf | 10 +++++ .../xlf/UpdateCommandStrings.zh-Hant.xlf | 10 +++++ src/Aspire.Cli/Utils/CliUpdateNotifier.cs | 38 ++++++++++++++++++- .../Commands/NewCommandTests.cs | 2 +- ...PublishCommandPromptingIntegrationTests.cs | 2 +- .../Templating/DotNetTemplateFactoryTests.cs | 2 +- .../TestConsoleInteractionService.cs | 2 +- .../TestExtensionInteractionService.cs | 2 +- 40 files changed, 303 insertions(+), 10 deletions(-) diff --git a/src/Aspire.Cli/Commands/UpdateCommand.cs b/src/Aspire.Cli/Commands/UpdateCommand.cs index f3272a04476..7c61ffb8724 100644 --- a/src/Aspire.Cli/Commands/UpdateCommand.cs +++ b/src/Aspire.Cli/Commands/UpdateCommand.cs @@ -24,6 +24,7 @@ internal sealed class UpdateCommand : BaseCommand private readonly IProjectUpdater _projectUpdater; private readonly ILogger _logger; private readonly ICliDownloader? _cliDownloader; + private readonly ICliUpdateNotifier _updateNotifier; public UpdateCommand( IProjectLocator projectLocator, @@ -41,12 +42,14 @@ public UpdateCommand( ArgumentNullException.ThrowIfNull(packagingService); ArgumentNullException.ThrowIfNull(projectUpdater); ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(updateNotifier); _projectLocator = projectLocator; _packagingService = packagingService; _projectUpdater = projectUpdater; _logger = logger; _cliDownloader = cliDownloader; + _updateNotifier = updateNotifier; var projectOption = new Option("--project"); projectOption.Description = UpdateCommandStrings.ProjectArgumentDescription; @@ -116,6 +119,20 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell cancellationToken); await _projectUpdater.UpdateProjectAsync(projectFile!, channel, cancellationToken); + + // After successful project update, check if CLI update is available and prompt + if (_cliDownloader is not null && _updateNotifier.IsUpdateAvailable()) + { + var shouldUpdateCli = await InteractionService.ConfirmAsync( + UpdateCommandStrings.UpdateCliAfterProjectUpdatePrompt, + defaultValue: true, + cancellationToken); + + if (shouldUpdateCli) + { + return await ExecuteSelfUpdateAsync(parseResult, cancellationToken); + } + } } catch (ProjectUpdaterException ex) { @@ -125,6 +142,24 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell } catch (ProjectLocatorException ex) { + // Check if this is a "no project found" error and prompt for self-update + if (string.Equals(ex.Message, ErrorStrings.NoProjectFileFound, StringComparisons.CliInputOrOutput)) + { + // Only prompt for self-update if not running as dotnet tool and downloader is available + if (_cliDownloader is not null) + { + var shouldUpdateCli = await InteractionService.ConfirmAsync( + UpdateCommandStrings.NoAppHostFoundUpdateCliPrompt, + defaultValue: true, + cancellationToken); + + if (shouldUpdateCli) + { + return await ExecuteSelfUpdateAsync(parseResult, cancellationToken); + } + } + } + return HandleProjectLocatorException(ex, InteractionService); } diff --git a/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs b/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs index 01f65a6b816..0ef62dfb683 100644 --- a/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs +++ b/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs @@ -243,10 +243,16 @@ public void DisplayEmptyLine() private const string UpdateUrl = "https://aka.ms/aspire/update"; - public void DisplayVersionUpdateNotification(string newerVersion) + public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null) { _ansiConsole.WriteLine(); _ansiConsole.MarkupLine(string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.NewCliVersionAvailable, newerVersion)); + + if (!string.IsNullOrEmpty(updateCommand)) + { + _ansiConsole.MarkupLine(string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.ToUpdateRunCommand, updateCommand)); + } + _ansiConsole.MarkupLine(string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.MoreInfoNewCliVersion, UpdateUrl)); } } diff --git a/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs b/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs index b782cf45c62..b253c7d5a00 100644 --- a/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs +++ b/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs @@ -308,9 +308,9 @@ public void DisplayMarkdown(string markdown) _consoleInteractionService.DisplayMarkdown(markdown); } - public void DisplayVersionUpdateNotification(string newerVersion) + public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null) { - _consoleInteractionService.DisplayVersionUpdateNotification(newerVersion); + _consoleInteractionService.DisplayVersionUpdateNotification(newerVersion, updateCommand); } public void LogMessage(LogLevel logLevel, string message) diff --git a/src/Aspire.Cli/Interaction/IInteractionService.cs b/src/Aspire.Cli/Interaction/IInteractionService.cs index 0dadd0775a7..50e5dfdaff9 100644 --- a/src/Aspire.Cli/Interaction/IInteractionService.cs +++ b/src/Aspire.Cli/Interaction/IInteractionService.cs @@ -25,6 +25,6 @@ internal interface IInteractionService void DisplayCancellationMessage(); void DisplayEmptyLine(); - void DisplayVersionUpdateNotification(string newerVersion); + void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null); void WriteConsoleLog(string message, int? lineNumber = null, string? type = null, bool isErrorMessage = false); } diff --git a/src/Aspire.Cli/Resources/InteractionServiceStrings.Designer.cs b/src/Aspire.Cli/Resources/InteractionServiceStrings.Designer.cs index 600c46cfad0..bb4474823b3 100644 --- a/src/Aspire.Cli/Resources/InteractionServiceStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/InteractionServiceStrings.Designer.cs @@ -338,5 +338,14 @@ public static string WaitingForDebuggerToAttachToAppHost { return ResourceManager.GetString("WaitingForDebuggerToAttachToAppHost", resourceCulture); } } + + /// + /// Looks up a localized string similar to [dim]To update, run: {0}[/]. + /// + public static string ToUpdateRunCommand { + get { + return ResourceManager.GetString("ToUpdateRunCommand", resourceCulture); + } + } } } diff --git a/src/Aspire.Cli/Resources/InteractionServiceStrings.resx b/src/Aspire.Cli/Resources/InteractionServiceStrings.resx index c1409e5776d..c718ff5ca29 100644 --- a/src/Aspire.Cli/Resources/InteractionServiceStrings.resx +++ b/src/Aspire.Cli/Resources/InteractionServiceStrings.resx @@ -208,6 +208,10 @@ [dim]For more information, see: [link]{0}[/][/] Do not translate [dim] and [link]. Also leave [/] as-is. {0} is a URL + + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + No app hosts were found (there may be app hosts project files with syntax errors/invalid SDK versions). diff --git a/src/Aspire.Cli/Resources/UpdateCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/UpdateCommandStrings.Designer.cs index e18496b9838..823b05bf397 100644 --- a/src/Aspire.Cli/Resources/UpdateCommandStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/UpdateCommandStrings.Designer.cs @@ -99,5 +99,7 @@ internal static string ProjectArgumentDescription { internal static string MappingRemovedFormat => ResourceManager.GetString("MappingRemovedFormat", resourceCulture); internal static string MappingRetainedFormat => ResourceManager.GetString("MappingRetainedFormat", resourceCulture); internal static string FallbackParsingWarning => ResourceManager.GetString("FallbackParsingWarning", resourceCulture); + internal static string NoAppHostFoundUpdateCliPrompt => ResourceManager.GetString("NoAppHostFoundUpdateCliPrompt", resourceCulture); + internal static string UpdateCliAfterProjectUpdatePrompt => ResourceManager.GetString("UpdateCliAfterProjectUpdatePrompt", resourceCulture); } } diff --git a/src/Aspire.Cli/Resources/UpdateCommandStrings.resx b/src/Aspire.Cli/Resources/UpdateCommandStrings.resx index a7dde2726fd..b008782aa1d 100644 --- a/src/Aspire.Cli/Resources/UpdateCommandStrings.resx +++ b/src/Aspire.Cli/Resources/UpdateCommandStrings.resx @@ -114,4 +114,10 @@ Note: Update plan generated using fallback parsing due to unresolvable AppHost SDK. Dependency analysis may have reduced accuracy. + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + + An update is available for the Aspire CLI. Would you like to update it now? + diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.cs.xlf index b9392611ebb..71992649c07 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.cs.xlf @@ -137,6 +137,11 @@ Zastavuje se Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Vytváří se vztah důvěryhodnosti k certifikátům... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.de.xlf index cabbf21608a..88333412831 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.de.xlf @@ -137,6 +137,11 @@ Aspire wird beendet. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Zertifikate werden als vertrauenswürdig eingestuft... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.es.xlf index 67c25c40013..d78f3a7571e 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.es.xlf @@ -137,6 +137,11 @@ Deteniendo Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Confiar en certificados... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.fr.xlf index b5019ce86b4..68c15af41dc 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.fr.xlf @@ -137,6 +137,11 @@ Arrêt d’Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Approbation des certificats... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.it.xlf index 287d02de9eb..444d6df5fb0 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.it.xlf @@ -137,6 +137,11 @@ Arresto di Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Conferma dell'attendibilità dei certificati in corso... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ja.xlf index 0987dd0d29d..dd70e409f19 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ja.xlf @@ -137,6 +137,11 @@ Aspire を停止しています。 + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... 証明書の信頼を処理中です... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ko.xlf index 1cf27893c44..44bc0ce8e42 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ko.xlf @@ -137,6 +137,11 @@ Aspire를 중지하는 중입니다. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... 인증서를 신뢰하는 중... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pl.xlf index 96e907c278a..6e3cf80b63c 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pl.xlf @@ -137,6 +137,11 @@ Zatrzymywanie platformy Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Ufanie certyfikatom... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pt-BR.xlf index 446fd104ee6..a37f795c2d7 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.pt-BR.xlf @@ -137,6 +137,11 @@ Interrompendo o Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Confiando nos certificados... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ru.xlf index 686c7cd4c10..3190188c29a 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.ru.xlf @@ -137,6 +137,11 @@ Производится остановка Aspire. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Выполняется доверие сертификатам... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.tr.xlf index 7c641303d64..6aab52c79e5 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.tr.xlf @@ -137,6 +137,11 @@ Aspire durduruluyor. + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... Sertifikalara güveniliyor... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hans.xlf index 1b04a2e41ef..62f55aacaa3 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hans.xlf @@ -137,6 +137,11 @@ 正在停止 Aspire。 + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... 正在信任证书... diff --git a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hant.xlf index 4cdef524be3..c65595e6c0a 100644 --- a/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/InteractionServiceStrings.zh-Hant.xlf @@ -137,6 +137,11 @@ 正在停止 Aspire。 + + [dim]To update, run: {0}[/] + [dim]To update, run: {0}[/] + Do not translate [dim]. Also leave [/] as-is. {0} is the command to run + Trusting certificates... 信任憑證... diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.cs.xlf index bc71025f673..bfe4677fd1a 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.cs.xlf @@ -97,6 +97,11 @@ Mapování: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config V souboru NuGet.config se nezjistily žádné změny. @@ -157,6 +162,11 @@ Neočekávaná cesta kódu. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Aktualizovat balíček {0} z {1} na {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.de.xlf index 37ce2c7e70f..e052eeafc06 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.de.xlf @@ -97,6 +97,11 @@ Zuordnung: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config In NuGet.config wurden keine Änderungen festgestellt. @@ -157,6 +162,11 @@ Unerwarteter Codepfad + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Paket {0} von {1} auf {2} aktualisieren diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.es.xlf index 0090072ba0e..66d02d56dd6 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.es.xlf @@ -97,6 +97,11 @@ Asignación: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config No se detectaron cambios en NuGet.config @@ -157,6 +162,11 @@ Ruta de acceso al código inesperada. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Actualizar paquete {0} de {1} a {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.fr.xlf index 3cb8ac57456..9a4214d1227 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.fr.xlf @@ -97,6 +97,11 @@ Mappage : {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config Aucune modification n’a été détectée dans NuGet.config @@ -157,6 +162,11 @@ Chemin de code inattendu. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Mettre à jour le package {0} de {1} à {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.it.xlf index c8ac74cee07..a9e390bcd4e 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.it.xlf @@ -97,6 +97,11 @@ Mapping: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config Nessuna modifica rilevata in NuGet.config @@ -157,6 +162,11 @@ Percorso del codice imprevisto. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Aggiorna il pacchetto {0} da {1} a {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ja.xlf index db386eeef55..86c692f7f2d 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ja.xlf @@ -97,6 +97,11 @@ マッピング: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config NuGet.config に変更は検出されませんでした @@ -157,6 +162,11 @@ 予期しないコード パスです。 + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} パッケージ {0} を {1} から {2} に更新する diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ko.xlf index f7c23864ac9..c50482ded37 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ko.xlf @@ -97,6 +97,11 @@ 매핑: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config NuGet.config에서 변경 내용이 검색되지 않음 @@ -157,6 +162,11 @@ 예기치 않은 코드 경로입니다. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} 패키지 {0}을(를) {1}에서 {2}(으)로 업데이트 diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pl.xlf index 727fe1f3fc2..c14c49085c5 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pl.xlf @@ -97,6 +97,11 @@ Mapowanie: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config Nie wykryto żadnych zmian w pliku NuGet.config @@ -157,6 +162,11 @@ Nieoczekiwana ścieżka w kodzie. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Aktualizacja {0} z {1} do {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pt-BR.xlf index a1b80b9a975..31d7822cf09 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.pt-BR.xlf @@ -97,6 +97,11 @@ Mapeamento: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config Nenhuma alteração detectada no NuGet.config @@ -157,6 +162,11 @@ Caminho de código inesperado. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Atualizar o pacote {0} de {1} para {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ru.xlf index 450b502695c..d2e6723837f 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.ru.xlf @@ -97,6 +97,11 @@ Сопоставление: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config Изменений в NuGet.config не обнаружено @@ -157,6 +162,11 @@ Непредвиденный путь к коду. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} Обновить пакет {0} с{1} до {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.tr.xlf index 8b53b4fa1d1..ba2e6c4691e 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.tr.xlf @@ -97,6 +97,11 @@ Eşleme: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config NuGet.config dosyasında değişiklik algılanmadı @@ -157,6 +162,11 @@ Beklenmeyen kod yolu. + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} {1} olan {0} paketini {2} olarak güncelleyin diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hans.xlf index d0663526544..bf6e2ed0580 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hans.xlf @@ -97,6 +97,11 @@ 映射: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config NuGet.config 中未检测到任何更改 @@ -157,6 +162,11 @@ 意外的代码路径。 + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} 将包 {0} 从 {1} 更新为 {2} diff --git a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hant.xlf index 11bc2dd1594..f9bf326e05e 100644 --- a/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/UpdateCommandStrings.zh-Hant.xlf @@ -97,6 +97,11 @@ 對應: {0} + + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + No Aspire AppHost project found. Would you like to update the Aspire CLI instead? + + No changes detected in NuGet.config 未偵測到 NuGet.config 中的變更 @@ -157,6 +162,11 @@ 未預期的程式碼路徑。 + + An update is available for the Aspire CLI. Would you like to update it now? + An update is available for the Aspire CLI. Would you like to update it now? + + Update package {0} from {1} to {2} 將套件 {0} 從 {1} 更新到 {2} diff --git a/src/Aspire.Cli/Utils/CliUpdateNotifier.cs b/src/Aspire.Cli/Utils/CliUpdateNotifier.cs index 0b39db95c38..de9537e0b01 100644 --- a/src/Aspire.Cli/Utils/CliUpdateNotifier.cs +++ b/src/Aspire.Cli/Utils/CliUpdateNotifier.cs @@ -13,6 +13,7 @@ internal interface ICliUpdateNotifier { Task CheckForCliUpdatesAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken); void NotifyIfUpdateAvailable(); + bool IsUpdateAvailable(); } internal class CliUpdateNotifier( @@ -49,10 +50,45 @@ public void NotifyIfUpdateAvailable() if (newerVersion is not null) { - interactionService.DisplayVersionUpdateNotification(newerVersion.ToString()); + var updateCommand = IsRunningAsDotNetTool() + ? "dotnet tool update -g Aspire.Cli.Tool" + : "aspire update --self"; + + interactionService.DisplayVersionUpdateNotification(newerVersion.ToString(), updateCommand); } } + public bool IsUpdateAvailable() + { + if (_availablePackages is null) + { + return false; + } + + var currentVersion = GetCurrentVersion(); + if (currentVersion is null) + { + return false; + } + + var newerVersion = PackageUpdateHelpers.GetNewerVersion(currentVersion, _availablePackages); + return newerVersion is not null; + } + + private static bool IsRunningAsDotNetTool() + { + // When running as a dotnet tool, the process path points to "dotnet" or "dotnet.exe" + // When running as a native binary, it points to "aspire" or "aspire.exe" + var processPath = Environment.ProcessPath; + if (string.IsNullOrEmpty(processPath)) + { + return false; + } + + var fileName = Path.GetFileNameWithoutExtension(processPath); + return string.Equals(fileName, "dotnet", StringComparison.OrdinalIgnoreCase); + } + protected virtual SemVersion? GetCurrentVersion() { return PackageUpdateHelpers.GetCurrentPackageVersion(); diff --git a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs index 4a94c66b607..0028707cdc8 100644 --- a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs @@ -682,5 +682,5 @@ public void DisplayEmptyLine() { } public void DisplayPlainText(string text) { } public void DisplayMarkdown(string markdown) { } public void WriteConsoleLog(string message, int? lineNumber = null, string? type = null, bool isErrorMessage = false) { } - public void DisplayVersionUpdateNotification(string newerVersion) { } + public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null) { } } diff --git a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs index bfa8ccc7597..042f243d3e9 100644 --- a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs @@ -994,7 +994,7 @@ public void DisplayEmptyLine() { } public void DisplayPlainText(string text) { } public void DisplayMarkdown(string markdown) { } - public void DisplayVersionUpdateNotification(string newerVersion) { } + public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null) { } public void WriteConsoleLog(string message, int? lineNumber = null, string? type = null, bool isErrorMessage = false) { diff --git a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs index 08d2bf3e668..f8a2cfe33f1 100644 --- a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs +++ b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs @@ -396,7 +396,7 @@ public void DisplayPlainText(string text) { } public void DisplayMarkdown(string markdown) { } public void DisplaySubtleMessage(string message, bool escapeMarkup = true) { } public void DisplayEmptyLine() { } - public void DisplayVersionUpdateNotification(string message) { } + public void DisplayVersionUpdateNotification(string message, string? updateCommand = null) { } public void WriteConsoleLog(string message, int? resourceHashCode, string? resourceName, bool isError) { } } diff --git a/tests/Aspire.Cli.Tests/TestServices/TestConsoleInteractionService.cs b/tests/Aspire.Cli.Tests/TestServices/TestConsoleInteractionService.cs index c1ff12f4125..2984fda5e4f 100644 --- a/tests/Aspire.Cli.Tests/TestServices/TestConsoleInteractionService.cs +++ b/tests/Aspire.Cli.Tests/TestServices/TestConsoleInteractionService.cs @@ -107,7 +107,7 @@ public void WriteConsoleLog(string message, int? lineNumber = null, string? type public Action? DisplayVersionUpdateNotificationCallback { get; set; } - public void DisplayVersionUpdateNotification(string newerVersion) + public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null) { DisplayVersionUpdateNotificationCallback?.Invoke(newerVersion); } diff --git a/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs b/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs index 39e75efc7a2..aa96c446035 100644 --- a/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs +++ b/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs @@ -138,7 +138,7 @@ public void WriteConsoleLog(string message, int? lineNumber = null, string? type public Action? DisplayVersionUpdateNotificationCallback { get; set; } - public void DisplayVersionUpdateNotification(string newerVersion) + public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null) { DisplayVersionUpdateNotificationCallback?.Invoke(newerVersion); } From 6ac79e3397d928b8a5ef23aaa67a6c7efaafaa87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 00:48:38 +0000 Subject: [PATCH 3/9] Add tests for CLI update prompts functionality Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- .../Commands/UpdateCommandTests.cs | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs index 153b3b7238a..6b42a247ab1 100644 --- a/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs @@ -5,8 +5,10 @@ using Aspire.Cli.Commands; using Aspire.Cli.Packaging; using Aspire.Cli.Projects; +using Aspire.Cli.Resources; using Aspire.Cli.Tests.TestServices; using Aspire.Cli.Tests.Utils; +using Aspire.Cli.Utils; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Cli.Tests.Commands; @@ -155,6 +157,130 @@ private UpdateCommand CreateUpdateCommand(TemporaryWorkspace workspace) var provider = services.BuildServiceProvider(); return provider.GetRequiredService(); } + + [Fact] + public async Task UpdateCommand_WhenNoProjectFound_PromptsForCliSelfUpdate() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + + var confirmCallbackInvoked = false; + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => + { + options.ProjectLocatorFactory = _ => new TestProjectLocator() + { + UseOrFindAppHostProjectFileAsyncCallback = (projectFile, _, _) => + { + // Simulate no project found by throwing ProjectLocatorException + throw new ProjectLocatorException(ErrorStrings.NoProjectFileFound); + } + }; + + options.InteractionServiceFactory = _ => new TestConsoleInteractionService() + { + ConfirmCallback = (prompt, defaultValue) => + { + // Verify the correct prompt is shown + confirmCallbackInvoked = true; + Assert.Contains("Would you like to update the Aspire CLI", prompt); + return false; // User says no + } + }; + + options.DotNetCliRunnerFactory = _ => new TestDotNetCliRunner(); + }); + + var provider = services.BuildServiceProvider(); + + // Act + var command = provider.GetRequiredService(); + var result = command.Parse("update"); + + var exitCode = await result.InvokeAsync().WaitAsync(CliTestConstants.DefaultTimeout); + + // Assert + Assert.True(confirmCallbackInvoked, "Confirm prompt should have been shown"); + Assert.Equal(ExitCodeConstants.FailedToFindProject, exitCode); + } + + [Fact] + public async Task UpdateCommand_WhenProjectUpdatedSuccessfully_PromptsForCliUpdate() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + + var confirmCallbackInvoked = false; + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => + { + options.ProjectLocatorFactory = _ => new TestProjectLocator() + { + UseOrFindAppHostProjectFileAsyncCallback = (projectFile, _, _) => + { + return Task.FromResult(new FileInfo(Path.Combine(workspace.WorkspaceRoot.FullName, "AppHost.csproj"))); + } + }; + + options.InteractionServiceFactory = _ => new TestConsoleInteractionService() + { + ConfirmCallback = (prompt, defaultValue) => + { + confirmCallbackInvoked = true; + // Verify the correct prompt is shown after project update + Assert.Contains("An update is available for the Aspire CLI", prompt); + return false; // User says no + } + }; + + options.DotNetCliRunnerFactory = _ => new TestDotNetCliRunner(); + + options.ProjectUpdaterFactory = _ => new TestProjectUpdater() + { + UpdateProjectAsyncCallback = (projectFile, channel, cancellationToken) => + { + return Task.FromResult(new ProjectUpdateResult { UpdatedApplied = true }); + } + }; + + options.PackagingServiceFactory = _ => new TestPackagingService(); + + // Configure update notifier to report that an update is available + options.CliUpdateNotifierFactory = _ => new TestCliUpdateNotifier() + { + IsUpdateAvailableCallback = () => true + }; + }); + + var provider = services.BuildServiceProvider(); + + // Act + var command = provider.GetRequiredService(); + var result = command.Parse("update --project AppHost.csproj"); + + var exitCode = await result.InvokeAsync().WaitAsync(CliTestConstants.DefaultTimeout); + + // Assert + Assert.True(confirmCallbackInvoked, "Confirm prompt should have been shown after successful project update"); + Assert.Equal(0, exitCode); + } +} + +// Test implementation of ICliUpdateNotifier +internal sealed class TestCliUpdateNotifier : ICliUpdateNotifier +{ + public Func? IsUpdateAvailableCallback { get; set; } + + public Task CheckForCliUpdatesAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public void NotifyIfUpdateAvailable() + { + // No-op for tests + } + + public bool IsUpdateAvailable() + { + return IsUpdateAvailableCallback?.Invoke() ?? false; + } } // Test implementation of IProjectUpdater From a7f510b04c32ce8a9c53f20cddd5c3df583cd131 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 04:47:45 +0000 Subject: [PATCH 4/9] Add aspire-update test scenario for CLI update functionality Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- tests/agent-scenarios/aspire-update/prompt.md | 369 ++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 tests/agent-scenarios/aspire-update/prompt.md diff --git a/tests/agent-scenarios/aspire-update/prompt.md b/tests/agent-scenarios/aspire-update/prompt.md new file mode 100644 index 00000000000..8794c703d3b --- /dev/null +++ b/tests/agent-scenarios/aspire-update/prompt.md @@ -0,0 +1,369 @@ +# Aspire Update Scenario + +This scenario tests the `aspire update` command functionality, including updating projects across different CLI versions and testing the new CLI self-update prompting feature introduced in this PR. + +## Overview + +This test validates that: +1. The latest released version of the Aspire CLI can be acquired and used to create a new project +2. A new starter application can be created and runs successfully +3. The latest daily build of the Aspire CLI can be acquired and used to update the project +4. The PR build of the Aspire CLI can be acquired +5. The `aspire update` command correctly prompts to update the CLI when an update is available +6. The CLI can be "downgraded" back to the daily build through the update prompt +7. The dashboard correctly shows the version from the PR build even when using the daily CLI + +## Prerequisites + +Before starting, ensure you have: +- Docker installed and running (for container-based resources if used) +- Sufficient disk space for multiple CLI versions and application artifacts +- Network access to download NuGet packages and CLI builds +- Browser automation tools available (playwright) for verification + +**Note**: The .NET SDK is not required as a prerequisite - the Aspire CLI will install it automatically. + +## Step 1: Download the Latest Released Version of the CLI + +Acquire the latest stable release version of the Aspire CLI. + +**Follow the CLI acquisition instructions already provided in the aspire-playground repository to obtain the latest released version of the Aspire CLI (native AOT build).** + +Once acquired, verify the CLI is installed correctly: + +```bash +aspire --version +``` + +Expected output should show the latest released version number (e.g., `13.0.0` or similar). + +**Note the version number for comparison in later steps.** + +## Step 2: Create a New Starter Project + +Create a new Aspire starter application using the released CLI version. + +### 2.1 Run the Aspire New Command + +Use `aspire new` with interactive template selection. Choose any template randomly - for this test, we'll use whichever template the agent selects. + +```bash +aspire new +``` + +**Follow the interactive prompts:** +1. Select any starter template (e.g., `aspire-starter` or `aspire-py-starter`) +2. Provide a name for the application (suggestion: `AspireUpdateTest`) +3. Accept default options for framework, frontend, etc. + +### 2.2 Verify Project Creation + +After creation, verify the project structure exists: + +```bash +ls -la +``` + +Expected: Project files and directories should be created successfully. + +## Step 3: Run the AppHost to Verify It Works + +Launch the application to verify it works with the released CLI version. + +### 3.1 Start the Application + +```bash +aspire run +``` + +Wait for the application to start (30-60 seconds). Observe the console output for: +- Dashboard URL with access token +- All resources showing as "Running" +- No critical errors + +### 3.2 Verify Dashboard Access + +Navigate to the dashboard URL (from console output) and verify: +- Dashboard loads successfully +- Resources are running +- No errors in the dashboard + +**Take a screenshot of the dashboard:** + +```bash +playwright-browser navigate $DASHBOARD_URL +playwright-browser take_screenshot --filename dashboard-released-version.png +``` + +### 3.3 Stop the Application + +Press `Ctrl+C` to stop the application and verify it shuts down cleanly. + +## Step 4: Download the Latest Daily Build of the Aspire CLI + +Acquire the latest daily build version of the Aspire CLI. + +**Follow the CLI acquisition instructions already provided in the aspire-playground repository to obtain the latest daily build of the Aspire CLI (native AOT build).** + +This will replace the released version installed in Step 1. + +## Step 5: Check the Version Number to Verify Installation + +Verify the daily build is now installed: + +```bash +aspire --version +``` + +Expected output should show the daily build version number (e.g., `13.0.0-preview.1.xxxxx` or similar). + +**The version should be different from (and typically newer than) the released version noted in Step 1.** + +## Step 6: Use `aspire update` to Update the AppHost Project + +Update the project to use packages from the daily channel. + +### 6.1 Run aspire update + +```bash +aspire update +``` + +**Follow the interactive prompts:** +1. When prompted to select a channel, choose the **daily** channel +2. Confirm the updates when prompted + +**Observe the update process:** +- Package references being analyzed +- Updates being applied +- NuGet.config being updated +- Successful completion message + +**Note:** Since the project was originally created with the released version and we're now using the daily CLI, the update should find packages to update. + +## Step 7: Run the AppHost to Verify It Worked + +Launch the application again with the updated packages. + +### 7.1 Start the Application + +```bash +aspire run +``` + +Wait for startup and verify all resources are running. + +### 7.2 Navigate to Dashboard Help Menu + +Access the dashboard and check the version information. + +```bash +# Navigate to dashboard +playwright-browser navigate $DASHBOARD_URL + +# Navigate to the Help menu (typically in the top-right) +# Look for version information in the Help menu or About dialog +``` + +**Take a screenshot showing the dashboard version:** + +```bash +playwright-browser take_screenshot --filename dashboard-daily-version.png +``` + +**Verify:** The dashboard version should reflect the daily build packages (matching the CLI version from Step 5). + +### 7.3 Stop the Application + +Press `Ctrl+C` to stop the application. + +## Step 8: Download the PR Build of the CLI + +Acquire the CLI build from this PR. + +**Follow the CLI acquisition instructions already provided in the aspire-playground repository to obtain the PR build of the Aspire CLI (native AOT build).** + +This will replace the daily build installed in Step 4. + +## Step 9: Run `aspire --version` to Verify the Version Number + +Verify the PR build is now installed: + +```bash +aspire --version +``` + +Expected output should show the PR build version number (e.g., `13.0.0-pr.12395.xxxxx` or similar). + +**The version should be different from both the released and daily versions noted earlier.** + +## Step 10: Do `aspire update` to Update the AppHost + +Run the update command again with the PR build CLI. + +### 10.1 Run aspire update + +```bash +aspire update +``` + +**Expected behavior (NEW in this PR):** +- The project should be detected as up-to-date (no package updates needed since we just updated to daily) +- A prompt should appear: **"An update is available for the Aspire CLI. Would you like to update it now?"** + +**This is the NEW functionality being tested - the CLI detects that a newer version (daily) is available compared to the current PR build and prompts to update the CLI itself.** + +### 10.2 Respond Yes to the Prompt + +When prompted to update the CLI, answer **yes** (or `y`). + +**Observe the CLI self-update process:** +- Current CLI location displayed +- Quality level prompt (select "daily") +- Download progress +- Extraction and installation +- Backup of current CLI +- Success message with new version + +## Step 11: Check `aspire --version` - Should Be Back at Daily Build + +Verify the CLI has been updated back to the daily build: + +```bash +aspire --version +``` + +**Expected output:** The version should now match the daily build version from Step 5 (not the PR build from Step 9). + +**This confirms the CLI self-update functionality worked correctly.** + +## Step 12: Run `aspire run` with Daily CLI, Dashboard Shows PR Build + +Launch the application one more time to verify an important behavior. + +### 12.1 Start the Application + +```bash +aspire run +``` + +Wait for startup and verify all resources are running. + +### 12.2 Check Dashboard Version in Help Menu + +Access the dashboard and check the version information. + +```bash +playwright-browser navigate $DASHBOARD_URL +``` + +Navigate to the Help menu or About section and look for version information. + +**Take a screenshot:** + +```bash +playwright-browser take_screenshot --filename dashboard-pr-build-version.png +``` + +**Expected behavior:** Even though we're using the daily build CLI (Step 11), the dashboard should show the version from the **PR build** packages because that's what the project's packages were last updated to. + +**This demonstrates that:** +1. The CLI version (what's used to run the project) is independent of the package versions (what the project references) +2. Downgrading the CLI doesn't downgrade the project packages +3. The dashboard version reflects the package versions, not the CLI version + +### 12.3 Stop the Application + +Press `Ctrl+C` to stop the application. + +## Step 13: Final Verification Checklist + +Confirm all test objectives were met: + +- [ ] Latest released CLI acquired and version verified +- [ ] New project created successfully with released CLI +- [ ] Application ran successfully with released CLI +- [ ] Dashboard accessible with released version +- [ ] Latest daily build CLI acquired and version verified +- [ ] Project updated to daily channel successfully via `aspire update` +- [ ] Application ran successfully with daily build CLI and updated packages +- [ ] Dashboard showed daily build version after update +- [ ] PR build CLI acquired and version verified +- [ ] `aspire update` with PR build CLI detected project was up-to-date +- [ ] **CLI self-update prompt appeared (NEW FEATURE)** +- [ ] User answered yes to CLI update prompt +- [ ] CLI self-updated back to daily build +- [ ] `aspire --version` confirmed CLI is back at daily build version +- [ ] Application ran with daily build CLI +- [ ] **Dashboard showed PR build version even with daily CLI (important behavior)** + +## Success Criteria + +The test is considered **PASSED** if: + +1. **Released CLI**: Successfully acquired and used to create a working project +2. **Daily CLI**: Successfully acquired and used to update the project to daily channel +3. **PR CLI**: Successfully acquired and detected as older than daily build +4. **Update Prompt**: The CLI correctly prompted to update itself when running `aspire update` with an older CLI version (NEW FEATURE) +5. **Self-Update**: The CLI successfully updated itself to the daily build when user confirmed +6. **Version Independence**: The dashboard correctly showed PR build package version even when running with daily build CLI + +The test is considered **FAILED** if: + +- CLI acquisition fails for any version +- Project creation fails +- Project update fails +- **Update prompt does NOT appear when expected (this is the key new feature being tested)** +- CLI self-update fails or doesn't actually update the CLI +- Dashboard version doesn't correctly reflect package versions + +## Key Testing Points for This PR + +This scenario specifically tests the NEW functionality added in this PR: + +1. **Automatic CLI Update Detection**: After a successful `aspire update` of the project, the CLI checks if a newer CLI version is available +2. **User Prompt**: The CLI prompts the user to update the CLI itself with a clear, actionable message +3. **Self-Update Integration**: Accepting the prompt triggers the `aspire update --self` functionality +4. **Version Awareness**: The CLI correctly compares its own version against available versions + +## Notes for Agent Execution + +When executing this scenario as an automated agent: + +1. **Multiple CLI Versions**: Be prepared to handle multiple CLI installations and version switches +2. **Interactive Prompts**: Pay careful attention to prompts, especially the new CLI update prompt +3. **Version Tracking**: Track and compare version numbers at each step +4. **Screenshots**: Capture dashboard screenshots showing version information +5. **Confirmation**: When the CLI update prompt appears, this is the key moment - it should happen after a successful `aspire update` when using an older CLI version +6. **Expected Flow**: Released → Daily → PR → (update prompt) → Daily +7. **Version Comparison**: The dashboard version should reflect package versions, not CLI version + +## Troubleshooting Tips + +### CLI Update Prompt Doesn't Appear + +If the update prompt doesn't appear when expected: +- Verify the PR build is actually older than the daily build +- Check that the project update completed successfully first +- Ensure the CLI downloader is available (not running as dotnet tool) +- Check console output for any error messages + +### CLI Self-Update Fails + +If the self-update fails: +- Verify network connectivity for downloads +- Check disk space for installation +- Ensure proper permissions for file operations +- Review console output for specific error messages + +### Version Confusion + +If version numbers are confusing: +- Remember: CLI version (what runs the app) ≠ Package version (what the app references) +- The dashboard shows the Aspire.Hosting package version +- The CLI version is shown by `aspire --version` +- After CLI self-update, CLI version changes but package versions remain the same + +--- + +**End of Aspire Update Scenario** From 0b0d707bd9beb6f590b72c8ba2ab65d8c1638939 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:10:57 +0000 Subject: [PATCH 5/9] Add XML documentation for IsRunningAsDotNetTool and improve test scenario wording Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- src/Aspire.Cli/Utils/CliUpdateNotifier.cs | 17 +++++++++++++++++ tests/agent-scenarios/aspire-update/prompt.md | 15 ++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Aspire.Cli/Utils/CliUpdateNotifier.cs b/src/Aspire.Cli/Utils/CliUpdateNotifier.cs index de9537e0b01..7e4339a56da 100644 --- a/src/Aspire.Cli/Utils/CliUpdateNotifier.cs +++ b/src/Aspire.Cli/Utils/CliUpdateNotifier.cs @@ -75,6 +75,23 @@ public bool IsUpdateAvailable() return newerVersion is not null; } + /// + /// Determines whether the Aspire CLI is running as a .NET tool or as a native binary. + /// + /// + /// true if running as a .NET tool (process name is "dotnet" or "dotnet.exe"); + /// false if running as a native binary (process name is "aspire" or "aspire.exe") or if the process path cannot be determined. + /// + /// + /// This detection is used to determine which update command to display to users: + /// + /// .NET tool installation: "dotnet tool update -g Aspire.Cli.Tool" + /// Native binary installation: "aspire update --self" + /// + /// The detection works by examining , which returns the full path to the current executable. + /// When running as a .NET tool, this path points to the dotnet host executable. When running as a native binary, + /// it points to the aspire executable itself. + /// private static bool IsRunningAsDotNetTool() { // When running as a dotnet tool, the process path points to "dotnet" or "dotnet.exe" diff --git a/tests/agent-scenarios/aspire-update/prompt.md b/tests/agent-scenarios/aspire-update/prompt.md index 8794c703d3b..db6d7eb3087 100644 --- a/tests/agent-scenarios/aspire-update/prompt.md +++ b/tests/agent-scenarios/aspire-update/prompt.md @@ -9,8 +9,8 @@ This test validates that: 2. A new starter application can be created and runs successfully 3. The latest daily build of the Aspire CLI can be acquired and used to update the project 4. The PR build of the Aspire CLI can be acquired -5. The `aspire update` command correctly prompts to update the CLI when an update is available -6. The CLI can be "downgraded" back to the daily build through the update prompt +5. The `aspire update` command correctly prompts to update the CLI when a newer CLI version is available +6. The CLI can be updated to the daily build through the update prompt 7. The dashboard correctly shows the version from the PR build even when using the daily CLI ## Prerequisites @@ -35,7 +35,7 @@ Once acquired, verify the CLI is installed correctly: aspire --version ``` -Expected output should show the latest released version number (e.g., `13.0.0` or similar). +Expected output should show the latest released version number (e.g., `9.5.2` or similar - released versions do not have `-preview` or `-pr` suffixes, though other metadata may be present). **Note the version number for comparison in later steps.** @@ -83,18 +83,19 @@ Wait for the application to start (30-60 seconds). Observe the console output fo ### 3.2 Verify Dashboard Access -Navigate to the dashboard URL (from console output) and verify: +Navigate to the dashboard URL (from console output) and perform a minimal check: - Dashboard loads successfully -- Resources are running -- No errors in the dashboard +- Resources are listed and showing as running -**Take a screenshot of the dashboard:** +**Take a screenshot of the dashboard showing resources:** ```bash playwright-browser navigate $DASHBOARD_URL playwright-browser take_screenshot --filename dashboard-released-version.png ``` +**Note:** This is a minimal verification - we just want to confirm the dashboard launches and displays running resources. Detailed resource checks are not needed here. + ### 3.3 Stop the Application Press `Ctrl+C` to stop the application and verify it shuts down cleanly. From 1d765ecc362d195aed2ea4965fa0b7e9365abc86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:30:33 +0000 Subject: [PATCH 6/9] Remove --self flag from CLI update notification command for native binaries Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- src/Aspire.Cli/Utils/CliUpdateNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Cli/Utils/CliUpdateNotifier.cs b/src/Aspire.Cli/Utils/CliUpdateNotifier.cs index 7e4339a56da..31f9a09c649 100644 --- a/src/Aspire.Cli/Utils/CliUpdateNotifier.cs +++ b/src/Aspire.Cli/Utils/CliUpdateNotifier.cs @@ -52,7 +52,7 @@ public void NotifyIfUpdateAvailable() { var updateCommand = IsRunningAsDotNetTool() ? "dotnet tool update -g Aspire.Cli.Tool" - : "aspire update --self"; + : "aspire update"; interactionService.DisplayVersionUpdateNotification(newerVersion.ToString(), updateCommand); } From b9336d1c2a901189c8959ac2ce11af7dafc1c15d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 03:02:54 +0000 Subject: [PATCH 7/9] Add verification that updated apphost runs successfully in test scenario overview Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- tests/agent-scenarios/aspire-update/prompt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/agent-scenarios/aspire-update/prompt.md b/tests/agent-scenarios/aspire-update/prompt.md index db6d7eb3087..63f84b17683 100644 --- a/tests/agent-scenarios/aspire-update/prompt.md +++ b/tests/agent-scenarios/aspire-update/prompt.md @@ -7,7 +7,7 @@ This scenario tests the `aspire update` command functionality, including updatin This test validates that: 1. The latest released version of the Aspire CLI can be acquired and used to create a new project 2. A new starter application can be created and runs successfully -3. The latest daily build of the Aspire CLI can be acquired and used to update the project +3. The latest daily build of the Aspire CLI can be acquired and used to update the project, and the updated apphost can be run successfully 4. The PR build of the Aspire CLI can be acquired 5. The `aspire update` command correctly prompts to update the CLI when a newer CLI version is available 6. The CLI can be updated to the daily build through the update prompt From 19e8baa35337d3b60b02c80f4f980bedf6745243 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 03:08:18 +0000 Subject: [PATCH 8/9] Remove unnecessary prerequisite and clarify expected behavior text in test scenario Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com> --- tests/agent-scenarios/aspire-update/prompt.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/agent-scenarios/aspire-update/prompt.md b/tests/agent-scenarios/aspire-update/prompt.md index 63f84b17683..01646174044 100644 --- a/tests/agent-scenarios/aspire-update/prompt.md +++ b/tests/agent-scenarios/aspire-update/prompt.md @@ -17,7 +17,6 @@ This test validates that: Before starting, ensure you have: - Docker installed and running (for container-based resources if used) -- Sufficient disk space for multiple CLI versions and application artifacts - Network access to download NuGet packages and CLI builds - Browser automation tools available (playwright) for verification @@ -208,7 +207,7 @@ Run the update command again with the PR build CLI. aspire update ``` -**Expected behavior (NEW in this PR):** +**Expected behavior:** - The project should be detected as up-to-date (no package updates needed since we just updated to daily) - A prompt should appear: **"An update is available for the Aspire CLI. Would you like to update it now?"** From dbdf8513815dc01a03c5ac650497f3ef2f51aa50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 07:27:50 +0000 Subject: [PATCH 9/9] Use selected channel from project update for CLI self-update to avoid re-prompting Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- src/Aspire.Cli/Commands/UpdateCommand.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Aspire.Cli/Commands/UpdateCommand.cs b/src/Aspire.Cli/Commands/UpdateCommand.cs index 7c61ffb8724..b9ce09b319b 100644 --- a/src/Aspire.Cli/Commands/UpdateCommand.cs +++ b/src/Aspire.Cli/Commands/UpdateCommand.cs @@ -130,7 +130,8 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell if (shouldUpdateCli) { - return await ExecuteSelfUpdateAsync(parseResult, cancellationToken); + // Use the same channel that was selected for the project update + return await ExecuteSelfUpdateAsync(parseResult, cancellationToken, channel.Name); } } } @@ -166,9 +167,9 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell return 0; } - private async Task ExecuteSelfUpdateAsync(ParseResult parseResult, CancellationToken cancellationToken) + private async Task ExecuteSelfUpdateAsync(ParseResult parseResult, CancellationToken cancellationToken, string? selectedQuality = null) { - var quality = parseResult.GetValue("--quality"); + var quality = selectedQuality ?? parseResult.GetValue("--quality"); // If quality is not specified, prompt the user if (string.IsNullOrEmpty(quality))