Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
d9500a1
Add AddDenoApp for Deno-native apps
rickylabs Jul 2, 2026
6f6a2f0
feat(hosting-js): full Deno flag surface for AddDenoApp
rickylabs Jul 3, 2026
9fe71cf
feat(hosting-js): pre-cache Deno image deps, set NODE_ENV, document s…
rickylabs Jul 3, 2026
f508946
feat(vscode-ext): add Deno debug adapter chain for AddDenoApp
rickylabs Jul 3, 2026
d5a3a1b
Fix Deno flag docs markdown lint
adamint Jul 4, 2026
e59d3a6
Fix Deno hosting debugger and publish behavior
adamint Jul 4, 2026
817bde3
Fix Deno task import map args
adamint Jul 4, 2026
227f040
Fix Deno task publish cache args
adamint Jul 6, 2026
ff1ce1f
Address Deno review feedback
davidfowl Jul 25, 2026
942524e
Merge main into Deno hosting PR
davidfowl Jul 25, 2026
abacdca
Fix Deno review feedback
davidfowl Jul 25, 2026
e7f81b7
Handle Deno task debug launches
davidfowl Jul 25, 2026
5c29498
Fix remaining Deno hosting feedback
davidfowl Jul 25, 2026
36b0b42
Address Deno follow-up review feedback
davidfowl Jul 25, 2026
d432598
Fix Deno publish edge cases
davidfowl Jul 25, 2026
38d1ebe
Fix Deno package-manager review feedback
davidfowl Jul 25, 2026
c9d6eb2
Address Deno hosting review coverage
davidfowl Jul 25, 2026
353ab42
Address Deno hosting review feedback
davidfowl Jul 25, 2026
b0fd0a0
Address Deno reviewer suppressed-comment feedback
davidfowl Jul 26, 2026
1ab7ffa
Align generated Deno package-script Dockerfile with Deno's Docker gui…
davidfowl Jul 26, 2026
b7d5be8
Reject comma-containing Deno permission values and correct deno serve…
davidfowl Jul 26, 2026
b81b01e
Reject serve host/port overrides and keep watch flags exclusive
davidfowl Jul 26, 2026
add3fb1
Follow POSIX double-quote escaping and generalize package-script docs
davidfowl Jul 26, 2026
bf6209f
Resolve the build image from the package manager when only a runtime …
davidfowl Jul 26, 2026
cf96718
Harden Deno shell boundary, flag conflicts, path checks, and permissi…
davidfowl Jul 26, 2026
6484832
Document generalized Deno managed-flag conflicts and cache-policy sup…
davidfowl Jul 26, 2026
cf9538e
Remove trailing blank line in deno-flag-surface.md
davidfowl Jul 26, 2026
ccbeb8c
Quote all Dockerfile RUN commands, reject unterminated quotes, fix in…
davidfowl Jul 26, 2026
06e6622
Forward resolution flags to deno cache and withdraw the serve endpoin…
davidfowl Jul 26, 2026
da8d131
Correct the PublishAsPackageScript docs about Deno dependency caching
davidfowl Jul 26, 2026
ea0d8b5
Unify Deno path validation and cover config aliases
davidfowl Jul 27, 2026
b5446ff
Address Deno integration review feedback
davidfowl Jul 29, 2026
11d007e
Address final Deno review feedback
davidfowl Jul 30, 2026
4360405
Honor OTLP protocol in Docker Compose
davidfowl Jul 30, 2026
78e9a72
Reject dotenv files in generated Deno images
davidfowl Jul 30, 2026
f0a85bb
Fix Deno permission and path validation
davidfowl Jul 30, 2026
e25f047
Keep Deno runnable without an OTLP endpoint
davidfowl Jul 30, 2026
36a5310
Document launch configuration callback context design
Aug 5, 2026
fc48eb7
docs: add launch configuration callback plan
Aug 6, 2026
ad326ea
Add launch configuration callback context
Aug 6, 2026
bfb9027
Fix launch producer restart handling
Aug 6, 2026
9a8c99a
Reset debug execution before rebuilding arguments
Aug 6, 2026
e9ea853
Migrate debug launch configuration producers
Aug 6, 2026
9efdff8
Finalize debug callback context API
Aug 6, 2026
65a56a7
Migrate debug support playground
Aug 6, 2026
ac32975
Fix debug support documentation whitespace
Aug 6, 2026
b0fcfd3
Address callback context review findings
Aug 6, 2026
8de33ce
Remove agent planning artifacts and restore load-bearing comments
Aug 7, 2026
900a331
Fix MAUI launch configuration regression and rename PrepareObjects
Aug 7, 2026
7d4d8fe
Address launch configuration context review
Aug 8, 2026
0ee07ef
Install Azure Functions Core Tools from release archive
Aug 8, 2026
2db433e
Recompute debug argument callbacks after original snapshot
Aug 8, 2026
6cb79d3
Validate legacy debug producer return type
Aug 8, 2026
1481f78
Revert unrelated Azure Functions tool install change
Aug 8, 2026
dcf4b09
Preserve launch override process args
Aug 8, 2026
024577a
Fix debug executable configuration snapshots
adamint Aug 9, 2026
24588ab
Reuse original argument resolutions across the debug rewrite
adamint Aug 9, 2026
5b8d2f3
Address review findings on debug launch configuration
adamint Aug 9, 2026
92901b8
Fix debug argument rewrite reuse and legacy overload validation
adamint Aug 10, 2026
64dd50d
Address debug launch context review feedback
Aug 10, 2026
3b95b28
Merge branch 'main' into adamint/issue-18956-launch-config-context
adamint Aug 10, 2026
a904ac7
Prevent later arg mutations from stripping debug-injected arguments
Aug 10, 2026
30d272c
Narrow debug launch context to resolved environment
Aug 10, 2026
35bea19
Clarify process-mode launch producer behavior
Aug 11, 2026
fa5c96c
Fix launch-override producer wiring
Aug 11, 2026
c5855b8
Clarify asynchronous debug producer guidance
Aug 11, 2026
7cdb91a
Cover the no-fallback rule when a launch configuration producer throws
Aug 11, 2026
b739005
Merge branch 'main' into adamint/issue-18956-launch-config-context
Aug 12, 2026
629c3ea
Merge remote-tracking branch 'upstream/main' into adamint/issue-18956…
Aug 13, 2026
130043f
Merge PR 19077 launch configuration changes
Aug 13, 2026
7447fcd
Merge latest main into Deno hosting PR
Aug 13, 2026
b9856e8
Document JavaScript runtime-specific hosting APIs
Aug 13, 2026
854a5a6
Validate Deno APIs through polyglot AppHosts
Aug 13, 2026
443c9f3
Honor OTLP protocols in Kubernetes publishing
Aug 13, 2026
7ae5fe6
Optimize Deno package-script Dockerfile layers
Aug 13, 2026
96cec63
Enable Deno telemetry only with OTLP endpoints
Aug 13, 2026
a842d49
Address Deno review follow-ups
Aug 13, 2026
50f3c87
Trigger Deno CI
Aug 13, 2026
9d03991
Use repository-local Deno setup action
Aug 13, 2026
ad47f25
Handle Deno entrypoint paths on Windows
Aug 13, 2026
a852b43
Fix final Deno CI dependencies
Aug 13, 2026
ecaf33f
Use HTTP for Deno telemetry validation
Aug 13, 2026
8bdb11f
Address final Deno hosting review feedback
Aug 13, 2026
4b0cfb9
Merge main into Deno hosting branch
Aug 13, 2026
1558a2d
Fix OTLP activation in publishers
Aug 13, 2026
a50d77c
Reject unsupported Deno publish modes
Aug 13, 2026
8198435
Fix explicit Deno launch metadata
Aug 13, 2026
ffd290c
Reject invalid Deno permission values
Aug 13, 2026
46c55c2
Merge origin/main into Deno hosting
sebastienros Sep 1, 2026
61f50c6
Fix late Deno debugger cleanup registration
sebastienros Sep 1, 2026
459393a
Fix Deno Python AppHost type validation
sebastienros Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions docs/deno-flag-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Deno flag surface for `AddDenoApp`

`AddDenoApp` maps the Deno CLI onto Aspire's resource model. The fluent `WithDeno*` methods on
`IResourceBuilder<DenoAppResource>` let a caller express the full Deno runtime flag surface, so a Deno
workload no longer has to fall back to a raw `AddExecutable("name", "deno", workdir, args...)`.

## Emitted command shape

```text
deno <run|task|serve> [runtime-flags] <entrypoint|task> [script-args]
```

Runtime flags are emitted in a fixed, valid-CLI order regardless of the order the fluent methods are
called:

1. Permissions — `-A`/`--allow-all` or granular `--allow-*`/`--deny-*` (category order: net, read,
write, run, env, sys, ffi; allow before deny)
2. Resolution — `--config`, `--import-map`, `--lock`/`--no-lock`, `--node-modules-dir[=mode]`
3. `--unstable-*`
4. `--watch` / `--watch-hmr`
5. `--inspect` / `--inspect-brk` / `--inspect-wait` (optional `host:port`)
6. Raw runtime args (`WithDenoRuntimeArgs`, escape hatch)

Then the entrypoint (or task name), then script args (`WithDenoScriptArgs`).

## Backward compatibility

A bare `AddDenoApp(name, workdir, entrypoint)` with no `WithDeno*` calls still emits
`deno run -A <entrypoint>`. The blanket `-A` grant is emitted by default (tri-state `AllowAll`): it is
kept unless the caller either opts out with `WithDenoAllowAll(false)` or configures at least one
granular `--allow-*` flag, in which case least-privilege is assumed and `-A` is dropped.

## Working directory and environment variables

These are **not** Deno flags in Aspire's model:

- **Working directory** is the `appDirectory` passed to `AddDenoApp` (the resource's
`WorkingDirectory`). Deno inherits the process cwd from Aspire, so there is no `--cwd` method — a
separate `--cwd` would desync from the resource working directory. For `deno task`, use standard
Aspire working-directory configuration rather than injecting `--cwd` via `WithDenoRuntimeArgs`.
- **Environment variables** are set with the standard `WithEnvironment(...)`. Do not use Deno's
`--env-file`: Aspire owns env injection (service discovery, `OTEL_*`, `PORT`, cert paths), and a
Deno-side dotenv load runs outside Aspire's ordering and can silently shadow injected values.

---

## Capabilities Aspire's resource model cannot (safely) express

The following are genuine limitations or conflicts with how Aspire injects endpoints, environment
variables, service discovery, and debugging. They are surfaced here rather than as dedicated methods.

### 1. Least-privilege `--allow-net` with a fixed host list

Aspire allocates endpoint ports dynamically in run mode and injects service-discovery hosts/ports as
environment variables at launch. A hard-coded `WithDenoAllowNet("host:port", ...)` cannot enumerate
those dynamic targets ahead of time, so outbound calls to Aspire-discovered services (or inbound
binding to a randomly-allocated endpoint port) may be blocked with `NotCapable`. Use `-A` (default),
or `WithDenoAllowNet()` with **no** value list (allow all hosts), when the app relies on Aspire
service discovery. A scoped allow-list is only safe for endpoints the caller controls end-to-end.

### 2. Least-privilege `--allow-env` with a fixed variable list

Aspire injects a broad, partly non-deterministic set of environment variables (`OTEL_*`,
`OTEL_EXPORTER_OTLP_*`, `DENO_CERT`/`DENO_TLS_CA_STORE`, `PORT`, service-discovery keys). Restricting
`--allow-env` to an application-authored subset makes the runtime and injected integrations throw when
they read an un-granted variable. A scoped `WithDenoAllowEnv(...)` must include every Aspire-injected
key the app or runtime reads, which the caller generally cannot know statically. Prefer `-A` or an
unscoped `WithDenoAllowEnv()`.

### 3. `deno serve --port` / `--host`

`deno serve` has its own `--port`/`--host` flags, but Aspire allocates the port and communicates it
via the injected `PORT` environment variable and endpoint annotations. Passing `--port` (through
`WithDenoRuntimeArgs`) overrides Aspire's allocation and desyncs the endpoint the dashboard/service
discovery advertise from the port the process actually binds. Bind to the injected `PORT` env var
instead; `WithDenoServe()` deliberately exposes no port method.
Comment thread
davidfowl marked this conversation as resolved.
Resolved

### 4. `--inspect*` versus Aspire/VS Code debugging

`AddDenoApp` already wires a `deno` debug launch configuration (`SupportsDebuggingAnnotation`) that the
Aspire IDE extension uses to attach an inspector. `WithDenoInspect*` is provided for callers who launch
Deno's inspector manually (e.g. an external Chrome DevTools session), but the two paths can contend for
the same inspector port. Do not combine a manual `WithDenoInspectBrk()` with IDE-driven debugging on
the same resource.

### 5. `--watch` / `--watch-hmr` in published containers

Watch/HMR are run-mode developer conveniences. The value is honored for the run-mode command line and
is also reflected in the generated Dockerfile entrypoint for fidelity, but file-watching has no useful
meaning in an immutable published container image and should be treated as run-mode only.
Comment thread
davidfowl marked this conversation as resolved.
Resolved

### 6. `deno task` permission flags

For `deno task`, permissions are defined by the task's own command inside `deno.json`, not on the
`deno task` invocation. `WithDenoTask(...)` therefore intentionally does **not** emit permission flags
(`--allow-*`/`--deny-*`); configure them in the task definition. Resolution flags (`--config`,
`--import-map`, `--lock`, `--node-modules-dir`) and `--unstable-*` are still emitted because they are
valid `deno task` options.

### 7. Interactive / TTY-oriented flags

Flags that assume an interactive terminal (for example `deno run` prompting for permissions when a
grant is missing) do not apply: Aspire runs the process non-interactively with stdout/stderr captured
for the dashboard. Always grant the permissions the workload needs explicitly (or use `-A`) rather than
relying on interactive permission prompts.

## Published container image

`AddDenoApp` generates a multi-stage Dockerfile (when the app directory has no hand-written
`Dockerfile`) tuned for Deno's execution model:

- **Dependency pre-caching.** The build stage runs `deno cache <entrypoint>` (or
`deno cache --frozen <entrypoint>` when a `deno.lock` is present) to resolve the entrypoint's full
module graph — remote URLs and `npm:`/`jsr:` specifiers — into `DENO_DIR`. `DENO_DIR` is pinned to
`/deno-dir` in both stages and copied `--from=build` into the runtime stage, so the image starts
offline / air-gapped and avoids a cold-start dependency fetch. There is no `node_modules` stage:
Deno caches under `DENO_DIR` rather than a project-local folder.
- **`NODE_ENV=production`.** Set in the runtime stage (and in run mode via the resource defaults) so
Deno's Node-compatibility mode — `npm:` resolution and package.json `exports` conditions — behaves
like the Node/Bun variants.
- **Native OpenTelemetry.** `OTEL_DENO=true` is exported by default and flows to the OTLP endpoint
configured by `WithOtlpExporter`. Native OTel is **stable** on the Deno versions the pinned
`denoland/deno:2` tag resolves to (verified on Deno 2.9.0), so **no `--unstable-otel` flag is
emitted** — the env var alone activates trace/metric/log export. The run-mode command and the
published container entrypoint stay consistent.

## Escape hatch

Any Deno flag without a dedicated method (for example `--v8-flags=...`, `--seed`, `--cached-only`,
`--reload`, `--env-file`) can be injected verbatim before the entrypoint with `WithDenoRuntimeArgs(...)`,
giving full parity with `AddExecutable("name", "deno", workdir, args...)`. This is unvalidated by
design; the conflicts documented above still apply when the injected flag overlaps Aspire-managed
concerns.
11 changes: 11 additions & 0 deletions extension/src/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type Capability =
| 'node' // Support for running Node.js projects
| 'bun' // Support for running Bun projects
| 'oven.bun-vscode' // Bun debug adapter extension identifier
| 'deno' // Support for running Deno projects (built-in to VS Code via js-debug)
| 'browser' // Support for browser debugging (built-in to VS Code via js-debug)
| 'maui' // Support for running .NET MAUI projects
| 'ms-dotnettools.dotnet-maui' // MAUI debug adapter extension identifier
Expand Down Expand Up @@ -63,6 +64,12 @@ export function isBunInstalled() {
return isExtensionInstalled("oven.bun-vscode");
}

export function isDenoInstalled() {
// Deno debugging uses VS Code's built-in js-debug (pwa-node) attaching to Deno's V8 inspector via
// --inspect-wait, so no third-party debug adapter extension is required.
return true;
}

export function getSupportedCapabilities(): Capabilities {
const capabilities: Capabilities = ['prompting', 'baseline.v1', 'secret-prompts.v1', 'file-pickers.v1', 'build-dotnet-using-cli'];

Expand Down Expand Up @@ -102,6 +109,10 @@ export function getSupportedCapabilities(): Capabilities {
capabilities.push("oven.bun-vscode");
}

if (isDenoInstalled()) {
capabilities.push("deno");
}

if (isMauiInstalled()) {
capabilities.push("maui");
capabilities.push("ms-dotnettools.dotnet-maui");
Expand Down
10 changes: 8 additions & 2 deletions extension/src/dcp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function isGoLaunchConfiguration(obj: any): obj is GoLaunchConfiguration
}

export interface JavaScriptRuntimeLaunchConfiguration extends ExecutableLaunchConfiguration {
type: "node" | "bun";
type: "node" | "bun" | "deno";
script_path?: string;
runtime_executable?: string;
working_directory?: string;
Expand All @@ -68,7 +68,7 @@ export interface JavaScriptRuntimeLaunchConfiguration extends ExecutableLaunchCo
}

export function isJavaScriptRuntimeLaunchConfiguration(obj: any): obj is JavaScriptRuntimeLaunchConfiguration {
return obj && (obj.type === 'node' || obj.type === 'bun');
return obj && (obj.type === 'node' || obj.type === 'bun' || obj.type === 'deno');
}

export type NodeLaunchConfiguration = JavaScriptRuntimeLaunchConfiguration & { type: "node" };
Expand All @@ -83,6 +83,12 @@ export function isBunLaunchConfiguration(obj: any): obj is BunLaunchConfiguratio
return obj && obj.type === 'bun';
}

export type DenoLaunchConfiguration = JavaScriptRuntimeLaunchConfiguration & { type: "deno" };

export function isDenoLaunchConfiguration(obj: any): obj is DenoLaunchConfiguration {
return obj && obj.type === 'deno';
}

export interface BrowserLaunchConfiguration extends ExecutableLaunchConfiguration {
type: "browser";
url?: string;
Expand Down
4 changes: 4 additions & 0 deletions extension/src/debugger/AspireDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,10 @@ export class AspireDebugSession implements vscode.DebugAdapter {
return await vscode.debug.startDebugging(workspaceFolder, debugConfig, parentSession);
}

registerResourceCleanup(disposable: vscode.Disposable): void {
this._disposables.push(disposable);
}
Comment thread
sebastienros marked this conversation as resolved.
Resolved

private getDebugSessionWorkspaceFolder(debugConfig: AspireResourceExtendedDebugConfiguration): vscode.WorkspaceFolder | undefined {
const resourcePath = typeof debugConfig.cwd === 'string'
? debugConfig.cwd
Expand Down
7 changes: 6 additions & 1 deletion extension/src/debugger/debuggerExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { debugProject, runProject } from "../loc/strings";
import { getEnvironmentWithoutE2EBridgeVariables, mergeEnvs } from "../utils/environment";
import { extensionLogOutputChannel } from "../utils/logging";
import { projectDebuggerExtension } from "./languages/dotnet";
import { isAzureFunctionsExtensionInstalled, isBunInstalled, isCsharpInstalled, isGoInstalled, isMauiInstalled, isPythonInstalled } from '../capabilities';
import { isAzureFunctionsExtensionInstalled, isBunInstalled, isCsharpInstalled, isDenoInstalled, isGoInstalled, isMauiInstalled, isPythonInstalled } from '../capabilities';
import { pythonDebuggerExtension } from "./languages/python";
import { nodeDebuggerExtension } from "./languages/node";
import { browserDebuggerExtension } from "./languages/browser";
import { azureFunctionsDebuggerExtension } from "./languages/azureFunctions";
import { goDebuggerExtension } from "./languages/go";
import { bunDebuggerExtension } from "./languages/bun";
import { denoDebuggerExtension } from "./languages/deno";
import { mauiDebuggerExtension } from "./languages/maui";
import { isDirectory } from "../utils/io";
import { waitForRunStartIdle } from "./runStartRegistry";
Expand Down Expand Up @@ -96,6 +97,10 @@ export function getResourceDebuggerExtensions(): ResourceDebuggerExtension[] {
extensions.push(bunDebuggerExtension);
}

if (isDenoInstalled()) {
extensions.push(denoDebuggerExtension);
}

if (isMauiInstalled()) {
extensions.push(mauiDebuggerExtension);
}
Expand Down
Loading