From d341d2471b8fdb91088880417a284fedc71bed08 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 27 May 2026 17:58:39 +1000 Subject: [PATCH 1/4] Friendly error for 'aspire do --list-steps' without a step 'aspire do' is always step-targeted, so '--list-steps' with no step has no meaningful scope. The validator previously allowed it, which caused the CLI to launch the AppHost and race ahead executing the full pipeline before the CLI could fetch steps and stop. That race surfaced as 'InvalidOperationException: Sequence contains more than one matching element' from AzurePublishingContext (#17526). Tighten the DoCommand validator to always require the step argument (outside the extension host, which prompts interactively). When the user specifies '--list-steps' without a step, emit a friendly, localized error pointing at concrete examples: 'aspire do deploy --list-steps' or 'aspire do publish --list-steps'. Adds StepArgumentRequired and ListStepsRequiresStep entries to DoCommandStrings.resx + Designer.cs and refreshes all xlf translations via UpdateXlf. Tests: - DoCommandTests: new DoCommandWithListStepsAndNoStepArgumentShowsFriendlyError regression case; existing list-steps tests updated to pass a step. - ListStepsTests (E2E): single Docker-backed test now exercises 'aspire do --list-steps' (asserts friendly error and the absence of the 'Sequence contains more than one matching element' crash) plus 'aspire do deploy --list-steps', 'aspire publish --list-steps' and 'aspire deploy --list-steps' against a freshly created starter app. Fixes: #17526 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Cli/Commands/DoCommand.cs | 19 ++++++- .../Resources/DoCommandStrings.Designer.cs | 18 +++++++ .../Resources/DoCommandStrings.resx | 7 +++ .../Resources/xlf/DoCommandStrings.cs.xlf | 12 ++++- .../Resources/xlf/DoCommandStrings.de.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.es.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.fr.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.it.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.ja.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.ko.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.pl.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.pt-BR.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.ru.xlf | 10 ++++ .../Resources/xlf/DoCommandStrings.tr.xlf | 10 ++++ .../xlf/DoCommandStrings.zh-Hans.xlf | 10 ++++ .../xlf/DoCommandStrings.zh-Hant.xlf | 10 ++++ .../ListStepsTests.cs | 52 ++++++++++++++++--- .../Commands/DoCommandTests.cs | 30 +++++++++-- 18 files changed, 244 insertions(+), 14 deletions(-) diff --git a/src/Aspire.Cli/Commands/DoCommand.cs b/src/Aspire.Cli/Commands/DoCommand.cs index 51922787000..77d40daf48c 100644 --- a/src/Aspire.Cli/Commands/DoCommand.cs +++ b/src/Aspire.Cli/Commands/DoCommand.cs @@ -35,9 +35,24 @@ public DoCommand(IDotNetCliRunner runner, IInteractionService interactionService { var step = result.GetValue(_stepArgument); var listSteps = result.GetValue(s_listStepsOption); - if (string.IsNullOrEmpty(step) && !listSteps && !ExtensionHelper.IsExtensionHost(interactionService, out _, out _)) + if (string.IsNullOrEmpty(step) && !ExtensionHelper.IsExtensionHost(interactionService, out _, out _)) { - result.AddError("The 'step' argument is required when not using --list-steps."); + if (listSteps) + { + // `aspire do --list-steps` with no step has no meaningful scope: the listing for + // `do` is always relative to a target step. Surface a friendly error pointing at + // concrete well-known step names rather than launching the AppHost and crashing + // mid-pipeline (see https://github.com/microsoft/aspire/issues/17526). + result.AddError(string.Format( + System.Globalization.CultureInfo.CurrentCulture, + DoCommandStrings.ListStepsRequiresStep, + "deploy", + "publish")); + } + else + { + result.AddError(DoCommandStrings.StepArgumentRequired); + } } }); } diff --git a/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs index c7224cb9a1c..d9aaf906423 100644 --- a/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs @@ -122,5 +122,23 @@ public static string StepArgumentDescription { return ResourceManager.GetString("StepArgumentDescription", resourceCulture); } } + + /// + /// Looks up a localized string similar to The 'step' argument is required.. + /// + public static string StepArgumentRequired { + get { + return ResourceManager.GetString("StepArgumentRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'.. + /// + public static string ListStepsRequiresStep { + get { + return ResourceManager.GetString("ListStepsRequiresStep", resourceCulture); + } + } } } diff --git a/src/Aspire.Cli/Resources/DoCommandStrings.resx b/src/Aspire.Cli/Resources/DoCommandStrings.resx index 2db6a8643cd..ccd354800a8 100644 --- a/src/Aspire.Cli/Resources/DoCommandStrings.resx +++ b/src/Aspire.Cli/Resources/DoCommandStrings.resx @@ -138,4 +138,11 @@ The name of the step to execute + + The 'step' argument is required. + + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf index cded75496fa..1bc73786397 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf @@ -12,6 +12,11 @@ Provádí se kanál... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. Operace byla zrušena. @@ -37,6 +42,11 @@ Název kroku, který se má provést + + The 'step' argument is required. + The 'step' argument is required. + + - + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf index 86ce4dee647..752ebaa7ecb 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf @@ -12,6 +12,11 @@ Pipeline wird ausgeführt … + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. Der Vorgang wurde abgebrochen. @@ -37,6 +42,11 @@ Der Name des auszuführenden Schritts. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf index 5a2abd0fc26..ea19500a181 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf @@ -12,6 +12,11 @@ Ejecutando canalización... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. Operación cancelada. @@ -37,6 +42,11 @@ Nombre del paso que se va a ejecutar. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf index f1fdc9a1809..60c12f636d0 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf @@ -12,6 +12,11 @@ Exécution du pipeline... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. L'opération a été annulée. @@ -37,6 +42,11 @@ Nom de l’étape à exécuter. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf index f59033ec917..f0ec955c5a5 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf @@ -12,6 +12,11 @@ Esecuzione della pipeline in corso... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. L'operazione è stata annullata. @@ -37,6 +42,11 @@ Nome del passaggio da eseguire. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf index 66a8f27a628..c63c156aca6 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf @@ -12,6 +12,11 @@ パイプラインを実行しています... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. 操作は取り消されました。 @@ -37,6 +42,11 @@ 実行するステップの名前です。 + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf index 27f033fa266..005b98fb52b 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf @@ -12,6 +12,11 @@ 파이프라인을 실행하는 중... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. 작업이 취소되었습니다. @@ -37,6 +42,11 @@ 실행할 단계의 이름입니다. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf index 6a4c038846f..c97d382271a 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf @@ -12,6 +12,11 @@ Wykonywanie potoku... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. Operacja została anulowana. @@ -37,6 +42,11 @@ Nazwa kroku do wykonania. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf index 88c144bf453..d0203332e61 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf @@ -12,6 +12,11 @@ Executando pipeline... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. A operação foi cancelada. @@ -37,6 +42,11 @@ O nome da etapa a ser executada. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf index 84495e31d97..01176cdde2e 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf @@ -12,6 +12,11 @@ Выполнение конвейера... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. Операция была отменена. @@ -37,6 +42,11 @@ Имя выполняемого шага. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf index f9d7324b6ee..13f05f14168 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf @@ -12,6 +12,11 @@ İşlem hattı yürütülüyor... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. İşlem iptal edildi. @@ -37,6 +42,11 @@ Yürütülecek adımın adı. + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf index 19934085624..3c07bc54dc8 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf @@ -12,6 +12,11 @@ 正在执行管道... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. 该操作已取消。 @@ -37,6 +42,11 @@ 要执行的步骤名称。 + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf index 81f8d9ba37b..56e12f4ac0e 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf @@ -12,6 +12,11 @@ 正在執行管線... + + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. + {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The operation was canceled. 已取消作業。 @@ -37,6 +42,11 @@ 要執行的步驟名稱。 + + The 'step' argument is required. + The 'step' argument is required. + + \ No newline at end of file diff --git a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs index 63343a555aa..8b4dbfa6aca 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs @@ -9,13 +9,15 @@ namespace Aspire.Cli.EndToEnd.Tests; /// -/// End-to-end tests for the aspire do --list-steps feature. -/// Verifies that the CLI can list pipeline steps without executing them. +/// End-to-end tests for the --list-steps feature on the aspire do, publish, and deploy commands. +/// Verifies that the CLI can list pipeline steps without executing them, and that +/// invalid combinations (such as `aspire do --list-steps` without a step argument) +/// surface a friendly error instead of crashing. /// public sealed class ListStepsTests(ITestOutputHelper output) { [Fact] - public async Task DoListStepsShowsPipelineSteps() + public async Task DoPublishAndDeployListStepsWork() { var repoRoot = CliE2ETestHelpers.GetRepoRoot(); var strategy = CliInstallStrategy.Detect(output.WriteLine); @@ -40,17 +42,53 @@ public async Task DoListStepsShowsPipelineSteps() await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); - // Run aspire do deploy --list-steps + // 1. Regression for https://github.com/microsoft/aspire/issues/17526: + // `aspire do --list-steps` with no step argument should surface a friendly error + // pointing at concrete examples rather than crashing with + // 'Sequence contains more than one matching element'. + await auto.TypeAsync("aspire do --list-steps"); + await auto.EnterAsync(); + + await auto.WaitUntilAsync(s => + { + if (s.ContainsText("Sequence contains more than one matching element")) + { + throw new InvalidOperationException( + "aspire do --list-steps regressed: pipeline executed and crashed instead of surfacing the friendly validation error."); + } + return s.ContainsText("aspire do deploy --list-steps") + && s.ContainsText("aspire do publish --list-steps"); + }, timeout: TimeSpan.FromMinutes(2), + description: "waiting for friendly error suggesting concrete aspire do --list-steps examples"); + + // The validation error returns a non-zero exit code, but the shell prompt should come back. + await auto.WaitForAnyPromptAsync(counter); + + // 2. `aspire do --list-steps` lists pipeline steps for that step. await auto.TypeAsync("aspire do deploy --list-steps"); await auto.EnterAsync(); + await auto.WaitUntilAsync(s => + s.ContainsText("Depends on:") || s.ContainsText("No dependencies"), + timeout: TimeSpan.FromMinutes(3), + description: "waiting for aspire do deploy --list-steps output"); + await auto.WaitForSuccessPromptAsync(counter); - // Wait for the output to contain step information - // The output should contain numbered steps with dependencies + // 3. `aspire publish --list-steps` lists steps for the publish target. + await auto.TypeAsync("aspire publish --list-steps"); + await auto.EnterAsync(); await auto.WaitUntilAsync(s => s.ContainsText("Depends on:") || s.ContainsText("No dependencies"), timeout: TimeSpan.FromMinutes(3), - description: "waiting for --list-steps output with step dependency information"); + description: "waiting for aspire publish --list-steps output"); + await auto.WaitForSuccessPromptAsync(counter); + // 4. `aspire deploy --list-steps` lists steps for the deploy target. + await auto.TypeAsync("aspire deploy --list-steps"); + await auto.EnterAsync(); + await auto.WaitUntilAsync(s => + s.ContainsText("Depends on:") || s.ContainsText("No dependencies"), + timeout: TimeSpan.FromMinutes(3), + description: "waiting for aspire deploy --list-steps output"); await auto.WaitForSuccessPromptAsync(counter); // Exit the terminal diff --git a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs index 7356d6fd67f..4403cbc77d9 100644 --- a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs @@ -325,8 +325,8 @@ public async Task DoCommandWithListStepsReturnsZero() using var provider = services.BuildServiceProvider(); var command = provider.GetRequiredService(); - // Act - no step argument needed with --list-steps - var result = command.Parse("do --list-steps"); + // Act - step argument is required, even with --list-steps + var result = command.Parse("do deploy --list-steps"); var exitCode = await result.InvokeAsync().DefaultTimeout(); // Assert @@ -335,6 +335,28 @@ public async Task DoCommandWithListStepsReturnsZero() Assert.True(requestStopCalled.Task.IsCompleted, "RequestStopAsync should have been called"); } + [Fact] + public async Task DoCommandWithListStepsAndNoStepArgumentShowsFriendlyError() + { + // Regression for https://github.com/microsoft/aspire/issues/17526: + // `aspire do --list-steps` with no step argument used to launch the AppHost + // and crash mid-pipeline. It should now fail validation with a friendly + // error pointing at concrete examples. + using var tempRepo = TemporaryWorkspace.Create(outputHelper); + + var services = CliTestHelper.CreateServiceCollection(tempRepo, outputHelper); + using var provider = services.BuildServiceProvider(); + var command = provider.GetRequiredService(); + + var result = command.Parse("do --list-steps"); + + Assert.NotEmpty(result.Errors); + var combined = string.Join("\n", result.Errors.Select(e => e.Message)); + Assert.Contains("--list-steps", combined); + Assert.Contains("aspire do deploy --list-steps", combined); + Assert.Contains("aspire do publish --list-steps", combined); + } + [Fact] public async Task DoCommandWithListStepsAndStepArgumentReturnsZero() { @@ -431,7 +453,7 @@ public async Task DoCommandWithListStepsDoesNotExecutePipeline() using var provider = services.BuildServiceProvider(); var command = provider.GetRequiredService(); - var result = command.Parse("do --list-steps"); + var result = command.Parse("do deploy --list-steps"); var exitCode = await result.InvokeAsync().DefaultTimeout(); // Assert - pipeline should NOT have been executed @@ -489,7 +511,7 @@ public async Task DoCommandListStepsDisplaysCustomSteps() using var provider = services.BuildServiceProvider(); var command = provider.GetRequiredService(); - var result = command.Parse("do --list-steps"); + var result = command.Parse("do deploy --list-steps"); var exitCode = await result.InvokeAsync().DefaultTimeout(); Assert.Equal(0, exitCode); From 9415e770772f716c15b6fea360266deef37e42a3 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 27 May 2026 18:01:46 +1000 Subject: [PATCH 2/4] List all well-known step names in the friendly error Expand the 'aspire do --list-steps' validation error to enumerate every well-known pipeline step instead of just naming 'deploy' and 'publish'. The step names are hand-maintained in DoCommand alongside a comment pointing at src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs (the CLI does not reference Aspire.Hosting). Updated unit and E2E assertions to match the new message shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Cli/Commands/DoCommand.cs | 27 +++++++++++++++++-- .../Resources/DoCommandStrings.Designer.cs | 2 +- .../Resources/DoCommandStrings.resx | 4 +-- .../Resources/xlf/DoCommandStrings.cs.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.de.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.es.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.fr.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.it.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.ja.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.ko.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.pl.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.pt-BR.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.ru.xlf | 6 ++--- .../Resources/xlf/DoCommandStrings.tr.xlf | 6 ++--- .../xlf/DoCommandStrings.zh-Hans.xlf | 6 ++--- .../xlf/DoCommandStrings.zh-Hant.xlf | 6 ++--- .../ListStepsTests.cs | 4 +-- .../Commands/DoCommandTests.cs | 6 ++++- 18 files changed, 74 insertions(+), 47 deletions(-) diff --git a/src/Aspire.Cli/Commands/DoCommand.cs b/src/Aspire.Cli/Commands/DoCommand.cs index 77d40daf48c..3e20aaa1981 100644 --- a/src/Aspire.Cli/Commands/DoCommand.cs +++ b/src/Aspire.Cli/Commands/DoCommand.cs @@ -21,6 +21,30 @@ internal sealed class DoCommand : PipelineCommandBase private readonly Argument _stepArgument; + // Mirror of Aspire.Hosting.Pipelines.WellKnownPipelineSteps used only for the friendly + // validation message when `aspire do --list-steps` is invoked without a step. The CLI + // does not reference Aspire.Hosting, so this list is hand-maintained; keep in sync with + // src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs. Sorted alphabetically so the + // rendered error is easy to scan. + private static readonly string[] s_wellKnownStepNames = + [ + "before-start", + "build", + "build-prereq", + "check-container-runtime", + "deploy", + "deploy-prereq", + "destroy", + "destroy-prereq", + "diagnostics", + "process-parameters", + "publish", + "publish-prereq", + "push", + "push-prereq", + "validate-compute-environments" + ]; + public DoCommand(IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, AspireCliTelemetry telemetry, IFeatures features, ICliUpdateNotifier updateNotifier, CliExecutionContext executionContext, ICliHostEnvironment hostEnvironment, IAppHostProjectFactory projectFactory, IConfiguration configuration, ILogger logger, IAnsiConsole ansiConsole) : base("do", DoCommandStrings.Description, runner, interactionService, projectLocator, telemetry, features, updateNotifier, executionContext, hostEnvironment, projectFactory, configuration, logger, ansiConsole) { @@ -46,8 +70,7 @@ public DoCommand(IDotNetCliRunner runner, IInteractionService interactionService result.AddError(string.Format( System.Globalization.CultureInfo.CurrentCulture, DoCommandStrings.ListStepsRequiresStep, - "deploy", - "publish")); + string.Join(", ", s_wellKnownStepNames))); } else { diff --git a/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs index d9aaf906423..756c491e10c 100644 --- a/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs @@ -133,7 +133,7 @@ public static string StepArgumentRequired { } /// - /// Looks up a localized string similar to The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'.. + /// Looks up a localized string similar to The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}.. /// public static string ListStepsRequiresStep { get { diff --git a/src/Aspire.Cli/Resources/DoCommandStrings.resx b/src/Aspire.Cli/Resources/DoCommandStrings.resx index ccd354800a8..58d6af3170e 100644 --- a/src/Aspire.Cli/Resources/DoCommandStrings.resx +++ b/src/Aspire.Cli/Resources/DoCommandStrings.resx @@ -142,7 +142,7 @@ The 'step' argument is required. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf index 1bc73786397..cdc802d631b 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf index 752ebaa7ecb..58e741aa1f2 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf index ea19500a181..e7aa0fa2f0a 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf index 60c12f636d0..bfbb98ea466 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf index f0ec955c5a5..f03ab4c4311 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf index c63c156aca6..8d1704adb83 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf index 005b98fb52b..3dc51ba43f0 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf index c97d382271a..e556bbb4223 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf index d0203332e61..cea92774370 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf index 01176cdde2e..1b5e8bc7e5c 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf index 13f05f14168..86bb4054fb5 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf index 3c07bc54dc8..3d0fc8e926c 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf index 56e12f4ac0e..96621015318 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - The 'step' argument is required when using --list-steps. Specify the step you want to inspect, for example: 'aspire do {0} --list-steps' or 'aspire do {1} --list-steps'. - {0} and {1} are well-known pipeline step names such as 'deploy' and 'publish'. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. + {0} is a comma-separated list of well-known pipeline step names. The operation was canceled. diff --git a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs index 8b4dbfa6aca..e38dbb0bef1 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs @@ -57,9 +57,9 @@ await auto.WaitUntilAsync(s => "aspire do --list-steps regressed: pipeline executed and crashed instead of surfacing the friendly validation error."); } return s.ContainsText("aspire do deploy --list-steps") - && s.ContainsText("aspire do publish --list-steps"); + && s.ContainsText("Well-known step names include:"); }, timeout: TimeSpan.FromMinutes(2), - description: "waiting for friendly error suggesting concrete aspire do --list-steps examples"); + description: "waiting for friendly error listing well-known steps and an example"); // The validation error returns a non-zero exit code, but the shell prompt should come back. await auto.WaitForAnyPromptAsync(counter); diff --git a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs index 4403cbc77d9..10c4bef9eb1 100644 --- a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs @@ -354,7 +354,11 @@ public async Task DoCommandWithListStepsAndNoStepArgumentShowsFriendlyError() var combined = string.Join("\n", result.Errors.Select(e => e.Message)); Assert.Contains("--list-steps", combined); Assert.Contains("aspire do deploy --list-steps", combined); - Assert.Contains("aspire do publish --list-steps", combined); + // The error lists well-known step names; spot-check a few from different + // categories (publish/deploy/build/destroy) to confirm the list is rendered. + Assert.Contains("publish", combined); + Assert.Contains("build", combined); + Assert.Contains("destroy", combined); } [Fact] From b58f17e3eeb083e6e0a5d523e89bf315b1d69ab8 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 27 May 2026 18:04:45 +1000 Subject: [PATCH 3/4] Trim list to build/publish/deploy and add docs link Replace the long enumeration of well-known step names with a short, opinionated suggestion (build, publish, deploy) and a link to the official 'aspire do' reference page on aspire.dev for the full list. This removes the hand-maintained mirror of WellKnownPipelineSteps in DoCommand and simplifies localization since the message no longer takes a format parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Cli/Commands/DoCommand.cs | 33 ++----------------- .../Resources/DoCommandStrings.Designer.cs | 2 +- .../Resources/DoCommandStrings.resx | 3 +- .../Resources/xlf/DoCommandStrings.cs.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.de.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.es.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.fr.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.it.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.ja.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.ko.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.pl.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.pt-BR.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.ru.xlf | 6 ++-- .../Resources/xlf/DoCommandStrings.tr.xlf | 6 ++-- .../xlf/DoCommandStrings.zh-Hans.xlf | 6 ++-- .../xlf/DoCommandStrings.zh-Hant.xlf | 6 ++-- .../ListStepsTests.cs | 4 +-- .../Commands/DoCommandTests.cs | 6 ++-- 18 files changed, 48 insertions(+), 78 deletions(-) diff --git a/src/Aspire.Cli/Commands/DoCommand.cs b/src/Aspire.Cli/Commands/DoCommand.cs index 3e20aaa1981..f7aa990c971 100644 --- a/src/Aspire.Cli/Commands/DoCommand.cs +++ b/src/Aspire.Cli/Commands/DoCommand.cs @@ -21,30 +21,6 @@ internal sealed class DoCommand : PipelineCommandBase private readonly Argument _stepArgument; - // Mirror of Aspire.Hosting.Pipelines.WellKnownPipelineSteps used only for the friendly - // validation message when `aspire do --list-steps` is invoked without a step. The CLI - // does not reference Aspire.Hosting, so this list is hand-maintained; keep in sync with - // src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs. Sorted alphabetically so the - // rendered error is easy to scan. - private static readonly string[] s_wellKnownStepNames = - [ - "before-start", - "build", - "build-prereq", - "check-container-runtime", - "deploy", - "deploy-prereq", - "destroy", - "destroy-prereq", - "diagnostics", - "process-parameters", - "publish", - "publish-prereq", - "push", - "push-prereq", - "validate-compute-environments" - ]; - public DoCommand(IDotNetCliRunner runner, IInteractionService interactionService, IProjectLocator projectLocator, AspireCliTelemetry telemetry, IFeatures features, ICliUpdateNotifier updateNotifier, CliExecutionContext executionContext, ICliHostEnvironment hostEnvironment, IAppHostProjectFactory projectFactory, IConfiguration configuration, ILogger logger, IAnsiConsole ansiConsole) : base("do", DoCommandStrings.Description, runner, interactionService, projectLocator, telemetry, features, updateNotifier, executionContext, hostEnvironment, projectFactory, configuration, logger, ansiConsole) { @@ -65,12 +41,9 @@ public DoCommand(IDotNetCliRunner runner, IInteractionService interactionService { // `aspire do --list-steps` with no step has no meaningful scope: the listing for // `do` is always relative to a target step. Surface a friendly error pointing at - // concrete well-known step names rather than launching the AppHost and crashing - // mid-pipeline (see https://github.com/microsoft/aspire/issues/17526). - result.AddError(string.Format( - System.Globalization.CultureInfo.CurrentCulture, - DoCommandStrings.ListStepsRequiresStep, - string.Join(", ", s_wellKnownStepNames))); + // common starting steps and the docs rather than launching the AppHost and + // crashing mid-pipeline (see https://github.com/microsoft/aspire/issues/17526). + result.AddError(DoCommandStrings.ListStepsRequiresStep); } else { diff --git a/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs index 756c491e10c..ab8fbd526f5 100644 --- a/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/DoCommandStrings.Designer.cs @@ -133,7 +133,7 @@ public static string StepArgumentRequired { } /// - /// Looks up a localized string similar to The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}.. + /// Looks up a localized string similar to The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps.. /// public static string ListStepsRequiresStep { get { diff --git a/src/Aspire.Cli/Resources/DoCommandStrings.resx b/src/Aspire.Cli/Resources/DoCommandStrings.resx index 58d6af3170e..1e5862df55b 100644 --- a/src/Aspire.Cli/Resources/DoCommandStrings.resx +++ b/src/Aspire.Cli/Resources/DoCommandStrings.resx @@ -142,7 +142,6 @@ The 'step' argument is required. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf index cdc802d631b..1050dc23952 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.cs.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf index 58e741aa1f2..b0ce2d267f9 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.de.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf index e7aa0fa2f0a..12c5af2811a 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.es.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf index bfbb98ea466..4bffb1c76a5 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.fr.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf index f03ab4c4311..19daad8d54c 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.it.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf index 8d1704adb83..687dba75e06 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ja.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf index 3dc51ba43f0..9e341bfd91c 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ko.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf index e556bbb4223..40869b4afe5 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pl.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf index cea92774370..c3688ae13a0 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.pt-BR.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf index 1b5e8bc7e5c..7787f742110 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.ru.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf index 86bb4054fb5..392907d6050 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.tr.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf index 3d0fc8e926c..44cdbd985d3 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hans.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf index 96621015318..ca03fadae07 100644 --- a/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/DoCommandStrings.zh-Hant.xlf @@ -13,9 +13,9 @@ - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Well-known step names include: {0}. - {0} is a comma-separated list of well-known pipeline step names. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The 'step' argument is required when using --list-steps. Example: 'aspire do deploy --list-steps'. Common starting steps are 'build', 'publish' and 'deploy'. See https://aspire.dev/reference/cli/commands/aspire-do/ for the full list of pipeline steps. + The operation was canceled. diff --git a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs index e38dbb0bef1..6a0d585d907 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs @@ -57,9 +57,9 @@ await auto.WaitUntilAsync(s => "aspire do --list-steps regressed: pipeline executed and crashed instead of surfacing the friendly validation error."); } return s.ContainsText("aspire do deploy --list-steps") - && s.ContainsText("Well-known step names include:"); + && s.ContainsText("aspire.dev/reference/cli/commands/aspire-do"); }, timeout: TimeSpan.FromMinutes(2), - description: "waiting for friendly error listing well-known steps and an example"); + description: "waiting for friendly error with example and docs link"); // The validation error returns a non-zero exit code, but the shell prompt should come back. await auto.WaitForAnyPromptAsync(counter); diff --git a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs index 10c4bef9eb1..daada98ee42 100644 --- a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs @@ -354,11 +354,9 @@ public async Task DoCommandWithListStepsAndNoStepArgumentShowsFriendlyError() var combined = string.Join("\n", result.Errors.Select(e => e.Message)); Assert.Contains("--list-steps", combined); Assert.Contains("aspire do deploy --list-steps", combined); - // The error lists well-known step names; spot-check a few from different - // categories (publish/deploy/build/destroy) to confirm the list is rendered. - Assert.Contains("publish", combined); Assert.Contains("build", combined); - Assert.Contains("destroy", combined); + Assert.Contains("publish", combined); + Assert.Contains("https://aspire.dev/reference/cli/commands/aspire-do/", combined); } [Fact] From bcd89e93d80d420b94e19e3761aff3071ba28b57 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 27 May 2026 19:11:23 +1000 Subject: [PATCH 4/4] Address review: fix extension-host bypass and harden E2E asserts - DoCommand validator: fire the ListStepsRequiresStep friendly error for `aspire do --list-steps` even when running in the extension host, because --list-steps does not flow through GetRunArgumentsAsync's interactive step prompt and would otherwise still hit the original pipeline crash from #17526. - ListStepsTests (E2E): replace long, wrap-sensitive substrings with short fragments ("required when using --list-steps", "aspire.dev/") so the assertion does not fail when the friendly error wraps in a narrow Docker terminal. - DoCommandTests: add DoCommandWithListStepsAndNoStepArgumentInExtensionHostShowsFriendlyError to regression-test the extension-host path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Cli/Commands/DoCommand.cs | 35 ++++++++++++------- .../ListStepsTests.cs | 8 +++-- .../Commands/DoCommandTests.cs | 30 ++++++++++++++++ 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/Aspire.Cli/Commands/DoCommand.cs b/src/Aspire.Cli/Commands/DoCommand.cs index f7aa990c971..ce14ce8b2a5 100644 --- a/src/Aspire.Cli/Commands/DoCommand.cs +++ b/src/Aspire.Cli/Commands/DoCommand.cs @@ -35,20 +35,29 @@ public DoCommand(IDotNetCliRunner runner, IInteractionService interactionService { var step = result.GetValue(_stepArgument); var listSteps = result.GetValue(s_listStepsOption); - if (string.IsNullOrEmpty(step) && !ExtensionHelper.IsExtensionHost(interactionService, out _, out _)) + if (!string.IsNullOrEmpty(step)) { - if (listSteps) - { - // `aspire do --list-steps` with no step has no meaningful scope: the listing for - // `do` is always relative to a target step. Surface a friendly error pointing at - // common starting steps and the docs rather than launching the AppHost and - // crashing mid-pipeline (see https://github.com/microsoft/aspire/issues/17526). - result.AddError(DoCommandStrings.ListStepsRequiresStep); - } - else - { - result.AddError(DoCommandStrings.StepArgumentRequired); - } + return; + } + + if (listSteps) + { + // `aspire do --list-steps` with no step has no meaningful scope: the listing for + // `do` is always relative to a target step. Surface a friendly error pointing at + // common starting steps and the docs rather than launching the AppHost and + // crashing mid-pipeline (see https://github.com/microsoft/aspire/issues/17526). + // This applies in the extension host too because `--list-steps` does not flow + // through the interactive step prompt in GetRunArgumentsAsync, so without this + // error the extension would still hit the original crash path. + result.AddError(DoCommandStrings.ListStepsRequiresStep); + return; + } + + // For a plain `aspire do` invocation, the extension host prompts the user for a step + // later in GetRunArgumentsAsync, so don't add a validation error there. + if (!ExtensionHelper.IsExtensionHost(interactionService, out _, out _)) + { + result.AddError(DoCommandStrings.StepArgumentRequired); } }); } diff --git a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs index 6a0d585d907..831248ddb38 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs @@ -56,8 +56,12 @@ await auto.WaitUntilAsync(s => throw new InvalidOperationException( "aspire do --list-steps regressed: pipeline executed and crashed instead of surfacing the friendly validation error."); } - return s.ContainsText("aspire do deploy --list-steps") - && s.ContainsText("aspire.dev/reference/cli/commands/aspire-do"); + // Match short fragments that are unlikely to straddle a wrap boundary in a narrow + // terminal. The full error message is a single long sentence, so asserting on the + // raw URL (or any 30+ char run) is flaky because the screen buffer inserts wrap + // newlines that defeat ContainsText's literal substring match. + return s.ContainsText("required when using --list-steps") + && s.ContainsText("aspire.dev/"); }, timeout: TimeSpan.FromMinutes(2), description: "waiting for friendly error with example and docs link"); diff --git a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs index daada98ee42..84254f5246b 100644 --- a/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs @@ -7,6 +7,7 @@ using Aspire.Cli.Backchannel; using Microsoft.Extensions.DependencyInjection; using Aspire.Cli.Utils; +using Aspire.Hosting; using Microsoft.AspNetCore.InternalTesting; namespace Aspire.Cli.Tests.Commands; @@ -359,6 +360,35 @@ public async Task DoCommandWithListStepsAndNoStepArgumentShowsFriendlyError() Assert.Contains("https://aspire.dev/reference/cli/commands/aspire-do/", combined); } + [Fact] + public async Task DoCommandWithListStepsAndNoStepArgumentInExtensionHostShowsFriendlyError() + { + // The extension host bypasses the plain `aspire do` step requirement because + // GetRunArgumentsAsync prompts the user interactively. But `--list-steps` does + // not flow through that prompt, so without the validator firing the extension + // would still hit the original crash from https://github.com/microsoft/aspire/issues/17526. + using var tempRepo = TemporaryWorkspace.Create(outputHelper); + + var services = CliTestHelper.CreateServiceCollection(tempRepo, outputHelper, options => + { + options.ExtensionBackchannelFactory = _ => new TestExtensionBackchannel(); + options.InteractionServiceFactory = sp => new TestExtensionInteractionService(sp); + options.ConfigurationCallback += config => + { + config[KnownConfigNames.ExtensionDebugSessionId] = "test-session-id"; + }; + }); + using var provider = services.BuildServiceProvider(); + var command = provider.GetRequiredService(); + + var result = command.Parse("do --list-steps"); + + Assert.NotEmpty(result.Errors); + var combined = string.Join("\n", result.Errors.Select(e => e.Message)); + Assert.Contains("--list-steps", combined); + Assert.Contains("https://aspire.dev/reference/cli/commands/aspire-do/", combined); + } + [Fact] public async Task DoCommandWithListStepsAndStepArgumentReturnsZero() {