Support debugging deploy, publish, and do commands in VS Code extension#14914
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14914Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14914" |
There was a problem hiding this comment.
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
DebugSessionOptionstype;PipelineCommandBaseand its subclasses updated to intercept in extension context and start debug sessions;DoCommandstep argument made optional in extension host context; backchannel race condition fix;pipelinescapability advertised. - Extension:
AspireCommandTypeunion;AspireDebugSessionupdated for multi-command support;displayLinesroutes to debug session or terminal; newdeploy,publish, anddoVS Code commands. - Playground: New
SimplePipelines.AppHostproject 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..."); |
There was a problem hiding this comment.
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.
| 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 }); |
There was a problem hiding this comment.
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.
Notes on tangential changesA few changes in this PR aren't directly related to the deploy/publish/do debugging feature but were encountered during development:
|
…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
Description
Until now, only
aspire runcould 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, ordo), and the CLI's--start-debug-sessionflag tells it to delegate the apphost launch back to the extension so the debugger can attach.On the CLI side,
PipelineCommandBasenow 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 inRunCommand.Key design decisions
DebugSessionOptionsfor backward compatibility. Rather than changing the positional parameters of thestartDebugSessionRPC call, a new optionalDebugSessionOptionsobject carries the command name and any additional arguments. Older extensions that don't pass options will default torunbehavior.--start-debug-sessionon pipeline commands. The same hidden option thatRunCommanduses is now added toPipelineCommandBasewhen running in extension context. This flag flows throughPublishContext.StartDebugSessionintoDotNetCliRunnerInvocationOptions, which controls whetherDotNetCliRunnerdelegates the apphost launch to the extension or starts it directly. WhenStartDebugSessionis false,NoExtensionLaunchis 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.WhenAnyto race the backchannel establishment against the apphost run completing. In extension context,DotNetCliRunnerreturns success immediately after delegating toLaunchAppHostAsync, which meanspendingRunwins 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 oncommand === 'run'.Args extraction in
startAppHost. The CLI sends the fulldotnetCLI argument list toLaunchAppHostAsync(e.g.,["run", "--no-build", "--project", "...", "--", ...appHostArgs]). Since the extension launches the apphost directly via the coreclr debugger rather than throughdotnet run,startAppHostnow extracts only the arguments after--.Interactive prompting for
aspire do. Thedocommand'sstepargument 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,GetRunArgumentsAsyncprompts 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 thepipelinescapability, it defers step prompting to the CLI rather than showing its own input box.Other changes
The
displayLinesmethod inInteractionServicepreviously 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
SimplePipelinesplayground 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