Skip to content

Support debugging deploy, publish, and do commands in VS Code extension#14914

Merged
adamint merged 12 commits intomicrosoft:release/13.2from
adamint:dev/adamint/support-debugging-deploy-publish-do
Mar 4, 2026
Merged

Support debugging deploy, publish, and do commands in VS Code extension#14914
adamint merged 12 commits intomicrosoft:release/13.2from
adamint:dev/adamint/support-debugging-deploy-publish-do

Conversation

@adamint
Copy link
Member

@adamint adamint commented Mar 3, 2026

Description

Until now, only aspire run could be launched as a debug session from VS Code. The deploy, publish, and do commands ran in the Aspire terminal as plain text — no debugger attached, no breakpoint support, no structured output in the debug console. This PR extends the full debug session experience to all three pipeline commands.

How it works

When a user invokes "Deploy app", "Publish deployment artifacts", or "Execute pipeline step (aspire do)" from the VS Code command palette, the extension starts an Aspire debug session just like it does for aspire run. The debug session spawns the CLI with the appropriate command (deploy, publish, or do), and the CLI's --start-debug-session flag tells it to delegate the apphost launch back to the extension so the debugger can attach.

On the CLI side, PipelineCommandBase now detects when it's running inside the Aspire extension terminal without an active debug session. When this happens, it resolves the apphost project (prompting the user if multiple are found), then asks VS Code to start a proper debug session with the correct command and arguments. This mirrors the existing interception pattern in RunCommand.

Key design decisions

DebugSessionOptions for backward compatibility. Rather than changing the positional parameters of the startDebugSession RPC call, a new optional DebugSessionOptions object carries the command name and any additional arguments. Older extensions that don't pass options will default to run behavior.

--start-debug-session on pipeline commands. The same hidden option that RunCommand uses is now added to PipelineCommandBase when running in extension context. This flag flows through PublishContext.StartDebugSession into DotNetCliRunnerInvocationOptions, which controls whether DotNetCliRunner delegates the apphost launch to the extension or starts it directly. When StartDebugSession is false, NoExtensionLaunch is set to true to preserve the original behavior where publish/deploy ran the apphost directly without extension involvement.

Backchannel race condition fix. Pipeline commands use Task.WhenAny to race the backchannel establishment against the apphost run completing. In extension context, DotNetCliRunner returns success immediately after delegating to LaunchAppHostAsync, which means pendingRun wins the race before the backchannel socket is created. The fix detects this case (successful completion in extension context) and continues waiting for the backchannel rather than throwing.

No auto-restart for pipeline commands. When the apphost debug session terminates, the existing code automatically restarts it (crash recovery for run). Pipeline commands exit normally after completing their work, so restart is now gated on command === 'run'.

Args extraction in startAppHost. The CLI sends the full dotnet CLI argument list to LaunchAppHostAsync (e.g., ["run", "--no-build", "--project", "...", "--", ...appHostArgs]). Since the extension launches the apphost directly via the coreclr debugger rather than through dotnet run, startAppHost now extracts only the arguments after --.

Interactive prompting for aspire do. The do command's step argument is required in normal CLI usage but optional when running in extension context. The argument arity is set conditionally at construction time. When the step is missing in extension context, GetRunArgumentsAsync prompts for it via the interaction service, which routes over RPC to VS Code's input UI. The extension also checks CLI capabilities — if the CLI advertises the pipelines capability, it defers step prompting to the CLI rather than showing its own input box.

Other changes

The displayLines method in InteractionService previously opened a temporary text document to show build errors. It now writes directly to the debug console when a debug session is active, or falls back to the Aspire terminal. Terminal progress bar escape sequences (\e]9;4;N\e\) emitted by the CLI are filtered out since they're meaningless in the debug console. CLI debug logging (--debug) is no longer forced on for pipeline commands — it's only added when the user has explicitly enabled the debug logging setting.

The deploy, publish, and do commands in the extension command palette are now single commands (no separate "Debug deploy" variants). They always launch with the debugger attached.

A SimplePipelines playground project is included with three pipeline steps (a standalone hello-world step, a custom deploy prerequisite, and a custom publish prerequisite) for manual testing.

Fixes #12123
Fixes #13073

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No
  • Does the change require an update in our Aspire docs?
    • Yes
    • No

Copilot AI review requested due to automatic review settings March 3, 2026 21:25
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14914

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14914"

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds debugging support for aspire deploy, aspire publish, and aspire do commands in the VS Code extension. Previously only aspire run could be debugged through VS Code. The CLI is updated to detect when it's running inside the Aspire extension context, intercept the command, and tell VS Code to start a proper debug session. The extension is updated to build the correct CLI args, route output appropriately, and avoid restarting pipeline command sessions when they complete normally.

Changes:

  • CLI: New DebugSessionOptions type; PipelineCommandBase and its subclasses updated to intercept in extension context and start debug sessions; DoCommand step argument made optional in extension host context; backchannel race condition fix; pipelines capability advertised.
  • Extension: AspireCommandType union; AspireDebugSession updated for multi-command support; displayLines routes to debug session or terminal; new deploy, publish, and do VS Code commands.
  • Playground: New SimplePipelines.AppHost project with hello-world, custom-deploy-prereq, and custom-publish-prereq pipeline steps.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Aspire.Cli/Commands/PipelineCommandBase.cs Core intercept-and-delegate logic for pipeline commands in extension context; backchannel race condition fix
src/Aspire.Cli/Commands/DoCommand.cs Step argument made optional in extension context; interactive prompt for missing step
src/Aspire.Cli/Commands/DeployCommand.cs Async GetRunArgumentsAsync and IConfiguration injection
src/Aspire.Cli/Commands/PublishCommand.cs Async GetRunArgumentsAsync and IConfiguration injection
src/Aspire.Cli/Commands/RunCommand.cs Passes DebugSessionOptions { Command = "run" } when starting debug session
src/Aspire.Cli/Commands/ConfigCommand.cs / ConfigInfo.cs Capabilities included in ConfigInfo JSON output
src/Aspire.Cli/Utils/ExtensionHelper.cs pipelines capability constant and GetAdvertisedCapabilities() method
src/Aspire.Cli/Backchannel/ExtensionBackchannel.cs / ExtensionBackchannelDataTypes.cs DebugSessionOptions type and updated StartDebugSessionAsync signature
src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs DebugSessionOptions added to serialization context
src/Aspire.Cli/Backchannel/ExtensionRpcTarget.cs Returns all advertised capabilities instead of a single hardcoded one
src/Aspire.Cli/Interaction/ExtensionInteractionService.cs Updated StartDebugSessionAsync to pass DebugSessionOptions
src/Aspire.Cli/Projects/IAppHostProject.cs New StartDebugSession property on PublishContext
src/Aspire.Cli/Projects/DotNetAppHostProject.cs StartDebugSession plumbed into run options; isSingleFileAppHost validation fix
src/Aspire.Cli/Projects/ProjectLocator.cs Validates app host before accepting as selected project file
extension/src/debugger/AspireDebugSession.ts Multi-command arg building; no-restart for pipeline commands; terminal escape sequence filtering
extension/src/server/interactionService.ts displayLines routes to debug session or terminal; startDebugSession with options
extension/src/server/rpcClient.ts Passes terminal provider to InteractionService
extension/src/editor/AspireEditorCommandProvider.ts New tryExecuteDeployAppHost, tryExecutePublishAppHost, tryExecuteDoAppHost
extension/src/commands/deploy.ts Simplified to use debug session path
extension/src/commands/publish.ts Simplified to use debug session path
extension/src/commands/do.ts New command with resolveStep capability check
extension/src/extension.ts Registers aspire-vscode.do; updates deploy/publish command wiring
extension/src/dcp/types.ts AspireCommandType, command, args, step fields on debug config
extension/src/types/configInfo.ts Optional Capabilities field
extension/package.json / package.nls.json command and step debug config fields; aspire-vscode.do command
extension/loc/xlf/aspire-vscode.xlf New localization strings
extension/src/utils/cliPath.ts / debugger/languages/cli.ts Removed verbose info log statements
extension/src/test/rpc/interactionServiceTests.test.ts Updated displayLines tests
tests/Aspire.Cli.Tests/TestServices/* Updated DebugSessionOptions parameter in test stubs
playground/SimplePipelines/SimplePipelines.AppHost/* New playground project
Aspire.slnx Added SimplePipelines.AppHost to solution

You can also share your feedback on Copilot code review. Take the survey.


var commandArgs = GetCommandArgs(parseResult).Concat(parseResult.UnmatchedTokens).ToArray();

extensionInteractionService.DisplayConsolePlainText($"Detected aspire {Name} inside the Aspire extension, starting a debug session in VS Code...");
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user-facing message on line 148 is a hardcoded interpolated string, not localized via a resource file. The analogous message in RunCommand.cs (line 174) uses the localized RunCommandStrings.StartingDebugSessionInExtension. This message should also be localized — ideally in a shared resource (or by parameterizing the existing RunCommandStrings.StartingDebugSessionInExtension with the command name) so it can be translated.

Copilot uses AI. Check for mistakes.
var commandArgs = GetCommandArgs(parseResult).Concat(parseResult.UnmatchedTokens).ToArray();

extensionInteractionService.DisplayConsolePlainText($"Detected aspire {Name} inside the Aspire extension, starting a debug session in VS Code...");
await extensionInteractionService.StartDebugSessionAsync(ExecutionContext.WorkingDirectory.FullName, passedAppHostProjectFile?.FullName, debug: true, new DebugSessionOptions { Command = Name, Args = commandArgs.Length > 0 ? commandArgs : null });
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the null-check guard block on lines 135–143, passedAppHostProjectFile is guaranteed to be non-null (either it was non-null from the start, or was found via the locator). The null-conditional operator ?.FullName on line 149 is therefore unreachable and can be simplified to .FullName.

Copilot uses AI. Check for mistakes.
@adamint
Copy link
Member Author

adamint commented Mar 3, 2026

Notes on tangential changes

A few changes in this PR aren't directly related to the deploy/publish/do debugging feature but were encountered during development:

ProjectLocator.cs — validates single-file apphosts before accepting them. Previously, TryGetProject returning a handler was enough to accept a file as a valid apphost. However, the handler can match .cs files that aren't actually valid single-file apphosts (e.g., a random .cs file in the workspace). Adding ValidateAppHostAsync ensures we don't silently accept invalid files.

AspireEditorCommandProvider.tsisAppHostCsFile logic change. The old approach found the first non-empty line and compared it. This broke for files with leading comments or blank lines before the builder line. Using lines.some(...) checks all lines in the file, which is more robust.

cliPath.ts and cli.ts — removed verbose log lines. These info-level log messages fired on every CLI path resolution and every process spawn, adding noise to the output channel without much diagnostic value. The important state changes (like updating the CLI path setting) are still logged.

@adamint adamint merged commit b6ff0c2 into microsoft:release/13.2 Mar 4, 2026
758 of 761 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Mar 4, 2026
Copilot AI pushed a commit that referenced this pull request Mar 10, 2026
…on (#14914)

* Add DebugSessionOptions type and thread through RPC layer

* Advertise CLI capabilities including pipelines in config info

* Add extension context interception and --start-debug-session to pipeline commands

* Support deploy/publish/do commands in AspireDebugSession

* Add deploy, publish, and do commands to VS Code extension

* Add DebugSessionOptions support and displayLines fallback in InteractionService

* Add SimplePipelines playground project

* Reduce noisy logging and validate single-file apphosts in ProjectLocator

* Prompt for missing pipeline step in DoCommand via interaction service

* Update extension README with deploy, publish, and do command documentation

* Add clarifying comment on NoExtensionLaunch flag
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants