Skip to content

Add Deno hosting: AddDenoApp / DenoAppResource in Aspire.Hosting.JavaScript - #18628

Open
rickylabs wants to merge 88 commits into
microsoft:mainfrom
rickylabs:feat/deno-hosting-adddenoapp
Open

Add Deno hosting: AddDenoApp / DenoAppResource in Aspire.Hosting.JavaScript#18628
rickylabs wants to merge 88 commits into
microsoft:mainfrom
rickylabs:feat/deno-hosting-adddenoapp

Conversation

@rickylabs

@rickylabs rickylabs commented Jul 3, 2026

Copy link
Copy Markdown

Add Deno hosting to Aspire.Hosting.JavaScript

Adds first-class Deno application hosting alongside Node and Bun through AddDenoApp and DenoAppResource.

What

  • Runs Deno entrypoints with deno run, named deno task tasks, or deno serve handlers.
  • Exposes a consolidated 16-method WithDeno* surface for permissions, config/import maps/locks, node modules, unstable features, watch, inspection, execution modes, and arguments.
  • Supports generated container and Docker Compose publishing using the pinned non-root denoland/deno:2.9.0 image.
  • Enables Deno native OpenTelemetry over OTLP HTTP/protobuf when a collector is available, without making dashboard-free AppHosts depend on an observability backend.
  • Integrates development certificate trust through DENO_CERT, DENO_TLS_CA_STORE, and OTEL_EXPORTER_OTLP_CERTIFICATE.
  • Adds VS Code debugging for direct run and serve launches through the built-in pwa-node adapter.
  • Projects the API to TypeScript AppHosts through Aspire's polyglot export system.
  • Marks all public Deno APIs experimental with diagnostic ASPIREDENO001.

Usage

Direct application

#pragma warning disable ASPIREDENO001

var builder = DistributedApplication.CreateBuilder(args);

builder.AddDenoApp("api", "../DenoApi", "main.ts")
    .WithHttpEndpoint(env: "PORT")
    .WithHttpHealthCheck("/")
    .WithExternalHttpEndpoints();

builder.Build().Run();
const port = Number(Deno.env.get("PORT") ?? 8000);

Deno.serve({ port }, () =>
    new Response("Hello from Deno!"));

Local execution defaults to deno run -A main.ts. Generated containers use the safer --allow-net --allow-env default.

Granular permissions

builder.AddDenoApp("worker", "../Worker", "worker.ts")
    .WithDenoAllowAll(false)
    .WithDenoAllow(DenoPermissionKind.Net, "api.example.com:443")
    .WithDenoAllow(DenoPermissionKind.Env, "API_KEY")
    .WithDenoAllow(DenoPermissionKind.Read, "./data")
    .WithDenoDeny(DenoPermissionKind.Read, "./data/private");

Supported permission kinds are Net, Read, Write, Run, Env, Import, Sys, and Ffi.

deno.json task

{
  "tasks": {
    "start": "deno run --allow-net --allow-env main.ts"
  }
}
builder.AddDenoApp("api", "../DenoApi", "main.ts")
    .WithDenoTask("start")
    .WithHttpEndpoint(env: "PORT");

WithRunScript("start") is also supported. Task permissions are controlled by the task command itself.

deno serve

export default {
  fetch(): Response {
    return new Response("Hello from deno serve!");
  }
};
builder.AddDenoApp("api", "../DenoApi", "handler.ts")
    .WithDenoServe()
    .WithExternalHttpEndpoints();

WithDenoServe creates the HTTP endpoint automatically. Published containers conventionally use port 8000 as the process-local target while Aspire allocates unique host ports.

Configuration and development options

builder.AddDenoApp("api", "../DenoApi", "main.ts")
    .WithDenoConfig("deno.json")
    .WithDenoImportMap("import_map.json")
    .WithDenoLock("deno.lock")
    .WithDenoNodeModulesDir(DenoNodeModulesDirMode.Auto)
    .WithDenoUnstable("kv", "worker-options")
    .WithDenoWatch(hmr: true)
    .WithDenoScriptArgs("--environment", "development");

WithDenoRuntimeArgs provides an escape hatch for runtime flags not represented by a dedicated API. WithDenoInspect supports --inspect, --inspect-brk, and --inspect-wait with an optional host and port.

TypeScript AppHost

import { createBuilder } from "./.aspire/modules/aspire.mjs";

const builder = await createBuilder();

await builder
    .addDenoApp("api", "./DenoApi", "main.ts")
    .withHttpEndpoint({ env: "PORT" })
    .withExternalHttpEndpoints();

await builder
    .addDenoApp("worker", "./Worker", "worker.ts")
    .withRunScript("start");

await builder.build().run();

Supported scenarios

Scenario Support
Direct JavaScript/TypeScript entrypoints AddDenoApp; runs with deno run
Named deno.json tasks WithDenoTask or WithRunScript
Fetch handlers WithDenoServe, including automatic endpoint configuration
Least-privilege execution Granular allow/deny APIs and explicit allow-all control
Deno config, import maps, and lockfiles Dedicated fluent APIs with publish-time path validation
npm compatibility DenoNodeModulesDirMode.None, Auto, or Manual locally
Watch/HMR and unstable capabilities WithDenoWatch and WithDenoUnstable
Script and runtime arguments Ordered APIs for pre-entrypoint and post-entrypoint arguments
Service discovery and health checks Standard Aspire WithReference, endpoint, and health-check APIs
Native telemetry Traces, metrics, and logs through Deno's built-in OTLP HTTP/protobuf exporter
Dashboard-free execution Runs normally without requiring a collector; native telemetry remains off until an endpoint is available
VS Code debugging Automatic for direct run/serve; configurable inspector modes
Generated container publishing Multi-stage, non-root Deno image with dependency caching
Docker Compose deployment Required OTLP protocol and endpoint are projected correctly
Polyglot AppHosts Exported C# API is available from TypeScript AppHosts

Run and publish behavior

Generated publishing:

  • Uses denoland/deno:2.9.0 for build and runtime stages and runs as the non-root deno user.
  • Defaults direct run/serve entrypoints to --allow-net --allow-env; deny-only policies narrow those defaults without broadening access to -A.
  • Pre-caches direct entrypoint dependencies into DENO_DIR=/deno-dir, carries the cache into the runtime stage, and starts cached-only unless the caller selects another policy.
  • Supports WithBuildScript and PublishAsPackageScript for task-based images.
  • Rejects --env-file rather than copying dotenv files into image layers; Aspire environment/secret injection or a user-authored Dockerfile remains available.
  • Normalizes generated-container paths, including read/write/FFI permission values and raw --cert paths; app/config/import-map/lock paths that escape the build context are rejected.
  • Emits exec-form entrypoints when shell expansion is unnecessary, including support for shell-less Deno images.
  • Preserves OtlpExporterAnnotation in publish mode so deployment targets can inject the appropriate collector. Docker Compose supplies Deno with the dashboard's HTTP OTLP endpoint and http/protobuf, while default and explicit gRPC exporters continue using gRPC.

Intentional limitations:

  • Deno must be installed and available on PATH for local execution.
  • Automatic VS Code debugging is unavailable for deno task because Deno rejects inspector flags on task launches.
  • Generated publishing does not support manually managed node_modules; use Auto or a user-authored Dockerfile.

Validation

  • Aspire.Hosting.JavaScript.Tests: 384 passed, 0 failed, including real dashboard-free direct/task startup and dashboard telemetry coverage.
  • Focused core OTLP exporter tests: 17 passed, 0 failed.
  • Aspire.Hosting.Docker.Tests: 100 passed, 0 failed, 1 Windows-only skip.
  • Deno ATS/API projection checks: 8 passed, including the exact exported surface and experimental attributes.
  • VS Code Deno debugger tests: 15 passed.
  • Extension TypeScript compilation and lint passed.
  • Ran the checked-in playground/AspireWithDeno AppHost against Deno 2.9.0: direct and task resources were healthy, returned distinct responses, and emitted structured logs and HTTP traces to the dashboard.
  • Published the playground to Docker Compose, built both generated images, deployed the stack, called both services successfully, and verified deployed logs/traces reached the dashboard over OTLP HTTP/protobuf.

rickylabs and others added 4 commits July 2, 2026 19:26
AddDenoApp previously hardcoded `deno run -A <script>`, which could not
express the flags a polyglot framework (NetScript) needs, forcing callers
back to raw AddExecutable. Close that gap with a composable fluent flag
surface on DenoAppResource.

New DenoCommandLineAnnotation captures the complete Deno command line and,
when present, fully controls the emitted arg vector in valid CLI order
(runtime flags before the entrypoint, script args after). Added WithDeno*
extension methods:

- Permissions: WithDenoAllowAll + granular allow/deny for
  net/read/write/run/env/sys/ffi (optional comma-separated value lists),
  with least-privilege auto-dropping -A once a granular allow is set.
- Resolution: WithDenoConfig, WithDenoImportMap, WithDenoLock,
  WithDenoNoLock, WithDenoNodeModulesDir.
- WithDenoUnstable (bare or qualified), WithDenoWatch(hmr),
  WithDenoInspect/Brk/Wait (optional host:port).
- Modes: WithDenoRun / WithDenoTask / WithDenoServe.
- Args: WithDenoScriptArgs (after entrypoint) and WithDenoRuntimeArgs
  (raw escape hatch before entrypoint = AddExecutable parity).

The published Dockerfile entrypoint now mirrors the configured command.
Bare AddDenoApp remains backward compatible (`deno run -A <entrypoint>`).

Documented Aspire-model limitations (least-privilege net/env vs injected
endpoints/env/service-discovery, deno serve --port, inspector contention,
watch in containers, task permissions) in docs/deno-flag-surface.md.

Tests: 13 new cases in AddDenoAppTests cover each flag category, ordering,
the three modes, the AddExecutable-replacement path, and backward compat.
Full class green: 35/35 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wKHquACkXnWPDgJYhhFjN
…table OTel

Close Bun-parity gaps for AddDenoApp so the published Deno image and runtime
defaults are at-or-above the Bun block.

GAP microsoft#2 (offline/air-gapped + cold start): the generated multi-stage Dockerfile
now pre-caches the entrypoint's full module graph into a deterministic DENO_DIR
(/deno-dir) in the build stage (`deno cache <entrypoint>`, or
`deno cache --frozen <entrypoint>` when a deno.lock exists) and copies
/deno-dir into the runtime stage, so the container starts without a network
dependency fetch. Deno caches under DENO_DIR (no node_modules stage).

GAP microsoft#3: set NODE_ENV=production in the runtime stage and in WithDenoDefaults
(development/production by environment), mirroring the Bun publish block, so
Deno's Node-compatibility mode behaves.

OTEL: verified empirically on Deno 2.9.0 (what denoland/deno:2 resolves to)
that native OpenTelemetry is STABLE — OTEL_DENO=true alone activates and
exports; `--unstable-otel` is no longer listed by `deno run --help=unstable`
and is only a backward-compat no-op. Per the stable-path guidance, no flag is
emitted; a code comment documents the verification and version.

Tests: add publish tests asserting the `deno cache`/`--frozen` step, DENO_DIR
copy, and NODE_ENV; update the Dockerfile/manifest verified snapshots. Document
published-image behavior in docs/deno-flag-surface.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hosting side emits a type:'deno' launch config, but the VS Code extension
had a full Bun debug chain with no Deno equivalent, so Deno debug sessions
never attached. Mirror the Bun/Node chain for Deno:

- languages/deno.ts: new ResourceDebuggerExtension mapping the Deno launch
  config onto js-debug's built-in pwa-node adapter (no third-party extension).
  It drives the launch through runtimeExecutable + runtimeArgs and attaches via
  attachSimplePort to Deno's V8 inspector, injecting `--inspect-wait` after the
  sub-command (run/serve/task) so attach is reliable (blocks until the debugger
  connects, no missed early code). Respects a user-configured WithDenoInspect*
  flag instead of double-injecting.
- debuggerExtensions.ts: register denoDebuggerExtension (gated on isDenoInstalled).
- capabilities.ts: add the 'deno' capability + isDenoInstalled() (true; Deno uses
  built-in js-debug).
- dcp/types.ts: accept 'deno' in isJavaScriptRuntimeLaunchConfiguration and add
  DenoLaunchConfiguration + guard.
- loc/strings.ts: add denoDisplayName/denoLabel.
- test/denoDebugger.test.ts: cover pwa-node mapping, --inspect-wait injection
  (run + task), user-inspector passthrough, and runtime_executable fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings July 3, 2026 04:05
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🚀 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/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 18628

Or

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

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jul 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds first-party Deno hosting support to Aspire.Hosting.JavaScript, including a new AddDenoApp resource API, a fluent Deno CLI flag surface, publish-time Dockerfile generation (with Deno module graph pre-caching), functional/unit coverage, and VS Code extension support for attaching the built-in js-debug adapter to Deno via the V8 inspector.

Changes:

  • Introduces DenoAppResource + AddDenoApp and a WithDeno* fluent surface for permissions/resolution/watch/inspect/modes/args.
  • Adds publish-time Dockerfile generation for Deno using denoland/deno:2 with a pinned DENO_DIR cache copied into runtime.
  • Adds coverage (unit + functional) and wires VS Code extension support for type:"deno" debug sessions.

Reviewed changes

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

Show a summary per file
File Description
src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs Adds AddDenoApp, Deno defaults (OTEL/cert trust), publish Dockerfile generation, and WithDeno package-manager integration.
src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs Adds the WithDeno* fluent Deno CLI flag surface and arg builder logic.
src/Aspire.Hosting.JavaScript/DenoCommandLineAnnotation.cs Adds internal annotation types/enums to model Deno command-line settings.
src/Aspire.Hosting.JavaScript/DenoAppResource.cs Adds the Deno resource type deriving from JavaScriptAppResource.
src/Aspire.Hosting.JavaScript/api/Aspire.Hosting.JavaScript.cs Updates the public API baseline for the new Deno APIs/types.
src/Aspire.Hosting.JavaScript/api/Aspire.Hosting.JavaScript.ats.txt Updates ATS handle/capability declarations for Deno.
tests/Aspire.Hosting.JavaScript.Tests/AddDenoAppTests.cs Adds unit tests for args, permissions, publish Dockerfile content, cert trust, and debug config emission.
tests/Aspire.Hosting.JavaScript.Tests/DenoAppFixture.cs Adds a test fixture that boots real Deno apps (direct + task) under the testing builder.
tests/Aspire.Hosting.JavaScript.Tests/DenoFunctionalTests.cs Adds functional tests that hit real HTTP endpoints from Deno apps.
tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddDenoAppTests.VerifyManifest.verified.txt Snapshot for Deno executable manifest output.
tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddDenoAppTests.VerifyDockerfile_includePackageJson=True.verified.txt Snapshot for generated Dockerfile (package.json present).
tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddDenoAppTests.VerifyDockerfile_includePackageJson=False.verified.txt Snapshot for generated Dockerfile (package.json absent).
tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddDenoAppTests.VerifyDockerfileWithCustomBaseImage.verified.txt Snapshot for custom build/runtime base images.
tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddDenoAppTests.VerifyDockerfileEmitsPerDockerfileDockerignore.verified.txt Snapshot for emitted per-Dockerfile dockerignore content.
extension/src/debugger/languages/deno.ts Implements Deno debug configuration mapping to js-debug (pwa-node) and inspector injection.
extension/src/debugger/debuggerExtensions.ts Registers the Deno debugger extension when capability is present.
extension/src/dcp/types.ts Extends launch configuration typing to include type: "deno".
extension/src/capabilities.ts Adds the deno capability and availability check.
extension/src/loc/strings.ts Adds display label helpers for Deno debugging.
extension/src/test/denoDebugger.test.ts Adds unit tests for the Deno debugger extension behavior.
docs/deno-flag-surface.md Documents the supported Deno CLI surface and intentional limitations.
playground/AspireWithDeno/aspire.config.json Adds a playground scenario config for Deno AppHost usage.
playground/AspireWithDeno/apphost.mts Minimal TypeScript AppHost demonstrating addDenoApp direct + task modes.
playground/AspireWithDeno/package.json Playground package metadata and aspire run script.
playground/AspireWithDeno/README.md Playground documentation and rationale for defaults like -A / OTEL.
playground/AspireWithDeno/tsconfig.json Playground TS config for the AppHost.
playground/AspireWithDeno/DenoFrontend/main.ts Minimal Deno HTTP server used by playground and functional tests.
playground/AspireWithDeno/DenoFrontend/deno.json Task definition to exercise deno task start.

Comment thread src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs Outdated
Comment thread src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs
Comment thread src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@davidfowl

David Fowler (davidfowl) commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

PR Testing Report

PR Information

Artifact Version Verification

  • Expected Commit: f508946
  • Installed PR CLI Version: 13.5.0-pr.18628.gf5089461
  • Extension Source/VSIX: built from short checkout at f508946, extension package version 1.16.0
  • Status: ✅ Verified

Changes Analyzed

Files Changed

  • src/Aspire.Hosting.JavaScript/DenoAppResource.cs - added Deno resource type
  • src/Aspire.Hosting.JavaScript/DenoCommandLineAnnotation.cs - added Deno command-line annotation model
  • src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs - added Deno fluent flag surface
  • src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs - added AddDenoApp, defaults, Dockerfile publishing, and VS Code launch config support
  • extension/src/debugger/languages/deno.ts and related extension files - added Deno debugger support
  • tests/Aspire.Hosting.JavaScript.Tests/*Deno* and snapshots - added hosting/unit/functional coverage
  • playground/AspireWithDeno/** and docs/deno-flag-surface.md - added sample/docs

Change Categories

  • CLI changes detected
  • Hosting integration changes
  • Dashboard changes
  • CI infrastructure changes
  • VS Code extension changes
  • Test changes
  • Docs/playground changes

Test Scenarios Executed

Scenario 1: Targeted hosting source validation

Objective: Verify the new Deno hosting source tests and snapshots pass from the PR checkout.
Coverage Type: Source/unit validation
Status: ✅ Passed

Steps:

  1. Ran ./restore.sh from the PR checkout.
  2. Ran dotnet test --project tests/Aspire.Hosting.JavaScript.Tests/Aspire.Hosting.JavaScript.Tests.csproj --no-launch-profile -- --filter-class "*.AddDenoAppTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true".

Evidence:

  • Restore log: session artifact: restore.log
  • Test log: session artifact: add-deno-tests.log

Observations:

  • 37 targeted AddDenoApp tests passed.

Scenario 2: VS Code extension build and unit validation

Objective: Verify the PR extension source compiles, lints, and passes VS Code unit tests.
Coverage Type: Build/unit validation
Status: ✅ Passed

Steps:

  1. Ran extension/build.sh in the PR checkout.
  2. Ran corepack yarn run test; the first attempt failed before tests due the known macOS VS Code IPC socket path-length limit in the long worktree path.
  3. Created a short checkout at temporary short VS Code checkout and reran extension/build.sh plus corepack yarn run test.

Evidence:

  • Build log: session artifact: extension-build-short.log
  • Unit test log: session artifact: extension-test-short.log

Observations:

  • Short-path extension validation passed with 1106 passing and 2 pending tests.
  • The long-path failure was environmental: IPC handle ... is longer than 103 chars / listen EINVAL.

Scenario 3: Deno debugger support in a real Extension Host

Objective: Verify Deno debugger registration and launch configuration generation from a real VS Code Extension Host, not only unit tests.
Coverage Type: User-visible E2E
Status: ✅ Passed

Steps:

  1. Added a temporary focused E2E spec in the short checkout only.
  2. Ran ASPIRE_EXTENSION_E2E_SPEC='out/test-e2e/test-e2e/denoDebugger.e2e.test.js' ASPIRE_EXTENSION_E2E_CLI_PATH='temporary short VS Code checkout/artifacts/bin/Aspire.Cli/Debug/net10.0/aspire' corepack yarn run test:e2e.
  3. The spec activated the Aspire extension, used the E2E control bridge, and verified Deno debugger support plus existing-inspector-port handling.

Evidence:

  • E2E log: session artifact: deno-debugger-e2e-activated.log
  • E2E diagnostics: temporary short VS Code checkout/extension/.test-results/e2e/all
  • Storage diagnostics: temporary short VS Code checkout/extension/.test-storage/all/<run-id>

Observations:

  • 2 focused E2E tests passed.
  • Verified Deno debugger support registration and generated pwa-node launch config in the Extension Host.

Scenario 4: Dogfood CLI Deno run and task apps

Objective: Verify the installed PR CLI can create a fresh AppHost that runs both direct deno run and deno task Deno apps.
Coverage Type: Happy path
Status: ✅ Passed

Steps:

  1. Installed the PR dogfood CLI in the repo container runner and verified 13.5.0-pr.18628.gf5089461.
  2. Created a fresh file-based C# aspire-empty app using the PR hive.
  3. Added Aspire.Hosting.JavaScript via #:package.
  4. Added denodirect with AddDenoApp(..., "main.ts") and denotask with WithRunScript("start").
  5. Ran aspire start, waited for both resources to become healthy, curled both endpoints, captured describe --format Json, and stopped the AppHost.

Evidence:

  • Log: session artifact: container-deno-happy-publish.log
  • Describe JSON: temporary container workspace/scenario-deno-happy/describe.json
  • Resource logs: temporary container workspace/scenario-deno-happy/denodirect.logs.txt, temporary container workspace/scenario-deno-happy/denotask.logs.txt

Observations:

  • denodirect became healthy and returned direct-ok.
  • denotask became healthy and returned task-ok.
  • describe showed denodirect app args ["run", "-A", "main.ts"] and denotask app args ["task", "start"].
  • Logs showed denodirect launched with deno run -A main.ts and denotask launched with deno task start.

Scenario 5: Deno Dockerfile publishing with WithDenoTask

Objective: Verify publish artifacts for the new Deno Dockerfile support, including cache pre-warming, runtime cache copy, non-root user, and task entrypoint when using the new Deno flag surface.
Coverage Type: Publish artifact validation
Status: ✅ Passed

Steps:

  1. Created a fresh file-based C# app using the PR hive.
  2. Added Aspire.Hosting.JavaScript and Aspire.Hosting.Docker via #:package.
  3. Added AddDockerComposeEnvironment("compose").
  4. Published one direct Deno app and one Deno task app using .WithDenoTask("start").
  5. Validated generated Dockerfiles and per-Dockerfile dockerignore files.

Evidence:

  • Log: session artifact: container-deno-publish-withdenotask.log
  • Published artifacts: temporary container workspace/scenario-deno-publish-withdenotask/publish-output/

Observations:

  • denodirect.Dockerfile contained FROM denoland/deno:2 AS build, ENV DENO_DIR=/deno-dir, RUN deno cache main.ts, COPY --from=build /deno-dir /deno-dir, ENV NODE_ENV=production, USER deno, and ENTRYPOINT ["deno","run","-A","main.ts"].
  • denotask.Dockerfile contained ENTRYPOINT ["deno","task","start"].
  • denodirect.Dockerfile.dockerignore and denotask.Dockerfile.dockerignore were generated.

Scenario 6: Missing Deno task fails safely

Objective: Verify a misconfigured WithRunScript("missing") app fails with a clear Deno task error and a failed resource state.
Coverage Type: Unhappy path
Status: ✅ Passed

Steps:

  1. Created a fresh file-based C# app using the PR hive.
  2. Added a Deno app with deno.json containing only a start task.
  3. Configured Aspire with .WithRunScript("missing").
  4. Started the AppHost and waited for the resource to become healthy.

Evidence:

  • Log: session artifact: container-deno-negative.log
  • Scenario details: temporary container workspace/scenario-deno-missing-task/

Expected Unhappy-Path Outcome: Non-zero wait result, failed/stopped resource, and a clear Deno task error.

Observations:

  • aspire wait denomissing --status healthy exited non-zero.
  • Resource state was Finished with exitCode: 1.
  • Logs showed Args = ["task", "missing"] and Task not found: missing with available tasks listed.

Scenario 7: Least-privilege Deno permissions fail safely without env access

Objective: Verify opting out of the default -A permission grant causes Deno to fail explicitly when the app reads Aspire-injected PORT without --allow-env.
Coverage Type: Unhappy path
Status: ✅ Passed

Steps:

  1. Created a fresh file-based C# app using the PR hive.
  2. Added a Deno app that reads Deno.env.get("PORT").
  3. Configured Aspire with .WithDenoAllowAll(false) and no granular permission grants.
  4. Started the AppHost and waited for the resource to become healthy.

Evidence:

  • Log: session artifact: container-deno-negative.log
  • Scenario details: temporary container workspace/scenario-deno-permission-failure/

Expected Unhappy-Path Outcome: Non-zero wait result, failed/stopped resource, and Deno NotCapable permission error.

Observations:

  • aspire wait denorestricted --status healthy exited non-zero.
  • Resource state was Finished with exitCode: 1.
  • Logs showed Args = ["run", "main.ts"] and NotCapable: Requires env access to "PORT", run again with the --allow-env flag.

Scenario 8: Publish mismatch for WithRunScript task apps

Objective: Verify published Dockerfile entrypoints match run-mode behavior for Deno task apps configured with WithRunScript("start").
Coverage Type: Publish artifact validation / regression check
Status: ❌ Failed

Steps:

  1. Created a fresh file-based C# app using the PR hive.
  2. Added Aspire.Hosting.JavaScript, Aspire.Hosting.Docker, and AddDockerComposeEnvironment("compose").
  3. Added a Deno app with .WithRunScript("start").
  4. Ran aspire publish and inspected denotask.Dockerfile.

Evidence:

  • Log: session artifact: container-deno-publish.log
  • Generated artifacts: temporary container workspace/scenario-deno-publish/publish-output/

Expected Outcome: Because run mode described and logged the resource as deno task start, the published Dockerfile should either emit ENTRYPOINT ["deno","task","start"] or clearly require/document a different publish API for task entrypoints.

Actual Outcome: denotask.Dockerfile emitted ENTRYPOINT ["deno","run","-A","main.ts"], ignoring the WithRunScript("start") behavior used at run time.

Impact: A Deno app that relies on deno.json task behavior, task-specific arguments, permissions, or setup can work under aspire start/run but publish a container that starts a different command.


Scenario 9: Docker Compose deployment and runtime validation

Objective: Verify the generated Deno containers build, deploy with Docker Compose, stay running, and serve the expected HTTP responses.
Coverage Type: Deployment/runtime validation
Status: ⚠️ Partially passed

Steps:

  1. Copied the PR playground app into a temporary deployment workspace.
  2. Ran aspire restore --apphost apphost.mts.
  3. Ran aspire deploy --apphost apphost.mts --non-interactive --nologo for the unmodified playground.
  4. After the default deploy failed during image build, created a temporary validation variant that set .withDockerfileBaseImage({ buildImage: "denoland/deno:2.9.1", runtimeImage: "denoland/deno:2.9.1" }) on both Deno apps and used .withDenoTask("start") for the task app.
  5. Ran aspire deploy --apphost apphost.mts --non-interactive --nologo for the validation variant.
  6. Inspected the Docker Compose service status and curled both app endpoints.

Evidence:

  • Default deploy log: session artifact: compose-deploy-run.log
  • Successful override deploy log: session artifact: compose-deploy-valid-image-run.log
  • Runtime verification log: session artifact: compose-deploy-valid-image-compose-status.log
  • Endpoint verification log: session artifact: compose-deploy-valid-image-direct-verify.log

Expected Outcome: The PR playground's Docker Compose deployment builds both Deno images, starts the Compose services, and both Deno app endpoints return the expected responses.

Actual Outcome:

  • The unmodified playground deployment failed before startup because the generated Dockerfile used denoland/deno:2, and Docker Hub returned docker.io/denoland/deno:2: not found.
  • Docker Hub has exact-version tags such as denoland/deno:2.9.1, but not the major-only denoland/deno:2 tag used by the generated Dockerfile default.
  • With the exact image tag override and explicit .withDenoTask("start"), Docker Compose deployment succeeded: image build, local tagging, Compose up, and endpoint summaries all completed.
  • The running denoapp container used command deno run -A main.ts and returned Hello from deno!.
  • The running denoscript container used command deno task start and returned Hello from deno task!.

Impact: The generated default Deno Dockerfile image tag prevents real Docker Compose deployment from building/running unless users override the base image to a valid tag. Once a valid image tag is configured, the generated containers run correctly; the task container also requires the explicit Deno task API to avoid the WithRunScript publish mismatch noted above.

Summary

Scenario Status Notes
Targeted hosting source validation ✅ Passed 37 AddDenoApp tests passed
VS Code extension build/unit validation ✅ Passed Passed from short checkout: 1106 passing, 2 pending
Deno debugger E2E ✅ Passed 2 focused Extension Host tests passed
Dogfood Deno run/task apps ✅ Passed Direct and task apps healthy; curl responses matched
Dockerfile publish with WithDenoTask ✅ Passed Deno Dockerfile defaults and task entrypoint validated
Missing Deno task ✅ Passed Safe failed state with clear task error
Least-privilege permission failure ✅ Passed Safe failed state with Deno NotCapable error
Publish WithRunScript task app ❌ Failed Dockerfile entrypoint did not match run-mode deno task start
Docker Compose deployment/runtime ⚠️ Partial Default deploy failed on invalid denoland/deno:2; exact image override deployed and both Deno endpoints responded

Overall Result

❌ ISSUES FOUND

Most Deno hosting and VS Code debugger scenarios passed, including real dogfood CLI run/task behavior, generated Dockerfile behavior with WithDenoTask, extension unit tests, and a focused real Extension Host E2E. Docker Compose runtime validation also passed after overriding the Deno base image to an existing exact tag. The issues found are:

  1. A Deno app configured with WithRunScript("start") runs as deno task start in run mode but publishes a Dockerfile entrypoint of deno run -A main.ts.
  2. The generated default Deno Dockerfile uses denoland/deno:2, but that tag does not exist in Docker Hub, causing real Docker Compose deployment to fail during image build.

Recommendations

  • Update Deno Dockerfile entrypoint generation to honor JavaScriptRunScriptAnnotation / package-manager task behavior, or document and enforce that publish task entrypoints require WithDenoTask instead of WithRunScript.
  • Add a publish snapshot test covering AddDenoApp(...).WithRunScript("start") so run and publish command behavior cannot drift.
  • Change the default Deno Dockerfile base image to a valid published tag, or pin/resolve the default to an exact Deno version before publish/deploy.
  • Add a deployment/build validation test or sample coverage that exercises generated Deno Dockerfiles against Docker Compose, not only snapshot text.

Comment thread docs/deno-flag-surface.md Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread extension/src/debugger/languages/deno.ts Outdated
Comment thread extension/src/debugger/languages/deno.ts Outdated
Comment thread src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs Outdated
Comment thread src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs
Comment thread src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs Outdated
Comment thread src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 4, 2026 18:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 29/29 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread docs/deno-flag-surface.md Outdated
Comment thread tests/Aspire.Hosting.JavaScript.Tests/AddDenoAppTests.cs Outdated
Comment thread src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs Outdated
Avoid emitting task-invalid Deno resolution flags for task mode while preserving supported task flags and run/serve behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs
Adam Ratzman and others added 8 commits August 13, 2026 07:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555

@adamint Adam Ratzman (adamint) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I reviewed and tested the updated Deno hosting implementation. The outstanding runtime, OTLP publishing, Docker layering, and polyglot validation issues are addressed.

Adam Ratzman added 2 commits August 13, 2026 11:12
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555
Replace the blocked third-party setup action with a pinned, checksum-verified local installer so workflows can start under the repository Actions policy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (2)

src/Aspire.Hosting/OtlpConfigurationExtensions.cs:119

  • The optional behavior depends on registration order. If a resource already called WithOtlpExporter() and then calls this new optional overload, the earlier non-optional environment callback still reads the last (HTTP) annotation and calls ResolveOtlpEndpoint; with no HTTP endpoint it throws instead of skipping export. Associate each callback with the annotation it registered (and ignore it when that annotation is no longer effective), or carry optionality on the effective annotation, and add coverage for required-then-optional registration.
    src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs:560
  • This changes deployed Kubernetes workloads from always using the dashboard's gRPC collector to selecting the HTTP collector for HttpProtobuf/HttpJson, but coverage only verifies generated YAML. Add a deployment E2E case that starts the chart and confirms telemetry reaches the dashboard over the selected HTTP protocol; the repository already has deployed Helm coverage in tests/Aspire.Deployment.EndToEnd.Tests/KubernetesHelmChartDeploymentTests.cs.
  • Files reviewed: 86/87 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 86 out of 87 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs:560

  • This changes the deployed Kubernetes service/port selected for OTLP, but coverage stops at the rendered values.yaml. A chart can contain these strings and still fail to route telemetry after deployment. Please add a Kubernetes deployment E2E that deploys an HTTP/protobuf exporter (ideally the Deno resource) and verifies the dashboard receives telemetry through otlp-http.
    src/Aspire.Hosting.Docker/DockerComposeEnvironmentResource.cs:379
  • This changes deployed Compose connectivity from the gRPC collector to a protocol-selected endpoint, but the added tests only inspect generated YAML (and use a test-only container annotation). They would not catch a stack that starts successfully but fails to deliver Deno telemetry through the dashboard's HTTP OTLP service. Please add an automated deployment E2E that starts the generated Compose stack with an actual AddDenoApp resource and verifies telemetry reaches the dashboard over HTTP/protobuf.
            var (otlpEndpoint, protocol) = otlpExporter.RequiredProtocol switch
            {
                OtlpProtocol.HttpProtobuf => (dashboard.OtlpHttpEndpoint, "http/protobuf"),
                OtlpProtocol.HttpJson => (dashboard.OtlpHttpEndpoint, "http/json"),
                _ => (dashboard.OtlpGrpcEndpoint, "grpc"),

Normalize path separators and Windows casing before locating the Deno entrypoint so inspector-like script arguments are not mistaken for runtime flags.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (1)

src/Aspire.Hosting.JavaScript/DenoHostingExtensions.cs:1382

  • When deno.lock exists, a caller using WithDenoRuntimeArgs("--no-lock") reaches the cache allowlist and emits deno cache --no-lock --frozen ...; Deno rejects these mutually exclusive flags, so the generated image cannot build. Treat the raw --no-lock spelling like WithDenoNoLock() when deciding whether to add --frozen.
        if (deno.NoLock)
        {
            return false;
        }
  • Files reviewed: 86/87 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (5)

extension/src/debugger/languages/deno.ts:175

  • This cleanup is keyed only by runId, while AspireDebugSession calls cleanupRun(runId) whenever any child debug session in that run terminates. A sibling can therefore remove this reservation after allocation but before Deno binds, allowing a concurrent Deno launch to select the same port. The unit test exercises only this file's termination listener and misses the run-level cleanup path; scope cleanup to the resource/debug-session ID.
    registerRunCleanup(launchOptions.runId, disposeRelease);
    launchOptions.debugSession.registerResourceCleanup({
        dispose: disposeRelease
    });

src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs:882

  • The generated Deno publisher is covered only by Dockerfile text assertions and snapshots. None of the deployment E2E tests builds and starts the generated image, so regressions in image ownership, the copied DENO_DIR, shell availability, or the emitted entrypoint can pass CI despite producing an unusable deployment. Add an automated deployment test that publishes, builds, starts, and calls a generated Deno container.
            .PublishAsDockerFile(c =>

src/Aspire.Hosting/OtlpConfigurationExtensions.cs:200

  • The endpoint/protocol is selected from the last OTLP exporter annotation, but activation variables are collected from every annotation. If a Deno resource's HTTP/protobuf annotation is followed by WithOtlpExporter(OtlpProtocol.Grpc) or HttpJson, this still enables OTEL_DENO against a protocol Deno's native exporter does not support. Read activation variables only from the effective annotation.
    src/Aspire.Hosting.Docker/DockerComposeEnvironmentResource.cs:408
  • The effective endpoint/protocol comes from the last exporter annotation, but this activates every exporter annotation. A Deno annotation followed by a gRPC or HTTP/JSON exporter therefore still emits OTEL_DENO with an incompatible deployment endpoint. Apply activation variables only from the effective annotation.
        foreach (var annotation in resource.Annotations.OfType<OtlpExporterAnnotation>())
        {
            if (annotation is not IReadOnlyDictionary<string, string> activationEnvironmentVariables)

src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs:588

  • The effective endpoint/protocol comes from the last exporter annotation, but this activates every exporter annotation. A Deno annotation followed by a gRPC or HTTP/JSON exporter therefore still emits OTEL_DENO with an incompatible deployment endpoint. Apply activation variables only from the effective annotation.
  • Files reviewed: 87/88 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (1)

src/Aspire.Hosting.JavaScript/README.md:36

  • runScriptName is an npm script name, not an entrypoint path. Unless the omitted package.json defines a script literally named app.js, this minimal example fails at startup. Use a real script name such as dev in both samples, or use AddNodeApp/addNodeApp when app.js is the entrypoint.
  • Files reviewed: 87/88 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Adam Ratzman and others added 6 commits August 13, 2026 14:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4386e8b9-e6f7-4fa2-ab5c-1d243629a555
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 577e9ecd-68d0-4029-b837-7ce00ca1b007
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 577e9ecd-68d0-4029-b837-7ce00ca1b007
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 577e9ecd-68d0-4029-b837-7ce00ca1b007
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 577e9ecd-68d0-4029-b837-7ce00ca1b007

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (1)

src/Aspire.Hosting/OtlpConfigurationExtensions.cs:100

  • The callback now reads the last exporter annotation, but skipIfEndpointUnavailable still belongs to the callback that was originally registered. If a resource calls WithOtlpExporter() and then WithOtlpExporterIfEndpointAvailable(HttpProtobuf), the earlier mandatory callback sees the final optional HTTP annotation and calls the resolver without an endpoint, which throws instead of leaving telemetry disabled. Make optionality part of the effective annotation (or ensure only the effective registration's callback runs), and add this reverse-order regression case.
  • Files reviewed: 70/71 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-vscode-extension NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants