Enable JavaScript/TypeScript AppHost debugging with IGuestProcessLauncher strategy#15091
Merged
adamint merged 23 commits intomicrosoft:release/13.2from Mar 10, 2026
Merged
Conversation
…oft#14686)" This reverts commit 514d4bf.
WithDebugging has no AspireExport attribute on this branch, so the generated TypeScript SDK doesn't include it, causing tsc to fail.
Add Node.js debugging support with minimal changes: - NodeLaunchConfiguration extending ExecutableLaunchConfiguration - Internal WithVSCodeDebugging methods wired into AddNodeApp/CreateDefaultJavaScriptAppBuilder - Extension: node debugger using built-in js-debug adapter - No new public APIs or extensibility system - Tests updated for additional CommandLineArgsCallbackAnnotation from WithDebugSupport
- BrowserLaunchConfiguration: launch config extending ExecutableLaunchConfiguration('browser')
with url, webRoot, and browser properties
- BrowserDebuggerResource: internal child resource for browser debugging
- WithBrowserDebugger: creates child resource with WithDebugSupport for browser capability
- ValidateBrowserCapability: validates extension supports browser debugging
- browser.ts: extension debugger using VS Code built-in pwa-msedge/chrome adapter
- Added 'browser' capability to extension capabilities
- Fixed duplicate 'run' in node.ts: args from DCP already contain the full command,
so runtimeArgs should use args directly instead of prepending 'run'
- Fixed getProjectFile to handle empty script_path for package manager mode
…/js-apphost-run-debug # Conflicts: # extension/src/capabilities.ts # src/Aspire.Hosting.JavaScript/BrowserLaunchConfiguration.cs # src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs # src/Aspire.Hosting.JavaScript/NodeLaunchConfiguration.cs # src/Aspire.Hosting.Python/Aspire.Hosting.Python.csproj # src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs # src/Aspire.Hosting/Aspire.Hosting.csproj # src/Aspire.Hosting/Dcp/DcpExecutor.cs # src/Aspire.Hosting/Dcp/Model/ExecutableLaunchConfiguration.cs # src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs # src/Aspire.Hosting/ResourceBuilderExtensions.cs # src/Aspire.Hosting/SupportsDebuggingAnnotation.cs # tests/Aspire.Hosting.JavaScript.Tests/AddNodeAppTests.cs # tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs # tests/Aspire.Hosting.Tests/ExecutableResourceBuilderExtensionTests.cs
…ching Introduce a strategy pattern for launching guest language AppHost processes, enabling the VS Code extension to launch and debug TypeScript/Node.js apphosts via its debug adapter instead of spawning a plain OS process. Problem: When launching a TypeScript AppHost from VS Code, the extension used plain 'node' which cannot resolve .js -> .ts imports. The runtime needs 'npx tsx' as the executable, which is specified in the RuntimeSpec but was not being forwarded to the extension's debug session. Solution: IGuestProcessLauncher strategy interface with two implementations: - ProcessGuestLauncher: Spawns a local OS process (default, CLI-only path). Resolves the command via PATH, starts a Process, captures stdout/stderr. - ExtensionGuestLauncher: Delegates to the VS Code extension debug session. Prepends the runtime command (e.g. 'npx') as args[0] in the RPC call so the extension can extract it as runtimeExecutable for the Node debug adapter. The launcher is non-null throughout the chain: GuestAppHostProject creates the appropriate launcher and passes it through ExecuteGuestAppHostAsync to GuestRuntime.RunAsync/PublishAsync to ExecuteCommandAsync to launcher.LaunchAsync. Also refactored InstallDependenciesAsync to use ProcessGuestLauncher instead of inline Process code, eliminating duplication. On the extension side, AspireDebugSession.startAppHost extracts args[0] as the runtimeExecutable and sets it on the NodeLaunchConfiguration, which flows through to debugConfiguration.runtimeExecutable in the node debugger extension. Tests: Added GuestRuntimeTests (15 tests) and ExtensionGuestLauncherTests (6 tests) covering command selection, placeholder replacement, env var merging, args prepending, and delegation behavior.
Contributor
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15091Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15091" |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enables running/debugging JavaScript/TypeScript AppHost projects from the VS Code extension by introducing a guest-process launcher strategy in the CLI (process vs extension-delegated launch) and adding Node + browser launch-configuration support across Hosting/DCP and the extension.
Changes:
- Add
IGuestProcessLauncher(+ process/extension implementations) and refactorGuestRuntime/GuestAppHostProjectto support extension-driven launching for JS/TS AppHosts. - Extend DCP/Hosting to produce typed Node and browser launch configurations (and delay launch-config production until endpoints are allocated).
- Add VS Code debugger extensions for Node and browser resources plus new tests covering the new launcher/runtime paths.
Reviewed changes
Copilot reviewed 44 out of 45 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs | Adds regression test ensuring IDE launch-config failures fall back to process execution. |
| tests/Aspire.Hosting.JavaScript.Tests/AddViteAppWithPnpmTests.cs | Adjusts how args callback annotation is selected in tests. |
| tests/Aspire.Hosting.JavaScript.Tests/AddViteAppTests.cs | Adjusts how args callback annotation is selected in tests. |
| tests/Aspire.Hosting.JavaScript.Tests/AddNodeAppTests.cs | Adds tests for VS Code debugging annotations and new browser debugger child resource. |
| tests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cs | Adds unit tests for GuestRuntime placeholder/env/command selection behavior and launcher usage. |
| tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs | Adds unit tests validating extension-delegated launching argument/env shaping. |
| src/Aspire.Hosting/Dcp/Model/ExecutableLaunchConfiguration.cs | Seals ProjectLaunchConfiguration. |
| src/Aspire.Hosting/Dcp/DcpExecutor.cs | Defers debug launch-config annotation until endpoints are allocated; adds safe fallback on producer errors. |
| src/Aspire.Hosting/Aspire.Hosting.csproj | Adds InternalsVisibleTo entries for JavaScript/Python assemblies and tests. |
| src/Aspire.Hosting.Python/Aspire.Hosting.Python.csproj | Removes linked/shared compile includes now sourced via project reference. |
| src/Aspire.Hosting.JavaScript/NodeLaunchConfiguration.cs | Introduces DCP launch configuration model for Node debugging. |
| src/Aspire.Hosting.JavaScript/BrowserLaunchConfiguration.cs | Introduces DCP launch configuration model for browser debugging. |
| src/Aspire.Hosting.JavaScript/BrowserDebuggerResource.cs | Adds an executable child resource to represent a browser debug session. |
| src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs | Adds VS Code debugging support hooks + WithBrowserDebugger() and emits Node/browser launch configs. |
| src/Aspire.Hosting.JavaScript/Aspire.Hosting.JavaScript.csproj | Removes linked/shared compile includes now sourced via project reference. |
| src/Aspire.Cli/Utils/ExtensionHelper.cs | Adds KnownCapabilities.Node. |
| src/Aspire.Cli/Projects/IGuestProcessLauncher.cs | Adds launcher strategy interface. |
| src/Aspire.Cli/Projects/ProcessGuestLauncher.cs | Adds default OS-process launcher implementation (PATH-resolved). |
| src/Aspire.Cli/Projects/ExtensionGuestLauncher.cs | Adds extension-delegating launcher implementation (prepends runtime executable into args). |
| src/Aspire.Cli/Projects/GuestRuntime.cs | Refactors to use launcher strategy, merges env vars, removes inline process code. |
| src/Aspire.Cli/Projects/GuestAppHostProject.cs | Chooses extension vs process launcher and handles null output for extension-launched runs. |
| src/Aspire.Cli/Commands/RunCommand.cs | Treats ConnectionLostException as success to support extension-driven termination path. |
| src/Aspire.Cli/Backchannel/ExtensionBackchannel.cs | Updates log message wording for LaunchAppHostAsync. |
| playground/TypeScriptAppHost/.modules/.codegen-hash | Updates generated hash for TypeScript playground artifacts. |
| playground/TypeScriptAppHost/.gitignore | Ignores .aspire/dcp/ in TypeScript playground. |
| playground/TypeScriptAppHost/.aspire/settings.json | Updates TypeScript playground settings (channel/sdkVersion). |
| playground/AspireWithJavaScript/AspireJavaScript.React/webpack.config.js | Enables source maps for the React webpack playground. |
| playground/AspireWithJavaScript/AspireJavaScript.NodeApp/app.js | Adjusts 404 handler wiring in Node playground app. |
| playground/AspireWithJavaScript/AspireJavaScript.MinimalApi/Properties/launchSettings.json | Removes launchUrl entries from playground launch settings. |
| playground/AspireWithJavaScript/AspireJavaScript.MinimalApi/AppHost.cs | Removes Swagger/OpenAPI setup from playground API. |
| playground/AspireWithJavaScript/AspireJavaScript.AppHost/AppHost.cs | Demonstrates WithBrowserDebugger() usage (with diagnostic suppression). |
| playground/AspireWithJavaScript/.vscode/settings.json | Sets dashboard browser preference for playground workspace. |
| extension/src/server/interactionService.ts | Minor formatting tweak in error handling path. |
| extension/src/loc/strings.ts | Adds localized browser display strings. |
| extension/src/editor/AspireEditorCommandProvider.ts | Expands AppHost detection to JS/TS and updates context keys. |
| extension/src/debugger/languages/node.ts | Adds Node debugger extension producing js-debug launch configuration. |
| extension/src/debugger/languages/browser.ts | Adds browser debugger extension producing js-debug launch configuration. |
| extension/src/debugger/debuggerExtensions.ts | Registers node/browser debugger extensions and passes isApphost flag. |
| extension/src/debugger/adapterTracker.ts | Adds restart detection/suppression for AppHost sessions via DAP tracker. |
| extension/src/debugger/AspireDebugSession.ts | Enables node apphost launching (runtimeExecutable extraction) and Aspire-session restart behavior. |
| extension/src/dcp/types.ts | Adds Node/Browser launch-config typings and isApphost flag. |
| extension/src/capabilities.ts | Adds node/browser capabilities and isNodeInstalled() hook. |
| extension/package.json | Adds activation/events & context menu support for apphost.ts/apphost.js and new context key. |
| extension/loc/xlf/aspire-vscode.xlf | Adds XLF entries for new strings and fixes a grammar issue. |
You can also share your feedback on Copilot code review. Take the survey.
…dpoint validation - Add InternalsVisibleTo for Aspire.Hosting.JavaScript.Tests to fix BrowserDebuggerResource accessibility - Use appHostFile.FullName in GuestRuntimeTests for cross-platform path compatibility - Update WithBrowserDebugger test to reflect deferred endpoint validation
…t() usage - Map browser names to pwa-prefixed debug adapter types (msedge -> pwa-msedge) - Delete debugConfiguration.cwd in browser debugger (matches comment) - Update comments explaining why First() is used instead of Single()
…ch apphost via extension
Member
Author
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
sebastienros
approved these changes
Mar 10, 2026
16 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR enables debugging JavaScript and TypeScript AppHost projects from the VS Code extension. Previously, only .NET (C#) and Python AppHosts could be run/debugged.
Problem
When a user clicks Debug or Run on a TypeScript AppHost (e.g.,
apphost.ts), the Aspire CLI resolves the runtime spec (fromTypeScriptLanguageSupport) and determines the correct command to launch the guest process — for TypeScript this isnpx tsx {appHostFile}. However, the VS Code extension needs to launch the process through VS Code's debug adapter (type'node'), which uses plainnodeby default. There was no mechanism to pass the custom runtime executable (npx) from the CLI through to the extension's debug configuration.Solution:
IGuestProcessLauncherStrategy PatternThis PR introduces a strategy pattern (
IGuestProcessLauncher) that cleanly separates how guest AppHost processes are launched:Architecture Details
IGuestProcessLauncher— Strategy interface with a single method:ProcessGuestLauncher— Default implementation that spawns an OS process. Used for:InstallDependenciesAsync(e.g.,npm install)PublishAsync(always runs as a direct process)ExtensionGuestLauncher— Delegates to the VS Code extension via JSON-RPC. The key trick: it prepends the runtime command asargs[0]before sending to the extension. This avoids changing the RPC protocol while still passing the custom runtime.Data-Driven Extension Launcher Selection
The decision of whether to use the extension launcher is driven by the
RuntimeSpecdata model, not hardcoded per language. Each language'sRuntimeSpec(provided byILanguageSupport.GetRuntimeSpec()) can setExtensionLaunchCapabilityto declare the VS Code extension capability required for extension-based launching:ExtensionLaunchCapability = "node"→ CLI checks if the extension has the"node"capabilityExtensionLaunchCapabilityasnull→ always use the default process launcherThis means new polyglot languages can opt into extension launching simply by setting
ExtensionLaunchCapabilityin theirRuntimeSpec— no CLI code changes needed.Extension-Side Flow (TypeScript)
When the extension receives a
launchAppHostRPC call for a JS/TS AppHost:AspireDebugSession.startAppHostdetects the file extension (.ts,.js, etc.)args[0]as theruntimeExecutable(e.g.,npx)args.slice(1)as the actual arguments (e.g.,["tsx", "apphost.ts"])NodeLaunchConfigurationwithruntime_executablesetnode.tsdebugger extension configures VS Code's debug adapter:runtimeExecutabletonpx(instead of defaultnode)runtimeArgs(for non-nodeexecutables)npx tsx apphost.tswith full debugging supportAdditional Extension Changes
node.tsandbrowser.tsdebugger extensions — Handle Node.js and browser launch configurations respectivelyNodeLaunchConfigurationandBrowserLaunchConfigurationtypes added to DCP typespackage.jsonactivation events and context menu items now support.tsand.jsfiles alongside.csAspireEditorCommandProviderdetects JS/TS apphosts viaimport ... from '...modules/aspire'patternsDebuggerProperties— Simplified launch configuration by removing the generic debugger properties pass-through in favor of typed configurationsisDeprecatedfromResourceDebuggerExtension— Simplified the interfacenodeandbrowsercapabilities are now properly gated behindisNodeInstalled()instead of always being presentNew Files
src/Aspire.Cli/Projects/IGuestProcessLauncher.cssrc/Aspire.Cli/Projects/ProcessGuestLauncher.cssrc/Aspire.Cli/Projects/ExtensionGuestLauncher.csextension/src/debugger/languages/node.tsextension/src/debugger/languages/browser.tstests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cstests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.csRefactoring
GuestRuntime.InstallDependenciesAsync— Was duplicating ~30 lines of inlineProcesscode. Now usesCreateDefaultLauncher()(aProcessGuestLauncher) to eliminate duplication.GuestRuntime.FindCommandremoved — No longer needed after refactoring.Checklist
RuntimeSpec.ExtensionLaunchCapabilityproperty on the experimentalRuntimeSpecclass