Add --list-steps flag to aspire do command#16085
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16085Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 16085" |
There was a problem hiding this comment.
Pull request overview
Adds a new --list-steps mode for Aspire pipeline commands that queries the AppHost for resolved pipeline steps and prints them (dependencies + tags) in execution/topological order, without running the pipeline.
Changes:
- Introduces a backchannel DTO (
PipelineStepInfo) and RPC (GetPipelineStepsAsync) to retrieve resolved pipeline steps from the AppHost. - Refactors
DistributedApplicationPipelineto separate “resolve steps” from execution, and exposes topological sorting for reuse. - Adds CLI support (
--list-steps) with formatting/output tests plus an end-to-end validation.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/Pipelines/DistributedApplicationPipelineTests.cs | Adds tests for step resolution behavior and topological ordering. |
| tests/Aspire.Cli.Tests/TestServices/TestAppHostCliBackchannel.cs | Extends the test backchannel stub with GetPipelineStepsAsync. |
| tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs | Updates mock backchannel implementation to satisfy the new interface method. |
| tests/Aspire.Cli.Tests/Commands/PipelineCommandListStepsTests.cs | Adds unit tests for CLI formatting/output of --list-steps. |
| tests/Aspire.Cli.Tests/Commands/DoCommandTests.cs | Adds integration tests verifying --list-steps RPC usage and non-execution behavior. |
| tests/Aspire.Cli.EndToEnd.Tests/ListStepsTests.cs | Adds E2E test exercising aspire do deploy --list-steps in Docker terminal automation. |
| src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs | Adds ResolveStepsAsync and exposes GetTopologicalOrder; execution now builds from resolved steps. |
| src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs | Adds PipelineStepInfo DTO shared between AppHost and CLI. |
| src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs | Advertises new capability and implements GetPipelineStepsAsync RPC. |
| src/Aspire.Cli/Commands/PipelineCommandBase.cs | Adds --list-steps option and prints steps instead of streaming publish activities. |
| src/Aspire.Cli/Commands/DoCommand.cs | Makes step optional to support --list-steps, adds runtime enforcement when not listing. |
| src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs | Registers PipelineStepInfo for source-generated JSON serialization. |
| src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs | Adds RPC client method GetPipelineStepsAsync. |
| @@ -368,7 +388,7 @@ public async Task ExecuteAsync(PipelineContext context) | |||
|
|
|||
| if (allSteps.Count == 0) | |||
| { | |||
| return; | |||
| return allSteps; | |||
| } | |||
|
|
|||
| ValidateSteps(allSteps); | |||
| @@ -380,10 +400,7 @@ public async Task ExecuteAsync(PipelineContext context) | |||
| // Capture resolved pipeline data for diagnostics (before filtering) | |||
| _lastResolvedSteps = allSteps; | |||
|
|
|||
| var (stepsToExecute, stepsByName) = FilterStepsForExecution(allSteps, context); | |||
|
|
|||
| // Build dependency graph and execute with readiness-based scheduler | |||
| await ExecuteStepsAsTaskDag(stepsToExecute, stepsByName, context).ConfigureAwait(false); | |||
| return allSteps; | |||
| } | |||
There was a problem hiding this comment.
ResolveStepsAsync XML docs say it "returns the steps in topological order" and "validates", but the method currently returns allSteps in original insertion order and does not validate the dependency graph for cycles (cycle validation only happens during execution). This can make --list-steps output incorrect/incomplete compared to actual execution failures. Consider calling the existing dependency-graph validation and returning a topologically sorted list here (or update the docs to match the actual behavior).
| // Step is required when not using --list-steps | ||
| if (string.IsNullOrEmpty(step) && !parseResult.GetValue(s_listStepsOption)) | ||
| { | ||
| throw new InvalidOperationException("The 'step' argument is required when not using --list-steps."); |
There was a problem hiding this comment.
Throwing InvalidOperationException when step is missing (and --list-steps is not set) will be surfaced via the generic exception handler in PipelineCommandBase as an "unexpected error" rather than a normal command-line validation error. Prefer System.CommandLine validation (e.g., AddValidator on the command/argument) so users get a standard parse error + help/usage and the correct exit code, without an exception-based control flow.
| // Step is required when not using --list-steps | |
| if (string.IsNullOrEmpty(step) && !parseResult.GetValue(s_listStepsOption)) | |
| { | |
| throw new InvalidOperationException("The 'step' argument is required when not using --list-steps."); | |
| // Step is required when not using --list-steps. | |
| if (string.IsNullOrEmpty(step) && !parseResult.GetValue(s_listStepsOption)) | |
| { | |
| var stepResult = parseResult.GetResult(_stepArgument); | |
| if (stepResult is not null) | |
| { | |
| stepResult.AddError("The 'step' argument is required when not using --list-steps."); | |
| } | |
| else | |
| { | |
| parseResult.CommandResult.AddError("The 'step' argument is required when not using --list-steps."); | |
| } | |
| return []; |
| // If --list-steps was specified, get pipeline steps and print them instead of executing | ||
| var listSteps = parseResult.GetValue(s_listStepsOption); | ||
| if (listSteps) | ||
| { | ||
| StopTerminalProgressBar(); | ||
|
|
||
| var steps = await backchannel.GetPipelineStepsAsync(cancellationToken); | ||
| PrintPipelineSteps(steps); | ||
|
|
||
| await backchannel.RequestStopAsync(cancellationToken).ConfigureAwait(false); | ||
| await pendingRun; | ||
| return ExitCodeConstants.Success; |
There was a problem hiding this comment.
The --list-steps path calls GetPipelineStepsAsync unconditionally. If the user runs a newer CLI against an older AppHost that doesn't advertise/support pipeline-steps.v1, this will likely fail with RemoteMethodNotFoundException and be reported as an "unexpected error". Consider checking backchannel.GetCapabilitiesAsync(...) for pipeline-steps.v1 (or catching RemoteMethodNotFoundException here) and converting it into an AppHostIncompatibleException with a clear message.
| #pragma warning disable ASPIREPIPELINES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
|
|
There was a problem hiding this comment.
#pragma warning disable ASPIREPIPELINES001 is added at the top of the file but never restored/scoped. This suppresses the diagnostic for the entire RPC target, potentially masking unrelated occurrences. Consider scoping the suppression just around the code that requires it, or add a corresponding #pragma warning restore ASPIREPIPELINES001.
|
I want to make sure that we have 2 things:
|
|
Dogfood Testing ReportCLI Version: Results
Feedback:
|
Implement support for 'aspire do --list-steps' that prints pipeline steps in execution order with their dependencies and tags, without executing them. Changes: - Add PipelineStepInfo DTO to BackchannelDataTypes.cs (source-shared) - Add ResolveStepsAsync to DistributedApplicationPipeline for resolving steps without executing them - Add GetPipelineStepsAsync RPC method to AppHostRpcTarget with 'pipeline-steps.v1' capability - Add GetPipelineStepsAsync to IAppHostCliBackchannel interface and implementations - Add --list-steps option to PipelineCommandBase with formatted output - Make DoCommand step argument optional when --list-steps is used Testing: - Unit tests for DoCommand with --list-steps (returns 0, calls GetPipelineStepsAsync, does not execute pipeline, calls RequestStopAsync) - Unit tests for PrintPipelineSteps formatting (dependencies, tags, sequential numbering, empty steps, full pipeline output) - Unit tests for ResolveStepsAsync and GetTopologicalOrder - E2E CLI test using Hex1b terminal automation Fixes #12376 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add color to the output: green step numbers, blue 'Depends on:' label, yellow 'Tags:' label, dim 'No dependencies' text. Add hanging indent for long dependency lists so wrapped items align under the first item. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Spectre.Console AnsiConsole emits ANSI escape codes even with AnsiSupport.No in CI environments. Strip ANSI codes before asserting on output content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix the continuation line indent to align wrapped dependency items
directly under the first item (19 chars), not double-indented.
Change step name color from bold white to cyan for better visibility.
Before:
├─ Depends on: provision-redis-infra, provision-postgres-infra,
build-webapi
After:
├─ Depends on: provision-redis-infra, provision-postgres-infra,
build-webapi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compute the continuation prefix width from the visible length of the first line prefix rather than hardcoding spaces. This ensures wrapped dependency items align correctly under the first item regardless of unicode box-drawing character widths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use request/response objects (GetPipelineStepsRequest/Response) per the backchannel spec contract rules instead of raw array return - Add capability check for 'pipeline-steps.v1' before calling GetPipelineStepsAsync, throwing AppHostIncompatibleException with a clear message if the AppHost doesn't support it - Scope #pragma warning disable ASPIREPIPELINES001 to just the method that needs it instead of file-wide - Fix ResolveStepsAsync XML doc to accurately state it returns collection order (not topological order) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a target step is specified (e.g. 'aspire do build --list-steps'), only show that step and its transitive dependencies instead of the full pipeline graph. This makes the output much more useful for understanding what a specific target will actually do. - aspire do --list-steps -> all steps (no filter) - aspire do build --list-steps -> build + its deps only - aspire publish --list-steps -> publish + its deps only - aspire deploy --list-steps -> deploy + its deps only Each PipelineCommandBase subclass provides its target step name via GetTargetStepName(). The step name is passed in GetPipelineStepsRequest and the AppHost filters using ComputeTransitiveDependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace InvalidOperationException with a proper command validator so users get a standard parse error with help/usage output instead of an unexpected error when running 'aspire do' without a step argument and without --list-steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
52e6727 to
d8c2da8
Compare
|
🎬 CLI E2E Test Recordings — 70 recordings uploaded (commit View recordings
📹 Recordings uploaded automatically from CI run #24371556571 |
davidfowl
left a comment
There was a problem hiding this comment.
Approved after testing the PR build on the pr-16093-test droplet.
|
Tested this on the Results:
Follow-up issues:
|
* Add --list-steps flag to aspire do command Implement support for 'aspire do --list-steps' that prints pipeline steps in execution order with their dependencies and tags, without executing them. Changes: - Add PipelineStepInfo DTO to BackchannelDataTypes.cs (source-shared) - Add ResolveStepsAsync to DistributedApplicationPipeline for resolving steps without executing them - Add GetPipelineStepsAsync RPC method to AppHostRpcTarget with 'pipeline-steps.v1' capability - Add GetPipelineStepsAsync to IAppHostCliBackchannel interface and implementations - Add --list-steps option to PipelineCommandBase with formatted output - Make DoCommand step argument optional when --list-steps is used Testing: - Unit tests for DoCommand with --list-steps (returns 0, calls GetPipelineStepsAsync, does not execute pipeline, calls RequestStopAsync) - Unit tests for PrintPipelineSteps formatting (dependencies, tags, sequential numbering, empty steps, full pipeline output) - Unit tests for ResolveStepsAsync and GetTopologicalOrder - E2E CLI test using Hex1b terminal automation Fixes #12376 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Improve --list-steps output formatting Add color to the output: green step numbers, blue 'Depends on:' label, yellow 'Tags:' label, dim 'No dependencies' text. Add hanging indent for long dependency lists so wrapped items align under the first item. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix PrintPipelineSteps tests to strip ANSI escape codes The Spectre.Console AnsiConsole emits ANSI escape codes even with AnsiSupport.No in CI environments. Strip ANSI codes before asserting on output content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix hanging indent alignment and use cyan for step names Fix the continuation line indent to align wrapped dependency items directly under the first item (19 chars), not double-indented. Change step name color from bold white to cyan for better visibility. Before: ├─ Depends on: provision-redis-infra, provision-postgres-infra, build-webapi After: ├─ Depends on: provision-redis-infra, provision-postgres-infra, build-webapi Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix wrapped dependency alignment to be computed dynamically Compute the continuation prefix width from the visible length of the first line prefix rather than hardcoding spaces. This ensures wrapped dependency items align correctly under the first item regardless of unicode box-drawing character widths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback: backchannel spec compliance and versioning - Use request/response objects (GetPipelineStepsRequest/Response) per the backchannel spec contract rules instead of raw array return - Add capability check for 'pipeline-steps.v1' before calling GetPipelineStepsAsync, throwing AppHostIncompatibleException with a clear message if the AppHost doesn't support it - Scope #pragma warning disable ASPIREPIPELINES001 to just the method that needs it instead of file-wide - Fix ResolveStepsAsync XML doc to accurately state it returns collection order (not topological order) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Scope --list-steps output to target step and its dependencies When a target step is specified (e.g. 'aspire do build --list-steps'), only show that step and its transitive dependencies instead of the full pipeline graph. This makes the output much more useful for understanding what a specific target will actually do. - aspire do --list-steps -> all steps (no filter) - aspire do build --list-steps -> build + its deps only - aspire publish --list-steps -> publish + its deps only - aspire deploy --list-steps -> deploy + its deps only Each PipelineCommandBase subclass provides its target step name via GetTargetStepName(). The step name is passed in GetPipelineStepsRequest and the AppHost filters using ComputeTransitiveDependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use System.CommandLine validator for missing step argument Replace InvalidOperationException with a proper command validator so users get a standard parse error with help/usage output instead of an unexpected error when running 'aspire do' without a step argument and without --list-steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
#847) The reference docs for aspire do and the deployment/pipelines conceptual page already document the --list-steps flag, but neither shows what the output actually looks like. Add a short, canonical numbered-tree sample (matches the formatter in microsoft/aspire#16085 and its snapshot tests) so readers can see at a glance what to expect — including the connectors used for steps with deps only, deps and tags, and steps with no dependencies. Context: #837 (item #2), follow-up polish to #801. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Description
Adds support for
aspire do --list-stepsflag that prints the pipeline steps that would be executed, in topological (execution) order, along with their dependencies and tags — without actually running them.Problem
Users need a simple way to inspect what pipeline steps will run for a given target. The existing
aspire do diagnosticsoutput is too verbose and technical for typical users (see issue discussion).Approach
The implementation spans the AppHost (Aspire.Hosting) and CLI (Aspire.Cli), connected via the backchannel RPC:
AppHost side:
PipelineStepInfoDTO inBackchannelDataTypes.cs(source-shared between CLI and AppHost)ResolveStepsAsync()onDistributedApplicationPipelinethat resolves all steps (collects from annotations, normalizes RequiredBy→DependsOn, validates) without executing themGetPipelineStepsAsync()RPC method onAppHostRpcTargetwithpipeline-steps.v1capabilityCLI side:
--list-stepsoption added toPipelineCommandBase(available ondo,publish,deploy)GetPipelineStepsAsyncinstead of streaming publishing activitiesRequestStopAsyncto shut down the AppHost immediatelyExample output:
Validation
Fixes #12376
Checklist
aspire.devissue: