From 43d36dee7deccb69710f5540c812af2c7121a301 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:47:18 +0000 Subject: [PATCH 1/9] Initial plan From 75dfc4e70b5b08e7d314a325e1321b34abb3cf17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:54:53 +0000 Subject: [PATCH 2/9] Add polyglot TypeScript apphost files for .NET Aspire samples Create TypeScript equivalents of C# AppHost.cs files using the Aspire polyglot apphost SDK for 14 samples. Each file uses the createBuilder pattern with camelCase method names and includes POLYGLOT GAP comments for features not yet available in the TypeScript SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- samples/Metrics/MetricsApp.AppHost/apphost.ts | 33 +++++++++++++ .../aspire-shop/AspireShop.AppHost/apphost.ts | 49 +++++++++++++++++++ .../ImageGallery.AppHost/apphost.ts | 31 ++++++++++++ .../AspireJavaScript.AppHost/apphost.ts | 34 +++++++++++++ .../AspireWithNode.AppHost/apphost.ts | 21 ++++++++ samples/aspire-with-python/apphost.ts | 15 ++++++ .../ClientAppsIntegration.AppHost/apphost.ts | 19 +++++++ samples/container-build/apphost.ts | 33 +++++++++++++ .../CustomResources.AppHost/apphost.ts | 13 +++++ .../DatabaseContainers.AppHost/apphost.ts | 47 ++++++++++++++++++ .../DatabaseMigrations.AppHost/apphost.ts | 22 +++++++++ .../HealthChecksUI.AppHost/apphost.ts | 34 +++++++++++++ .../OrleansVoting.AppHost/apphost.ts | 23 +++++++++ .../VolumeMount.AppHost/apphost.ts | 22 +++++++++ 14 files changed, 396 insertions(+) create mode 100644 samples/Metrics/MetricsApp.AppHost/apphost.ts create mode 100644 samples/aspire-shop/AspireShop.AppHost/apphost.ts create mode 100644 samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts create mode 100644 samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts create mode 100644 samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts create mode 100644 samples/aspire-with-python/apphost.ts create mode 100644 samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts create mode 100644 samples/container-build/apphost.ts create mode 100644 samples/custom-resources/CustomResources.AppHost/apphost.ts create mode 100644 samples/database-containers/DatabaseContainers.AppHost/apphost.ts create mode 100644 samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts create mode 100644 samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts create mode 100644 samples/orleans-voting/OrleansVoting.AppHost/apphost.ts create mode 100644 samples/volume-mount/VolumeMount.AppHost/apphost.ts diff --git a/samples/Metrics/MetricsApp.AppHost/apphost.ts b/samples/Metrics/MetricsApp.AppHost/apphost.ts new file mode 100644 index 00000000..63e04d55 --- /dev/null +++ b/samples/Metrics/MetricsApp.AppHost/apphost.ts @@ -0,0 +1,33 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: AddOpenTelemetryCollector() is a custom AppHost extension method (from MetricsApp.AppHost.OpenTelemetryCollector) +// and is not available in the TypeScript polyglot SDK. + +const prometheus = await builder.addContainer("prometheus", "prom/prometheus", "v3.2.1") + .withBindMount("../prometheus", "/etc/prometheus", true) + .withArgs("--web.enable-otlp-receiver", "--config.file=/etc/prometheus/prometheus.yml") + .withHttpEndpoint({ targetPort: 9090 }); +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Prometheus Dashboard") — lambda URL customization is not available in the TypeScript SDK. + +const prometheusHttpEndpoint = prometheus.getEndpoint("http"); + +const grafana = await builder.addContainer("grafana", "grafana/grafana") + .withBindMount("../grafana/config", "/etc/grafana", true) + .withBindMount("../grafana/dashboards", "/var/lib/grafana/dashboards", true) + .withEnvironment("PROMETHEUS_ENDPOINT", prometheusHttpEndpoint) + .withHttpEndpoint({ targetPort: 3000 }); +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Grafana Dashboard") — lambda URL customization is not available in the TypeScript SDK. + +// POLYGLOT GAP: builder.AddOpenTelemetryCollector("otelcollector", "../otelcollector/config.yaml") +// .WithEnvironment("PROMETHEUS_ENDPOINT", `${prometheus.GetEndpoint("http")}/api/v1/otlp`) +// AddOpenTelemetryCollector is a custom extension method not available in the TypeScript SDK. + +const app = builder.addProject("app") + .withEnvironment("GRAFANA_URL", grafana.getEndpoint("http")); +// POLYGLOT GAP: AddProject("app") — generic type parameter for project reference is not available; use addProject("name") instead. +// POLYGLOT GAP: .WithUrlForEndpoint("https", u => u.DisplayText = "Instrumented App") — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly) — lambda URL customization is not available. + +await builder.build().run(); diff --git a/samples/aspire-shop/AspireShop.AppHost/apphost.ts b/samples/aspire-shop/AspireShop.AppHost/apphost.ts new file mode 100644 index 00000000..6900fb49 --- /dev/null +++ b/samples/aspire-shop/AspireShop.AppHost/apphost.ts @@ -0,0 +1,49 @@ +import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const postgres = await builder.addPostgres("postgres") + .withPgAdmin() + .withLifetime(ContainerLifetime.Persistent); + +const execCtx = await builder.executionContext.get(); +const isRunMode = await execCtx.isRunMode.get(); +if (isRunMode) { + await postgres.withDataVolume(); +} + +const catalogDb = postgres.addDatabase("catalogdb"); + +const basketCache = await builder.addRedis("basketcache") + .withDataVolume() + .withRedisCommander(); + +const catalogDbManager = builder.addProject("catalogdbmanager") + .withReference(catalogDb) + .waitFor(catalogDb) + .withHttpHealthCheck("/health"); +// POLYGLOT GAP: AddProject("catalogdbmanager") — generic type parameter for project reference is not available; use addProject("name") instead. +// POLYGLOT GAP: .WithHttpCommand("/reset-db", "Reset Database", commandOptions: new() { IconName = "DatabaseLightning" }) — custom HTTP commands are not available in the TypeScript SDK. + +const catalogService = builder.addProject("catalogservice") + .withReference(catalogDb) + .waitFor(catalogDbManager) + .withHttpHealthCheck("/health"); +// POLYGLOT GAP: AddProject("catalogservice") — generic type parameter for project reference is not available. + +const basketService = builder.addProject("basketservice") + .withReference(basketCache) + .waitFor(basketCache); +// POLYGLOT GAP: AddProject("basketservice") — generic type parameter for project reference is not available. + +const frontend = builder.addProject("frontend") + .withExternalHttpEndpoints() + .withHttpHealthCheck("/health") + .withReference(basketService) + .withReference(catalogService) + .waitFor(catalogService); +// POLYGLOT GAP: AddProject("frontend") — generic type parameter for project reference is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("https", url => url.DisplayText = "Online Store (HTTPS)") — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("http", url => url.DisplayText = "Online Store (HTTP)") — lambda URL customization is not available. + +await builder.build().run(); diff --git a/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts new file mode 100644 index 00000000..91bffc82 --- /dev/null +++ b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts @@ -0,0 +1,31 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: AddAzureContainerAppEnvironment("env") — Azure Container App environment is not available in the TypeScript polyglot SDK. +// builder.addAzureContainerAppEnvironment("env"); + +// POLYGLOT GAP: AddAzureStorage("storage").RunAsEmulator() — Azure Storage emulator integration is not available. +// POLYGLOT GAP: .ConfigureInfrastructure(infra => { ... }) — Bicep infrastructure configuration with +// Azure.Provisioning.Storage.StorageAccount and BlobService properties is not available. +// const storage = builder.addAzureStorage("storage").runAsEmulator(); + +// POLYGLOT GAP: storage.AddBlobs("blobs") — Azure Blob storage integration is not available. +// POLYGLOT GAP: storage.AddQueues("queues") — Azure Queue storage integration is not available. +// const blobs = storage.addBlobs("blobs"); +// const queues = storage.addQueues("queues"); + +// POLYGLOT GAP: AddAzureFunctionsProject("functions") — Azure Functions project integration is not available. +// POLYGLOT GAP: .WithRoleAssignments(storage, StorageBuiltInRole.StorageBlobDataContributor, ...) — Azure role assignments are not available. +// POLYGLOT GAP: .WithHostStorage(storage) — host storage configuration is not available. +// const functions = builder.addAzureFunctionsProject("functions") +// .withReference(queues).withReference(blobs).waitFor(storage) +// .withRoleAssignments(storage, ...).withHostStorage(storage); + +// POLYGLOT GAP: AddProject("frontend") — generic type parameter for project reference is not available. +// POLYGLOT GAP: Full functionality requires Azure Storage, Queues, Blobs, and Functions project references above. +const frontend = builder.addProject("frontend") + .withExternalHttpEndpoints(); +// POLYGLOT GAP: .withReference(queues).withReference(blobs).waitFor(functions) — cannot reference Azure resources (see gaps above). + +await builder.build().run(); diff --git a/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts b/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts new file mode 100644 index 00000000..1d90df8e --- /dev/null +++ b/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts @@ -0,0 +1,34 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: AddProject("weatherapi") — generic type parameter for project reference is not available. +const weatherApi = builder.addProject("weatherapi") + .withExternalHttpEndpoints(); + +// POLYGLOT GAP: AddJavaScriptApp() is not available in the TypeScript polyglot SDK. +// The following Angular, React, and Vue apps cannot be added directly. + +// POLYGLOT GAP: builder.AddJavaScriptApp("angular", "../AspireJavaScript.Angular", runScriptName: "start") +// .WithReference(weatherApi).WaitFor(weatherApi).WithHttpEndpoint(env: "PORT") +// .WithExternalHttpEndpoints().PublishAsDockerFile() +// AddJavaScriptApp, WithHttpEndpoint with env parameter, and PublishAsDockerFile are not available. + +// POLYGLOT GAP: builder.AddJavaScriptApp("react", "../AspireJavaScript.React", runScriptName: "start") +// .WithReference(weatherApi).WaitFor(weatherApi).WithEnvironment("BROWSER", "none") +// .WithHttpEndpoint(env: "PORT").WithExternalHttpEndpoints().PublishAsDockerFile() +// AddJavaScriptApp, WithHttpEndpoint with env parameter, and PublishAsDockerFile are not available. + +// POLYGLOT GAP: builder.AddJavaScriptApp("vue", "../AspireJavaScript.Vue").WithRunScript("start").WithNpm(installCommand: "ci") +// .WithReference(weatherApi).WaitFor(weatherApi).WithHttpEndpoint(env: "PORT") +// .WithExternalHttpEndpoints().PublishAsDockerFile() +// AddJavaScriptApp, WithRunScript, WithNpm, WithHttpEndpoint with env parameter, and PublishAsDockerFile are not available. + +// POLYGLOT GAP: builder.AddViteApp("reactvite", "../AspireJavaScript.Vite") +// .WithReference(weatherApi).WithEnvironment("BROWSER", "none") +// AddViteApp is not available in the TypeScript polyglot SDK. + +// POLYGLOT GAP: weatherApi.PublishWithContainerFiles(reactVite, "./wwwroot") +// PublishWithContainerFiles is not available in the TypeScript polyglot SDK. + +await builder.build().run(); diff --git a/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts b/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts new file mode 100644 index 00000000..213a1699 --- /dev/null +++ b/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts @@ -0,0 +1,21 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const cache = await builder.addRedis("cache") + .withRedisInsight(); + +// POLYGLOT GAP: AddProject("weatherapi") — generic type parameter for project reference is not available. +const weatherapi = builder.addProject("weatherapi") + .withHttpHealthCheck("/health"); + +// POLYGLOT GAP: AddNodeApp("frontend", "../NodeFrontend", "./app.js") is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: .WithNpm() — npm configuration is not available. +// POLYGLOT GAP: .WithRunScript("dev") — run script configuration is not available. +// POLYGLOT GAP: .WithHttpEndpoint(port: 5223, env: "PORT") — WithHttpEndpoint with env parameter is not available. +// The following Node.js app cannot be added directly: +// builder.AddNodeApp("frontend", "../NodeFrontend", "./app.js").WithNpm().WithRunScript("dev") +// .WithHttpEndpoint(port: 5223, env: "PORT").WithExternalHttpEndpoints().WithHttpHealthCheck("/health") +// .WithReference(weatherapi).WaitFor(weatherapi).WithReference(cache).WaitFor(cache) + +await builder.build().run(); diff --git a/samples/aspire-with-python/apphost.ts b/samples/aspire-with-python/apphost.ts new file mode 100644 index 00000000..86a2de32 --- /dev/null +++ b/samples/aspire-with-python/apphost.ts @@ -0,0 +1,15 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const cache = builder.addRedis("cache"); + +// POLYGLOT GAP: AddUvicornApp("app", "./app", "main:app") is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: .WithUv() — UV package manager configuration is not available. +// POLYGLOT GAP: .WithExternalHttpEndpoints().WithReference(cache).WaitFor(cache).WithHttpHealthCheck("/health") +// The Python/Uvicorn app cannot be added directly. + +// POLYGLOT GAP: AddViteApp("frontend", "./frontend") is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: app.PublishWithContainerFiles(frontend, "./static") — PublishWithContainerFiles is not available. + +await builder.build().run(); diff --git a/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts b/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts new file mode 100644 index 00000000..4566a6e0 --- /dev/null +++ b/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts @@ -0,0 +1,19 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: AddProject("apiservice") — generic type parameter for project reference is not available. +const apiService = builder.addProject("apiservice"); + +// POLYGLOT GAP: OperatingSystem.IsWindows() — platform check is not available in the TypeScript apphost context. +// The following Windows-only projects cannot be conditionally added: +// if (OperatingSystem.IsWindows()) { +// builder.AddProject("winformsclient") +// .WithReference(apiService).WaitFor(apiService).WithExplicitStart().ExcludeFromManifest(); +// builder.AddProject("wpfclient") +// .WithReference(apiService).WaitFor(apiService).WithExplicitStart().ExcludeFromManifest(); +// } +// POLYGLOT GAP: .WithExplicitStart() — explicit start configuration is not available. +// POLYGLOT GAP: .ExcludeFromManifest() — manifest exclusion is not available. + +await builder.build().run(); diff --git a/samples/container-build/apphost.ts b/samples/container-build/apphost.ts new file mode 100644 index 00000000..ad913160 --- /dev/null +++ b/samples/container-build/apphost.ts @@ -0,0 +1,33 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: builder.AddParameter("goversion", "1.25.4", publishValueAsDefault: true) — AddParameter with publishValueAsDefault is not available in the TypeScript SDK. +// Using a plain string as a workaround for the parameter value. +const goVersion = "1.25.4"; + +const execCtx = await builder.executionContext.get(); +const isRunMode = await execCtx.isRunMode.get(); +const isPublishMode = !isRunMode; + +// POLYGLOT GAP: AddDockerfile("ginapp", "./ginapp") and AddDockerfile("ginapp", "./ginapp", "Dockerfile.dev") — AddDockerfile is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: .WithBuildArg("GO_VERSION", goVersion) — WithBuildArg is not available. +// POLYGLOT GAP: .WithOtlpExporter() — OTLP exporter configuration is not available. +// POLYGLOT GAP: .WithDeveloperCertificateTrust(trust: true) — developer certificate trust is not available. +// +// The following Dockerfile-based container cannot be added directly: +// let ginapp; +// if (isPublishMode) { +// ginapp = builder.addDockerfile("ginapp", "./ginapp").withBuildArg("GO_VERSION", goVersion); +// } else { +// ginapp = builder.addDockerfile("ginapp", "./ginapp", "Dockerfile.dev") +// .withBuildArg("GO_VERSION", goVersion).withBindMount("./ginapp", "/app"); +// } +// ginapp.withHttpEndpoint({ targetPort: 5555, env: "PORT" }) +// .withHttpHealthCheck("/").withExternalHttpEndpoints() +// .withOtlpExporter().withDeveloperCertificateTrust(true); +// if (isPublishMode) { +// ginapp.withEnvironment("GIN_MODE", "release").withEnvironment("TRUSTED_PROXIES", "all"); +// } + +await builder.build().run(); diff --git a/samples/custom-resources/CustomResources.AppHost/apphost.ts b/samples/custom-resources/CustomResources.AppHost/apphost.ts new file mode 100644 index 00000000..9f4df246 --- /dev/null +++ b/samples/custom-resources/CustomResources.AppHost/apphost.ts @@ -0,0 +1,13 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: AddTalkingClock("talking-clock") is a custom resource extension (from CustomResources.AppHost) +// and is not available in the TypeScript polyglot SDK. +// builder.AddTalkingClock("talking-clock"); + +// POLYGLOT GAP: AddTestResource("test") is a custom resource extension (from CustomResources.AppHost) +// and is not available in the TypeScript polyglot SDK. +// builder.AddTestResource("test"); + +await builder.build().run(); diff --git a/samples/database-containers/DatabaseContainers.AppHost/apphost.ts b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts new file mode 100644 index 00000000..112dc9c0 --- /dev/null +++ b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts @@ -0,0 +1,47 @@ +import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const todosDbName = "Todos"; + +const postgres = await builder.addPostgres("postgres") + .withEnvironment("POSTGRES_DB", todosDbName) + .withBindMount("../DatabaseContainers.ApiService/data/postgres", "/docker-entrypoint-initdb.d") + .withDataVolume() + .withLifetime(ContainerLifetime.Persistent); +// POLYGLOT GAP: .WithPgWeb() — PgWeb integration is not available in the TypeScript polyglot SDK. + +const todosDb = postgres.addDatabase(todosDbName); + +const catalogDbName = "catalog"; + +const mysql = await builder.addMySql("mysql") + .withEnvironment("MYSQL_DATABASE", catalogDbName) + .withBindMount("../DatabaseContainers.ApiService/data/mysql", "/docker-entrypoint-initdb.d") + .withDataVolume() + .withLifetime(ContainerLifetime.Persistent); + +const catalogDb = mysql.addDatabase(catalogDbName); + +const sqlserver = await builder.addSqlServer("sqlserver") + .withDataVolume() + .withLifetime(ContainerLifetime.Persistent); + +// POLYGLOT GAP: WithCreationScript(File.ReadAllText(initScriptPath)) — reading a file and passing its content +// via WithCreationScript is not available in the TypeScript polyglot SDK. +// In C#: var initScriptPath = Path.Join(Path.GetDirectoryName(typeof(Program).Assembly.Location), "init.sql"); +// var addressBookDb = sqlserver.AddDatabase("AddressBook").WithCreationScript(File.ReadAllText(initScriptPath)); +const addressBookDb = sqlserver.addDatabase("AddressBook"); + +// POLYGLOT GAP: AddProject("apiservice") — generic type parameter for project reference is not available. +const apiservice = builder.addProject("apiservice") + .withReference(todosDb) + .waitFor(todosDb) + .withReference(catalogDb) + .waitFor(catalogDb) + .withReference(addressBookDb) + .waitFor(addressBookDb) + .withHttpHealthCheck("/alive") + .withHttpHealthCheck("/health"); + +await builder.build().run(); diff --git a/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts b/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts new file mode 100644 index 00000000..469d5650 --- /dev/null +++ b/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts @@ -0,0 +1,22 @@ +import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const sqlserver = await builder.addSqlServer("sqlserver") + .withDataVolume() + .withLifetime(ContainerLifetime.Persistent); + +const db1 = sqlserver.addDatabase("db1"); + +// POLYGLOT GAP: AddProject("migration") — generic type parameter for project reference is not available. +const migrationService = builder.addProject("migration") + .withReference(db1) + .waitFor(db1); + +// POLYGLOT GAP: AddProject("api") — generic type parameter for project reference is not available. +// POLYGLOT GAP: .WaitForCompletion(migrationService) — WaitForCompletion is not available in the TypeScript polyglot SDK; only WaitFor is available. +const api = builder.addProject("api") + .withReference(db1) + .waitFor(migrationService); + +await builder.build().run(); diff --git a/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts new file mode 100644 index 00000000..0588914d --- /dev/null +++ b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts @@ -0,0 +1,34 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +// POLYGLOT GAP: AddDockerComposeEnvironment("compose") — Docker Compose integration is not available in the TypeScript polyglot SDK. +// builder.AddDockerComposeEnvironment("compose"); + +const cache = builder.addRedis("cache"); + +// POLYGLOT GAP: AddProject("apiservice") — generic type parameter for project reference is not available. +// POLYGLOT GAP: .WithHttpProbe(ProbeType.Liveness, "/alive") — probe configuration is not available. +// POLYGLOT GAP: .WithFriendlyUrls(displayText: "API") — WithFriendlyUrls is a custom extension method not available in the TypeScript SDK. +const apiService = builder.addProject("apiservice") + .withHttpHealthCheck("/health"); + +// POLYGLOT GAP: AddProject("webfrontend") — generic type parameter for project reference is not available. +// POLYGLOT GAP: .WithHttpProbe(ProbeType.Liveness, "/alive") — probe configuration is not available. +// POLYGLOT GAP: .WithFriendlyUrls("Web Frontend") — WithFriendlyUrls is a custom extension method not available. +const webFrontend = builder.addProject("webfrontend") + .withReference(cache) + .waitFor(cache) + .withReference(apiService) + .waitFor(apiService) + .withHttpHealthCheck("/health") + .withExternalHttpEndpoints(); + +// POLYGLOT GAP: AddHealthChecksUI("healthchecksui") — HealthChecks UI integration is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: .WithReference(apiService).WithReference(webFrontend) — references for health checks UI. +// POLYGLOT GAP: .WithFriendlyUrls("HealthChecksUI Dashboard", "http") — custom extension method not available. +// POLYGLOT GAP: .WithHttpProbe(ProbeType.Liveness, "/") — probe configuration is not available. +// POLYGLOT GAP: .WithExternalHttpEndpoints() — for the health checks UI resource. +// POLYGLOT GAP: .WithHostPort(7230) — conditional host port in run mode for health checks UI. + +await builder.build().run(); diff --git a/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts b/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts new file mode 100644 index 00000000..554e555e --- /dev/null +++ b/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts @@ -0,0 +1,23 @@ +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const redis = builder.addRedis("voting-redis"); + +// POLYGLOT GAP: AddOrleans("voting-cluster") — Orleans integration is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: .WithClustering(redis) — Orleans clustering configuration is not available. +// POLYGLOT GAP: .WithGrainStorage("votes", redis) — Orleans grain storage configuration is not available. +// const orleans = builder.addOrleans("voting-cluster").withClustering(redis).withGrainStorage("votes", redis); + +// POLYGLOT GAP: AddProject("voting-fe") — generic type parameter for project reference is not available. +// POLYGLOT GAP: .WithReference(orleans) — Orleans resource reference is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("https", u => u.DisplayText = "Voting App") — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly) — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("orleans-gateway", ...) — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint("orleans-silo", ...) — lambda URL customization is not available. +const votingFe = builder.addProject("voting-fe") + .waitFor(redis) + .withReplicas(3) + .withExternalHttpEndpoints(); + +await builder.build().run(); diff --git a/samples/volume-mount/VolumeMount.AppHost/apphost.ts b/samples/volume-mount/VolumeMount.AppHost/apphost.ts new file mode 100644 index 00000000..19cb1fe2 --- /dev/null +++ b/samples/volume-mount/VolumeMount.AppHost/apphost.ts @@ -0,0 +1,22 @@ +import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const sqlserver = await builder.addSqlServer("sqlserver") + .withDataVolume() + .withLifetime(ContainerLifetime.Persistent); + +const sqlDatabase = sqlserver.addDatabase("sqldb"); + +// POLYGLOT GAP: AddAzureStorage("Storage").RunAsEmulator(emulator => emulator.WithDataVolume()) — Azure Storage emulator +// with callback configuration is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: .AddBlobs("BlobConnection") — Azure Blob storage integration is not available. +// const blobs = builder.addAzureStorage("Storage").runAsEmulator(...).addBlobs("BlobConnection"); + +// POLYGLOT GAP: AddProject("blazorweb") — generic type parameter for project reference is not available. +const blazorweb = builder.addProject("blazorweb") + .withReference(sqlDatabase) + .waitFor(sqlDatabase); +// POLYGLOT GAP: .withReference(blobs).waitFor(blobs) — Azure Blob resource reference cannot be added (see Azure Storage gap above). + +await builder.build().run(); From 47fe94b629f8e47f80056f6ea152f99d14d83cc2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:59:06 +0000 Subject: [PATCH 3/9] Add polyglot apphost.ts TypeScript conversions for all samples with gap analysis Create apphost.ts files for 14 samples, converting C# AppHost.cs to TypeScript polyglot apphost SDK equivalents. Each file uses the Aspire polyglot SDK pattern (createBuilder/build/run) and documents all POLYGLOT GAP items where C# features are not available in the TypeScript SDK. Add comprehensive POLYGLOT_NOTES.md documenting per-sample gap analysis, cross-cutting issues, feature availability matrix, and recommendations. Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- samples/POLYGLOT_NOTES.md | 442 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 samples/POLYGLOT_NOTES.md diff --git a/samples/POLYGLOT_NOTES.md b/samples/POLYGLOT_NOTES.md new file mode 100644 index 00000000..9482a2cb --- /dev/null +++ b/samples/POLYGLOT_NOTES.md @@ -0,0 +1,442 @@ +# Polyglot AppHost TypeScript Conversion Notes + +This document logs all issues, gaps, limitations, and errors discovered while attempting to rewrite +each sample's `AppHost.cs` as a polyglot `apphost.ts` using the Aspire TypeScript SDK. + +## Overview + +Each sample directory now contains an `apphost.ts` file alongside the existing `AppHost.cs`. The +TypeScript versions use the Aspire polyglot apphost SDK (`createBuilder()` from `.modules/aspire.js`) +which communicates with a .NET AppHost Server via JSON-RPC. + +### Prerequisites + +To run the polyglot TypeScript apphosts: + +1. Install the staging Aspire CLI: + ```powershell + iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" + ``` +2. Node.js (v18+) or Bun must be installed +3. Run `aspire run` from the sample directory containing `apphost.ts` + +The CLI will: +- Scaffold an AppHost server project +- Generate the TypeScript SDK in `.modules/` +- Start the .NET AppHost server + Node.js/Bun guest runtime +- The generated SDK provides typed builder classes for all available capabilities + +### Skipped Sample + +- **standalone-dashboard**: This is a standalone console application, not an Aspire AppHost sample. It + configures OpenTelemetry directly and does not use `DistributedApplication.CreateBuilder()`. + +--- + +## Per-Sample Gap Analysis + +### 1. Metrics (`samples/Metrics/MetricsApp.AppHost/apphost.ts`) + +**Convertible:** Partially +**Status:** Container resources work, but custom extension and URL customization do not. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Add container with image/tag | `AddContainer("prometheus", "prom/prometheus", "v3.2.1")` | ✅ Available | +| Bind mount (read-only) | `.WithBindMount("../prometheus", "/etc/prometheus", isReadOnly: true)` | ✅ Available | +| Container args | `.WithArgs("--web.enable-otlp-receiver", ...)` | ✅ Available | +| HTTP endpoint | `.WithHttpEndpoint(targetPort: 9090)` | ✅ Available | +| URL display customization | `.WithUrlForEndpoint("http", u => u.DisplayText = "...")` | ❌ Lambda callback not available | +| Custom extension method | `AddOpenTelemetryCollector(...)` | ❌ Custom C# extension — not exported to ATS | +| Project reference (generic) | `AddProject("app")` | ⚠️ `addProject("app")` (no type-safe project binding) | +| Environment from endpoint | `.WithEnvironment("GRAFANA_URL", grafana.GetEndpoint("http"))` | ✅ Available | + +**Key Limitation:** The `AddOpenTelemetryCollector` is a custom extension method defined in the sample's +AppHost project. Custom C# extensions require `[AspireExport]` attributes to be available in the +polyglot SDK, which this sample does not have. + +--- + +### 2. aspire-shop (`samples/aspire-shop/AspireShop.AppHost/apphost.ts`) + +**Convertible:** Mostly +**Status:** Core resource orchestration works. HTTP commands and URL customization are gaps. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Postgres + PgAdmin | `AddPostgres("postgres").WithPgAdmin()` | ✅ Available | +| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | +| Conditional data volume | `if (IsRunMode) postgres.WithDataVolume()` | ✅ Available (via `executionContext`) | +| Add database | `postgres.AddDatabase("catalogdb")` | ✅ Available | +| Redis + Commander | `AddRedis("basketcache").WithDataVolume().WithRedisCommander()` | ✅ Available | +| Project references | `AddProject("name")` | ⚠️ `addProject("name")` — no generic type | +| HTTP health check | `.WithHttpHealthCheck("/health")` | ✅ Available | +| HTTP command | `.WithHttpCommand("/reset-db", "Reset Database", ...)` | ❌ Not available | +| URL display customization | `.WithUrlForEndpoint("https", url => url.DisplayText = "...")` | ❌ Lambda callback not available | +| External HTTP endpoints | `.WithExternalHttpEndpoints()` | ✅ Available | +| Resource references | `.WithReference(resource).WaitFor(resource)` | ✅ Available | + +**Key Limitation:** `WithHttpCommand` is an advanced feature for adding custom dashboard commands. +This is not exposed through ATS capabilities. + +--- + +### 3. aspire-with-javascript (`samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts`) + +**Convertible:** Minimally +**Status:** Almost entirely blocked — JavaScript/Node.js hosting APIs are not available. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Project reference | `AddProject("weatherapi")` | ⚠️ `addProject("weatherapi")` | +| JavaScript app | `AddJavaScriptApp("angular", "../path", runScriptName: "start")` | ❌ Not available | +| Vite app | `AddViteApp("reactvite", "../path")` | ❌ Not available | +| Run script | `.WithRunScript("start")` | ❌ Not available | +| npm configuration | `.WithNpm(installCommand: "ci")` | ❌ Not available | +| Publish as Dockerfile | `.PublishAsDockerFile()` | ❌ Not available | +| Publish container files | `weatherApi.PublishWithContainerFiles(reactVite, "./wwwroot")` | ❌ Not available | +| HTTP endpoint with env | `.WithHttpEndpoint(env: "PORT")` | ❌ env parameter not available | + +**Key Limitation:** The entire `Aspire.Hosting.JavaScript` package (AddJavaScriptApp, AddViteApp, +WithNpm, WithRunScript, PublishAsDockerFile) is not available in the polyglot SDK. This sample +is fundamentally about JavaScript hosting, making it almost entirely unconvertible. + +--- + +### 4. aspire-with-node (`samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts`) + +**Convertible:** Partially +**Status:** Redis and project resources work, but Node.js app hosting is not available. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Redis + Insight | `AddRedis("cache").WithRedisInsight()` | ✅ Available | +| Project reference | `AddProject("weatherapi")` | ⚠️ `addProject("weatherapi")` | +| Node.js app | `AddNodeApp("frontend", "../NodeFrontend", "./app.js")` | ❌ Not available | +| npm config | `.WithNpm()` | ❌ Not available | +| Run script | `.WithRunScript("dev")` | ❌ Not available | +| HTTP endpoint with env | `.WithHttpEndpoint(port: 5223, env: "PORT")` | ❌ env parameter not available | + +**Key Limitation:** `AddNodeApp` from `Aspire.Hosting.NodeJs` is not available. The Node.js +frontend cannot be orchestrated. + +--- + +### 5. aspire-with-python (`samples/aspire-with-python/apphost.ts`) + +**Convertible:** Minimally +**Status:** Almost entirely blocked — Python and Vite hosting APIs are not available. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Redis | `AddRedis("cache")` | ✅ Available | +| Uvicorn app | `AddUvicornApp("app", "./app", "main:app")` | ❌ Not available | +| UV package manager | `.WithUv()` | ❌ Not available | +| Vite app | `AddViteApp("frontend", "./frontend")` | ❌ Not available | +| Publish container files | `app.PublishWithContainerFiles(frontend, "./static")` | ❌ Not available | + +**Key Limitation:** Both `Aspire.Hosting.Python` (AddUvicornApp, WithUv) and +`Aspire.Hosting.JavaScript` (AddViteApp) packages are not available in the polyglot SDK. + +--- + +### 6. client-apps-integration (`samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts`) + +**Convertible:** Partially +**Status:** API service project works, but Windows platform checks and desktop app features don't. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Project reference | `AddProject("apiservice")` | ⚠️ `addProject("apiservice")` | +| Platform check | `OperatingSystem.IsWindows()` | ❌ Not available in TS context | +| Explicit start | `.WithExplicitStart()` | ❌ Not available | +| Exclude from manifest | `.ExcludeFromManifest()` | ❌ Not available | + +**Key Limitation:** `OperatingSystem.IsWindows()` has no equivalent in the TypeScript SDK. +Desktop apps (WinForms, WPF) are Windows-only and their conditional inclusion cannot be expressed. + +--- + +### 7. container-build (`samples/container-build/apphost.ts`) + +**Convertible:** Minimally +**Status:** Almost entirely blocked — Dockerfile build APIs are not available. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Parameter with default | `AddParameter("goversion", "1.25.4", publishValueAsDefault: true)` | ❌ Not available | +| Dockerfile build | `AddDockerfile("ginapp", "./ginapp")` | ❌ Not available | +| Build arg | `.WithBuildArg("GO_VERSION", goVersion)` | ❌ Not available | +| OTLP exporter | `.WithOtlpExporter()` | ❌ Not available | +| Certificate trust | `.WithDeveloperCertificateTrust(trust: true)` | ❌ Not available | +| Execution context | `builder.ExecutionContext.IsPublishMode` | ✅ Available (via `executionContext`) | + +**Key Limitation:** `AddDockerfile` and `WithBuildArg` from `Aspire.Hosting` are not available. +This sample is fundamentally about building containers from Dockerfiles. + +--- + +### 8. custom-resources (`samples/custom-resources/CustomResources.AppHost/apphost.ts`) + +**Convertible:** Not convertible +**Status:** Entirely blocked — custom resource types require C# implementation. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Custom resource | `AddTalkingClock("talking-clock")` | ❌ Custom C# extension | +| Custom resource | `AddTestResource("test")` | ❌ Custom C# extension | + +**Key Limitation:** This sample demonstrates creating custom resource types in C#. These are +implemented as C# classes and extension methods. The polyglot SDK can only access capabilities +from NuGet packages with `[AspireExport]` attributes — custom project-level extensions are not +available. + +--- + +### 9. database-containers (`samples/database-containers/DatabaseContainers.AppHost/apphost.ts`) + +**Convertible:** Mostly +**Status:** Core database hosting works with minor gaps. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Postgres | `AddPostgres("postgres")` | ✅ Available | +| MySQL | `AddMySql("mysql")` | ✅ Available | +| SQL Server | `AddSqlServer("sqlserver")` | ✅ Available | +| Environment variable | `.WithEnvironment("POSTGRES_DB", todosDbName)` | ✅ Available | +| Bind mount | `.WithBindMount(source, target)` | ✅ Available | +| Data volume | `.WithDataVolume()` | ✅ Available | +| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | +| Add database | `.AddDatabase("name")` | ✅ Available | +| PgWeb | `.WithPgWeb()` | ❌ Not available | +| Creation script | `.WithCreationScript(File.ReadAllText(path))` | ❌ Not available (file I/O + API) | +| HTTP health check | `.WithHttpHealthCheck("/health")` | ✅ Available | + +**Key Limitation:** `WithCreationScript` requires reading a SQL file from disk and passing its +content. The polyglot SDK doesn't have a `File.ReadAllText` equivalent, and `WithCreationScript` +itself may not be exported. `WithPgWeb` is also not available. + +--- + +### 10. database-migrations (`samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts`) + +**Convertible:** Mostly +**Status:** Works with minor API gap for WaitForCompletion. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| SQL Server + volume | `AddSqlServer("sqlserver").WithDataVolume()` | ✅ Available | +| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | +| Add database | `.AddDatabase("db1")` | ✅ Available | +| Project references | `AddProject("name")` | ⚠️ `addProject("name")` | +| Wait for completion | `.WaitForCompletion(migrationService)` | ❌ Only `waitFor` available | +| Resource references | `.WithReference(db1)` | ✅ Available | + +**Key Limitation:** `WaitForCompletion` (wait for a service to finish, not just start) is not +available. This is semantically different from `WaitFor` — it waits for the migration service to +complete its work before starting the API. + +--- + +### 11. health-checks-ui (`samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts`) + +**Convertible:** Partially +**Status:** Core project orchestration works, but specialized features don't. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Docker Compose | `AddDockerComposeEnvironment("compose")` | ❌ Not available | +| Redis | `AddRedis("cache")` | ✅ Available | +| Project references | `AddProject("name")` | ⚠️ `addProject("name")` | +| Health checks UI | `AddHealthChecksUI("healthchecksui")` | ❌ Not available | +| HTTP probe | `.WithHttpProbe(ProbeType.Liveness, "/alive")` | ❌ Not available | +| Custom URL helper | `.WithFriendlyUrls(...)` | ❌ Custom extension method | +| Host port | `.WithHostPort(7230)` | ❌ Not available | +| Execution context | `builder.ExecutionContext.IsRunMode` | ✅ Available | + +**Key Limitation:** `AddDockerComposeEnvironment`, `AddHealthChecksUI`, and `WithHttpProbe` are +specialized integrations not available in the polyglot SDK. The `WithFriendlyUrls` is a custom +extension method defined in the same AppHost.cs file. + +--- + +### 12. orleans-voting (`samples/orleans-voting/OrleansVoting.AppHost/apphost.ts`) + +**Convertible:** Minimally +**Status:** Redis works, but Orleans integration is entirely unavailable. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Redis | `AddRedis("voting-redis")` | ✅ Available | +| Orleans cluster | `AddOrleans("voting-cluster")` | ❌ Not available | +| Orleans clustering | `.WithClustering(redis)` | ❌ Not available | +| Orleans grain storage | `.WithGrainStorage("votes", redis)` | ❌ Not available | +| Project with replicas | `.WithReplicas(3)` | ✅ Available | +| Orleans reference | `.WithReference(orleans)` | ❌ Orleans resource not available | +| URL customization | `.WithUrlForEndpoint(...)` | ❌ Lambda callback not available | + +**Key Limitation:** The entire `Aspire.Hosting.Orleans` package (AddOrleans, WithClustering, +WithGrainStorage) is not available in the polyglot SDK. + +--- + +### 13. volume-mount (`samples/volume-mount/VolumeMount.AppHost/apphost.ts`) + +**Convertible:** Partially +**Status:** SQL Server works, but Azure Storage emulator does not. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| SQL Server + volume | `AddSqlServer("sqlserver").WithDataVolume()` | ✅ Available | +| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | +| Add database | `.AddDatabase("sqldb")` | ✅ Available | +| Azure Storage emulator | `AddAzureStorage("Storage").RunAsEmulator(...)` | ❌ Not available | +| Emulator callback | `.RunAsEmulator(emulator => emulator.WithDataVolume())` | ❌ Lambda not available | +| Azure Blobs | `.AddBlobs("BlobConnection")` | ❌ Not available | +| Project reference | `AddProject("blazorweb")` | ⚠️ `addProject("blazorweb")` | + +**Key Limitation:** `AddAzureStorage` and `RunAsEmulator` with callback configuration are not +available. Azure storage integration is a major gap. + +--- + +### 14. aspire-with-azure-functions (`samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts`) + +**Convertible:** Minimally +**Status:** Almost entirely blocked — Azure-specific APIs are not available. + +| Feature | C# API | TypeScript Status | +|---------|--------|-------------------| +| Azure Container App Env | `AddAzureContainerAppEnvironment("env")` | ❌ Not available | +| Azure Storage | `AddAzureStorage("storage").RunAsEmulator()` | ❌ Not available | +| Configure infrastructure | `.ConfigureInfrastructure(...)` | ❌ Not available | +| URL display customization | `.WithUrls(...)` | ❌ Lambda callback not available | +| Azure Blobs | `storage.AddBlobs("blobs")` | ❌ Not available | +| Azure Queues | `storage.AddQueues("queues")` | ❌ Not available | +| Azure Functions project | `AddAzureFunctionsProject("name")` | ❌ Not available | +| Role assignments | `.WithRoleAssignments(storage, ...)` | ❌ Not available | +| Host storage | `.WithHostStorage(storage)` | ❌ Not available | +| Project reference | `AddProject("frontend")` | ⚠️ `addProject("frontend")` | + +**Key Limitation:** This sample is entirely Azure-focused. None of the Azure-specific hosting +packages (Azure Storage, Functions, Container Apps) are available in the polyglot SDK. + +--- + +## Cross-Cutting Issues Summary + +### Universally Available Features ✅ +These features work across all samples in the TypeScript polyglot SDK: +- `createBuilder()` — Create the distributed application builder +- `addRedis("name")` — Add Redis container resource +- `addPostgres("name")` — Add PostgreSQL container resource +- `addMySql("name")` — Add MySQL container resource +- `addSqlServer("name")` — Add SQL Server container resource +- `addContainer("name", "image", "tag")` — Add generic container resource +- `.addDatabase("name")` — Add database to a database server +- `.withDataVolume()` — Add data volume to container +- `.withLifetime(ContainerLifetime.Persistent)` — Set container lifetime +- `.withBindMount(source, target, isReadOnly)` — Add bind mount +- `.withEnvironment("key", "value")` — Set environment variable +- `.withArgs(...)` — Set container arguments +- `.withHttpEndpoint({ targetPort: N })` — Add HTTP endpoint +- `.withHttpHealthCheck("/path")` — Add HTTP health check +- `.withExternalHttpEndpoints()` — Expose endpoints externally +- `.withReference(resource)` — Add resource reference +- `.waitFor(resource)` — Wait for resource to be ready +- `.withReplicas(N)` — Set replica count +- `.withRedisCommander()` / `.withRedisInsight()` — Redis admin tools +- `.withPgAdmin()` — PostgreSQL admin tool +- `getEndpoint("name")` — Get endpoint reference +- `builder.executionContext` — Access execution context (run vs publish mode) +- `builder.build().run()` — Build and run the application + +### Universally Unavailable Features ❌ +These features are NOT available in any sample: +1. **`AddProject("name")` generic type parameter** — The TypeScript SDK uses `addProject("name")` without type-safe project binding. This means the AppHost server needs another mechanism to discover and bind .NET project references. +2. **`WithUrlForEndpoint` with lambda callback** — URL display customization (display text, display location) requires callbacks that aren't available in the polyglot SDK. +3. **Custom C# extension methods** — Any extension method defined in the sample's AppHost project (e.g., `AddOpenTelemetryCollector`, `AddTalkingClock`, `WithFriendlyUrls`) requires `[AspireExport]` annotation and NuGet packaging to be available. + +### Major Feature Category Gaps ❌ +1. **JavaScript/Node.js hosting** (`Aspire.Hosting.JavaScript`): `AddJavaScriptApp`, `AddViteApp`, `AddNodeApp`, `WithNpm`, `WithRunScript`, `PublishAsDockerFile` +2. **Python hosting** (`Aspire.Hosting.Python`): `AddUvicornApp`, `WithUv` +3. **Azure integrations** (`Aspire.Hosting.Azure.*`): `AddAzureStorage`, `AddAzureFunctionsProject`, `AddAzureContainerAppEnvironment`, `RunAsEmulator`, `ConfigureInfrastructure`, `WithRoleAssignments` +4. **Orleans** (`Aspire.Hosting.Orleans`): `AddOrleans`, `WithClustering`, `WithGrainStorage` +5. **Docker Compose** (`Aspire.Hosting.Docker`): `AddDockerComposeEnvironment` +6. **Dockerfile builds**: `AddDockerfile`, `WithBuildArg` +7. **HealthChecks UI**: `AddHealthChecksUI`, `WithHttpProbe` +8. **Parameters**: `AddParameter` with `publishValueAsDefault` +9. **Completion waiting**: `WaitForCompletion` (different from `WaitFor`) +10. **Dashboard features**: `WithHttpCommand`, `WithHostPort`, `WithExplicitStart`, `ExcludeFromManifest` + +### Sample Conversion Feasibility Matrix + +| Sample | Feasibility | Notes | +|--------|-------------|-------| +| Metrics | ⚠️ Partial | Container resources work; custom OTel collector extension missing | +| aspire-shop | ✅ Mostly | Core orchestration works; HTTP commands, URL customization missing | +| aspire-with-javascript | ❌ Minimal | JS/Vite hosting entirely unavailable | +| aspire-with-node | ⚠️ Partial | Redis works; Node.js app hosting unavailable | +| aspire-with-python | ❌ Minimal | Python/Vite hosting entirely unavailable | +| client-apps-integration | ⚠️ Partial | API project works; platform checks, desktop apps unavailable | +| container-build | ❌ Minimal | Dockerfile builds entirely unavailable | +| custom-resources | ❌ None | Custom resources require C# implementation | +| database-containers | ✅ Mostly | Core DB hosting works; PgWeb, creation scripts missing | +| database-migrations | ✅ Mostly | Works except WaitForCompletion | +| health-checks-ui | ⚠️ Partial | Core projects work; Compose, HealthChecks UI, probes missing | +| orleans-voting | ❌ Minimal | Redis works; Orleans entirely unavailable | +| volume-mount | ⚠️ Partial | SQL Server works; Azure Storage unavailable | +| aspire-with-azure-functions | ❌ Minimal | Azure integrations entirely unavailable | + +### Recommendations for Aspire Team + +1. **Priority: Export JavaScript/Node.js hosting** — `AddJavaScriptApp`, `AddNodeApp`, `AddViteApp` should be annotated with `[AspireExport]` to enable polyglot samples involving JS frontends. +2. **Priority: Export Dockerfile builds** — `AddDockerfile` and `WithBuildArg` are fundamental container capabilities that should be available in all languages. +3. **Add `WithUrlForEndpoint` with simple string overload** — Instead of requiring a lambda, provide `withUrlForEndpoint("http", { displayText: "My App" })` as a simpler API. +4. **Export Azure integrations** — Azure Storage, Functions, and Container Apps are common enough to warrant ATS export. +5. **Add `WaitForCompletion`** — This is a commonly used capability for migration/init patterns. +6. **Document `addProject` limitations** — Clarify how TypeScript apphosts discover and reference .NET projects without generic type parameters. +7. **Support `AddParameter`** — Parameters with default values are important for publish-mode configuration. +8. **Consider custom resource extensibility** — Provide a mechanism for TypeScript apphosts to define custom resource types or interact with custom C# extensions. + +--- + +## Environment and Testing Notes + +### Running Polyglot AppHosts + +To test any of these TypeScript apphosts: + +```bash +# Install staging Aspire CLI +# On Windows: +iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" + +# On Linux/macOS: +curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging + +# Navigate to sample directory and run +cd samples/ +aspire run +``` + +### Expected Behavior + +When running `aspire run` with an `apphost.ts` present: +1. The CLI detects the TypeScript apphost +2. It scaffolds a .NET AppHost server project +3. It generates the TypeScript SDK in `.modules/` +4. It starts both the .NET server and Node.js guest +5. The Aspire dashboard shows all declared resources +6. Resources start in dependency order (via `waitFor`) + +### Known Runtime Issues + +1. **`.modules/` not pre-generated**: The TypeScript SDK is generated at runtime by the CLI. The + `import ... from "./.modules/aspire.js"` will fail if run directly with `node` or `ts-node`. + Always use `aspire run`. +2. **Project discovery**: `addProject("name")` may not automatically discover .NET project files. + The CLI may need additional configuration or a manifest to map project names to paths. +3. **Async chaining**: The TypeScript SDK uses `Thenable` wrappers for fluent async chaining. + Single `await` at the end of a chain is the expected pattern, but complex branching (like + conditional `withDataVolume`) may require intermediate `await` calls. From 781e1e0941123216dda99f29cc9d0bb3f623fcbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:24:57 +0000 Subject: [PATCH 4/9] Add aspire add setup commands and use package APIs in apphost.ts files Address review feedback: each apphost.ts now includes setup comments listing which aspire add commands to run for integration packages. Previously commented-out code (marked as POLYGLOT GAP) is now active code that uses APIs available after running aspire add. Updated POLYGLOT_NOTES.md with per-sample setup instructions and revised feasibility matrix. Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- samples/Metrics/MetricsApp.AppHost/apphost.ts | 22 +- samples/POLYGLOT_NOTES.md | 497 +++++++----------- .../aspire-shop/AspireShop.AppHost/apphost.ts | 13 +- .../ImageGallery.AppHost/apphost.ts | 48 +- .../AspireJavaScript.AppHost/apphost.ts | 52 +- .../AspireWithNode.AppHost/apphost.ts | 23 +- samples/aspire-with-python/apphost.ts | 23 +- .../ClientAppsIntegration.AppHost/apphost.ts | 28 +- samples/container-build/apphost.ts | 47 +- .../CustomResources.AppHost/apphost.ts | 16 +- .../DatabaseContainers.AppHost/apphost.ts | 21 +- .../DatabaseMigrations.AppHost/apphost.ts | 8 +- .../HealthChecksUI.AppHost/apphost.ts | 40 +- .../OrleansVoting.AppHost/apphost.ts | 19 +- .../VolumeMount.AppHost/apphost.ts | 17 +- 15 files changed, 420 insertions(+), 454 deletions(-) diff --git a/samples/Metrics/MetricsApp.AppHost/apphost.ts b/samples/Metrics/MetricsApp.AppHost/apphost.ts index 63e04d55..905651c8 100644 --- a/samples/Metrics/MetricsApp.AppHost/apphost.ts +++ b/samples/Metrics/MetricsApp.AppHost/apphost.ts @@ -1,15 +1,17 @@ +// Setup: No additional packages required (uses core container and project APIs). +// +// AddOpenTelemetryCollector is a custom AppHost extension method defined in the C# project +// (MetricsApp.AppHost.OpenTelemetryCollector) and requires [AspireExport] to be available here. + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: AddOpenTelemetryCollector() is a custom AppHost extension method (from MetricsApp.AppHost.OpenTelemetryCollector) -// and is not available in the TypeScript polyglot SDK. - const prometheus = await builder.addContainer("prometheus", "prom/prometheus", "v3.2.1") .withBindMount("../prometheus", "/etc/prometheus", true) .withArgs("--web.enable-otlp-receiver", "--config.file=/etc/prometheus/prometheus.yml") .withHttpEndpoint({ targetPort: 9090 }); -// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Prometheus Dashboard") — lambda URL customization is not available in the TypeScript SDK. +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Prometheus Dashboard") — lambda URL customization is not available. const prometheusHttpEndpoint = prometheus.getEndpoint("http"); @@ -18,16 +20,14 @@ const grafana = await builder.addContainer("grafana", "grafana/grafana") .withBindMount("../grafana/dashboards", "/var/lib/grafana/dashboards", true) .withEnvironment("PROMETHEUS_ENDPOINT", prometheusHttpEndpoint) .withHttpEndpoint({ targetPort: 3000 }); -// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Grafana Dashboard") — lambda URL customization is not available in the TypeScript SDK. +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Grafana Dashboard") — lambda URL customization is not available. -// POLYGLOT GAP: builder.AddOpenTelemetryCollector("otelcollector", "../otelcollector/config.yaml") -// .WithEnvironment("PROMETHEUS_ENDPOINT", `${prometheus.GetEndpoint("http")}/api/v1/otlp`) -// AddOpenTelemetryCollector is a custom extension method not available in the TypeScript SDK. +// POLYGLOT GAP: AddOpenTelemetryCollector("otelcollector", ...) is a custom C# extension +// method from MetricsApp.AppHost.OpenTelemetryCollector. To use it here, it would need +// [AspireExport] annotation and to be distributed as a NuGet package. const app = builder.addProject("app") .withEnvironment("GRAFANA_URL", grafana.getEndpoint("http")); -// POLYGLOT GAP: AddProject("app") — generic type parameter for project reference is not available; use addProject("name") instead. -// POLYGLOT GAP: .WithUrlForEndpoint("https", u => u.DisplayText = "Instrumented App") — lambda URL customization is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly) — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint callbacks for display text/location are not available. await builder.build().run(); diff --git a/samples/POLYGLOT_NOTES.md b/samples/POLYGLOT_NOTES.md index 9482a2cb..25fcdc5b 100644 --- a/samples/POLYGLOT_NOTES.md +++ b/samples/POLYGLOT_NOTES.md @@ -17,14 +17,34 @@ To run the polyglot TypeScript apphosts: ```powershell iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" ``` + Or on Linux/macOS: + ```bash + curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging + ``` 2. Node.js (v18+) or Bun must be installed -3. Run `aspire run` from the sample directory containing `apphost.ts` +3. **Add integration packages** using `aspire add {package}` for each sample (see per-sample setup below) +4. Run `aspire run` from the sample directory containing `apphost.ts` + +### Adding Integrations with `aspire add` + +The `aspire add` command adds NuGet hosting packages to the backing .NET AppHost server project. +This triggers code generation that makes the integration APIs available in the TypeScript SDK +(`.modules/aspire.js`). **You must run `aspire add` for each integration before the TypeScript +APIs become available.** + +For example: +```bash +aspire add redis # Makes builder.addRedis() available +aspire add postgres # Makes builder.addPostgres() available +aspire add javascript # Makes builder.addJavaScriptApp(), addNodeApp(), addViteApp() available +aspire add orleans # Makes builder.addOrleans() available +``` The CLI will: -- Scaffold an AppHost server project -- Generate the TypeScript SDK in `.modules/` -- Start the .NET AppHost server + Node.js/Bun guest runtime -- The generated SDK provides typed builder classes for all available capabilities +- Scaffold an AppHost server project (if not already present) +- Add the NuGet package to the server project +- Regenerate the TypeScript SDK in `.modules/` with the new capabilities +- Start the .NET AppHost server + Node.js/Bun guest runtime on `aspire run` ### Skipped Sample @@ -33,371 +53,247 @@ The CLI will: --- -## Per-Sample Gap Analysis +## Per-Sample Setup and Gap Analysis + +Each sample requires specific `aspire add` commands to install its integration packages. Run these +commands from the sample directory before using `aspire run`. ### 1. Metrics (`samples/Metrics/MetricsApp.AppHost/apphost.ts`) +**Setup:** No additional packages required (uses core container and project APIs). + **Convertible:** Partially -**Status:** Container resources work, but custom extension and URL customization do not. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Add container with image/tag | `AddContainer("prometheus", "prom/prometheus", "v3.2.1")` | ✅ Available | -| Bind mount (read-only) | `.WithBindMount("../prometheus", "/etc/prometheus", isReadOnly: true)` | ✅ Available | -| Container args | `.WithArgs("--web.enable-otlp-receiver", ...)` | ✅ Available | -| HTTP endpoint | `.WithHttpEndpoint(targetPort: 9090)` | ✅ Available | -| URL display customization | `.WithUrlForEndpoint("http", u => u.DisplayText = "...")` | ❌ Lambda callback not available | -| Custom extension method | `AddOpenTelemetryCollector(...)` | ❌ Custom C# extension — not exported to ATS | -| Project reference (generic) | `AddProject("app")` | ⚠️ `addProject("app")` (no type-safe project binding) | -| Environment from endpoint | `.WithEnvironment("GRAFANA_URL", grafana.GetEndpoint("http"))` | ✅ Available | - -**Key Limitation:** The `AddOpenTelemetryCollector` is a custom extension method defined in the sample's -AppHost project. Custom C# extensions require `[AspireExport]` attributes to be available in the -polyglot SDK, which this sample does not have. +**Remaining Gaps:** +- `AddOpenTelemetryCollector(...)` — Custom C# extension method from `MetricsApp.AppHost.OpenTelemetryCollector`. Would need `[AspireExport]` and NuGet packaging. +- `.WithUrlForEndpoint` lambda callbacks — URL display text/location customization not available. --- ### 2. aspire-shop (`samples/aspire-shop/AspireShop.AppHost/apphost.ts`) +**Setup:** +```bash +aspire add postgres +aspire add redis +``` + **Convertible:** Mostly -**Status:** Core resource orchestration works. HTTP commands and URL customization are gaps. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Postgres + PgAdmin | `AddPostgres("postgres").WithPgAdmin()` | ✅ Available | -| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | -| Conditional data volume | `if (IsRunMode) postgres.WithDataVolume()` | ✅ Available (via `executionContext`) | -| Add database | `postgres.AddDatabase("catalogdb")` | ✅ Available | -| Redis + Commander | `AddRedis("basketcache").WithDataVolume().WithRedisCommander()` | ✅ Available | -| Project references | `AddProject("name")` | ⚠️ `addProject("name")` — no generic type | -| HTTP health check | `.WithHttpHealthCheck("/health")` | ✅ Available | -| HTTP command | `.WithHttpCommand("/reset-db", "Reset Database", ...)` | ❌ Not available | -| URL display customization | `.WithUrlForEndpoint("https", url => url.DisplayText = "...")` | ❌ Lambda callback not available | -| External HTTP endpoints | `.WithExternalHttpEndpoints()` | ✅ Available | -| Resource references | `.WithReference(resource).WaitFor(resource)` | ✅ Available | - -**Key Limitation:** `WithHttpCommand` is an advanced feature for adding custom dashboard commands. -This is not exposed through ATS capabilities. +**Remaining Gaps:** +- `.WithHttpCommand("/reset-db", ...)` — Custom dashboard commands not available. +- `.WithUrlForEndpoint` lambda callbacks — URL display text customization not available. --- ### 3. aspire-with-javascript (`samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts`) -**Convertible:** Minimally -**Status:** Almost entirely blocked — JavaScript/Node.js hosting APIs are not available. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Project reference | `AddProject("weatherapi")` | ⚠️ `addProject("weatherapi")` | -| JavaScript app | `AddJavaScriptApp("angular", "../path", runScriptName: "start")` | ❌ Not available | -| Vite app | `AddViteApp("reactvite", "../path")` | ❌ Not available | -| Run script | `.WithRunScript("start")` | ❌ Not available | -| npm configuration | `.WithNpm(installCommand: "ci")` | ❌ Not available | -| Publish as Dockerfile | `.PublishAsDockerFile()` | ❌ Not available | -| Publish container files | `weatherApi.PublishWithContainerFiles(reactVite, "./wwwroot")` | ❌ Not available | -| HTTP endpoint with env | `.WithHttpEndpoint(env: "PORT")` | ❌ env parameter not available | +**Setup:** +```bash +aspire add javascript +``` -**Key Limitation:** The entire `Aspire.Hosting.JavaScript` package (AddJavaScriptApp, AddViteApp, -WithNpm, WithRunScript, PublishAsDockerFile) is not available in the polyglot SDK. This sample -is fundamentally about JavaScript hosting, making it almost entirely unconvertible. +**Convertible:** Mostly (after `aspire add javascript`) +**Remaining Gaps:** +- `.PublishAsDockerFile()` — Publish-time Dockerfile generation may not be available. +- `publishWithContainerFiles(reactVite, "./wwwroot")` — Bundling Vite output into a project's wwwroot may not be available. --- ### 4. aspire-with-node (`samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts`) -**Convertible:** Partially -**Status:** Redis and project resources work, but Node.js app hosting is not available. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Redis + Insight | `AddRedis("cache").WithRedisInsight()` | ✅ Available | -| Project reference | `AddProject("weatherapi")` | ⚠️ `addProject("weatherapi")` | -| Node.js app | `AddNodeApp("frontend", "../NodeFrontend", "./app.js")` | ❌ Not available | -| npm config | `.WithNpm()` | ❌ Not available | -| Run script | `.WithRunScript("dev")` | ❌ Not available | -| HTTP endpoint with env | `.WithHttpEndpoint(port: 5223, env: "PORT")` | ❌ env parameter not available | +**Setup:** +```bash +aspire add javascript +aspire add redis +``` -**Key Limitation:** `AddNodeApp` from `Aspire.Hosting.NodeJs` is not available. The Node.js -frontend cannot be orchestrated. +**Convertible:** Fully (after `aspire add javascript` + `aspire add redis`) +**Remaining Gaps:** None expected. --- ### 5. aspire-with-python (`samples/aspire-with-python/apphost.ts`) -**Convertible:** Minimally -**Status:** Almost entirely blocked — Python and Vite hosting APIs are not available. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Redis | `AddRedis("cache")` | ✅ Available | -| Uvicorn app | `AddUvicornApp("app", "./app", "main:app")` | ❌ Not available | -| UV package manager | `.WithUv()` | ❌ Not available | -| Vite app | `AddViteApp("frontend", "./frontend")` | ❌ Not available | -| Publish container files | `app.PublishWithContainerFiles(frontend, "./static")` | ❌ Not available | +**Setup:** +```bash +aspire add javascript +aspire add python +aspire add redis +``` -**Key Limitation:** Both `Aspire.Hosting.Python` (AddUvicornApp, WithUv) and -`Aspire.Hosting.JavaScript` (AddViteApp) packages are not available in the polyglot SDK. +**Convertible:** Mostly (after adding packages) +**Remaining Gaps:** +- `publishWithContainerFiles(frontend, "./static")` — May not be available. --- ### 6. client-apps-integration (`samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts`) -**Convertible:** Partially -**Status:** API service project works, but Windows platform checks and desktop app features don't. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Project reference | `AddProject("apiservice")` | ⚠️ `addProject("apiservice")` | -| Platform check | `OperatingSystem.IsWindows()` | ❌ Not available in TS context | -| Explicit start | `.WithExplicitStart()` | ❌ Not available | -| Exclude from manifest | `.ExcludeFromManifest()` | ❌ Not available | +**Setup:** No additional packages required. -**Key Limitation:** `OperatingSystem.IsWindows()` has no equivalent in the TypeScript SDK. -Desktop apps (WinForms, WPF) are Windows-only and their conditional inclusion cannot be expressed. +**Convertible:** Mostly +**Notes:** Uses `process.platform === "win32"` instead of `OperatingSystem.IsWindows()`. +**Remaining Gaps:** +- `.withExplicitStart()` / `.excludeFromManifest()` — May not be available as capabilities. --- ### 7. container-build (`samples/container-build/apphost.ts`) -**Convertible:** Minimally -**Status:** Almost entirely blocked — Dockerfile build APIs are not available. +**Setup:** No additional packages required (uses core Dockerfile and parameter APIs). -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Parameter with default | `AddParameter("goversion", "1.25.4", publishValueAsDefault: true)` | ❌ Not available | -| Dockerfile build | `AddDockerfile("ginapp", "./ginapp")` | ❌ Not available | -| Build arg | `.WithBuildArg("GO_VERSION", goVersion)` | ❌ Not available | -| OTLP exporter | `.WithOtlpExporter()` | ❌ Not available | -| Certificate trust | `.WithDeveloperCertificateTrust(trust: true)` | ❌ Not available | -| Execution context | `builder.ExecutionContext.IsPublishMode` | ✅ Available (via `executionContext`) | - -**Key Limitation:** `AddDockerfile` and `WithBuildArg` from `Aspire.Hosting` are not available. -This sample is fundamentally about building containers from Dockerfiles. +**Convertible:** Mostly +**Remaining Gaps:** +- `.withDeveloperCertificateTrust(true)` — Developer certificate trust may not be available. --- ### 8. custom-resources (`samples/custom-resources/CustomResources.AppHost/apphost.ts`) -**Convertible:** Not convertible -**Status:** Entirely blocked — custom resource types require C# implementation. +**Setup:** N/A — This sample uses custom C# resource extensions (`AddTalkingClock`, `AddTestResource`). -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Custom resource | `AddTalkingClock("talking-clock")` | ❌ Custom C# extension | -| Custom resource | `AddTestResource("test")` | ❌ Custom C# extension | - -**Key Limitation:** This sample demonstrates creating custom resource types in C#. These are -implemented as C# classes and extension methods. The polyglot SDK can only access capabilities -from NuGet packages with `[AspireExport]` attributes — custom project-level extensions are not -available. +**Convertible:** Not convertible +**Remaining Gaps:** +- Custom resource types (`AddTalkingClock`, `AddTestResource`) are C# classes defined in the project. They would need `[AspireExport]` attributes and NuGet distribution to be accessible from TypeScript. --- ### 9. database-containers (`samples/database-containers/DatabaseContainers.AppHost/apphost.ts`) -**Convertible:** Mostly -**Status:** Core database hosting works with minor gaps. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Postgres | `AddPostgres("postgres")` | ✅ Available | -| MySQL | `AddMySql("mysql")` | ✅ Available | -| SQL Server | `AddSqlServer("sqlserver")` | ✅ Available | -| Environment variable | `.WithEnvironment("POSTGRES_DB", todosDbName)` | ✅ Available | -| Bind mount | `.WithBindMount(source, target)` | ✅ Available | -| Data volume | `.WithDataVolume()` | ✅ Available | -| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | -| Add database | `.AddDatabase("name")` | ✅ Available | -| PgWeb | `.WithPgWeb()` | ❌ Not available | -| Creation script | `.WithCreationScript(File.ReadAllText(path))` | ❌ Not available (file I/O + API) | -| HTTP health check | `.WithHttpHealthCheck("/health")` | ✅ Available | - -**Key Limitation:** `WithCreationScript` requires reading a SQL file from disk and passing its -content. The polyglot SDK doesn't have a `File.ReadAllText` equivalent, and `WithCreationScript` -itself may not be exported. `WithPgWeb` is also not available. +**Setup:** +```bash +aspire add postgres +aspire add mysql +aspire add sqlserver +``` + +**Convertible:** Fully (after adding packages) +**Notes:** Uses Node.js `readFileSync` to read `init.sql` and pass it to `withCreationScript()`. +**Remaining Gaps:** None expected. --- ### 10. database-migrations (`samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts`) -**Convertible:** Mostly -**Status:** Works with minor API gap for WaitForCompletion. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| SQL Server + volume | `AddSqlServer("sqlserver").WithDataVolume()` | ✅ Available | -| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | -| Add database | `.AddDatabase("db1")` | ✅ Available | -| Project references | `AddProject("name")` | ⚠️ `addProject("name")` | -| Wait for completion | `.WaitForCompletion(migrationService)` | ❌ Only `waitFor` available | -| Resource references | `.WithReference(db1)` | ✅ Available | +**Setup:** +```bash +aspire add sqlserver +``` -**Key Limitation:** `WaitForCompletion` (wait for a service to finish, not just start) is not -available. This is semantically different from `WaitFor` — it waits for the migration service to -complete its work before starting the API. +**Convertible:** Fully (after adding package) +**Notes:** Uses `waitForCompletion()` which should be available via core capabilities. +**Remaining Gaps:** None expected. --- ### 11. health-checks-ui (`samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts`) -**Convertible:** Partially -**Status:** Core project orchestration works, but specialized features don't. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Docker Compose | `AddDockerComposeEnvironment("compose")` | ❌ Not available | -| Redis | `AddRedis("cache")` | ✅ Available | -| Project references | `AddProject("name")` | ⚠️ `addProject("name")` | -| Health checks UI | `AddHealthChecksUI("healthchecksui")` | ❌ Not available | -| HTTP probe | `.WithHttpProbe(ProbeType.Liveness, "/alive")` | ❌ Not available | -| Custom URL helper | `.WithFriendlyUrls(...)` | ❌ Custom extension method | -| Host port | `.WithHostPort(7230)` | ❌ Not available | -| Execution context | `builder.ExecutionContext.IsRunMode` | ✅ Available | - -**Key Limitation:** `AddDockerComposeEnvironment`, `AddHealthChecksUI`, and `WithHttpProbe` are -specialized integrations not available in the polyglot SDK. The `WithFriendlyUrls` is a custom -extension method defined in the same AppHost.cs file. +**Setup:** +```bash +aspire add redis +aspire add docker +``` + +**Convertible:** Mostly (after adding packages) +**Remaining Gaps:** +- `.WithFriendlyUrls(...)` — Custom C# extension method defined in the AppHost project. Needs `[AspireExport]` and NuGet packaging. --- ### 12. orleans-voting (`samples/orleans-voting/OrleansVoting.AppHost/apphost.ts`) -**Convertible:** Minimally -**Status:** Redis works, but Orleans integration is entirely unavailable. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Redis | `AddRedis("voting-redis")` | ✅ Available | -| Orleans cluster | `AddOrleans("voting-cluster")` | ❌ Not available | -| Orleans clustering | `.WithClustering(redis)` | ❌ Not available | -| Orleans grain storage | `.WithGrainStorage("votes", redis)` | ❌ Not available | -| Project with replicas | `.WithReplicas(3)` | ✅ Available | -| Orleans reference | `.WithReference(orleans)` | ❌ Orleans resource not available | -| URL customization | `.WithUrlForEndpoint(...)` | ❌ Lambda callback not available | +**Setup:** +```bash +aspire add redis +aspire add orleans +``` -**Key Limitation:** The entire `Aspire.Hosting.Orleans` package (AddOrleans, WithClustering, -WithGrainStorage) is not available in the polyglot SDK. +**Convertible:** Mostly (after adding packages) +**Remaining Gaps:** +- `.WithUrlForEndpoint` lambda callbacks — URL display text/location customization not available. --- ### 13. volume-mount (`samples/volume-mount/VolumeMount.AppHost/apphost.ts`) -**Convertible:** Partially -**Status:** SQL Server works, but Azure Storage emulator does not. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| SQL Server + volume | `AddSqlServer("sqlserver").WithDataVolume()` | ✅ Available | -| Container lifetime | `.WithLifetime(ContainerLifetime.Persistent)` | ✅ Available | -| Add database | `.AddDatabase("sqldb")` | ✅ Available | -| Azure Storage emulator | `AddAzureStorage("Storage").RunAsEmulator(...)` | ❌ Not available | -| Emulator callback | `.RunAsEmulator(emulator => emulator.WithDataVolume())` | ❌ Lambda not available | -| Azure Blobs | `.AddBlobs("BlobConnection")` | ❌ Not available | -| Project reference | `AddProject("blazorweb")` | ⚠️ `addProject("blazorweb")` | +**Setup:** +```bash +aspire add sqlserver +aspire add azure-storage +``` -**Key Limitation:** `AddAzureStorage` and `RunAsEmulator` with callback configuration are not -available. Azure storage integration is a major gap. +**Convertible:** Fully (after adding packages) +**Remaining Gaps:** None expected. --- ### 14. aspire-with-azure-functions (`samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts`) -**Convertible:** Minimally -**Status:** Almost entirely blocked — Azure-specific APIs are not available. - -| Feature | C# API | TypeScript Status | -|---------|--------|-------------------| -| Azure Container App Env | `AddAzureContainerAppEnvironment("env")` | ❌ Not available | -| Azure Storage | `AddAzureStorage("storage").RunAsEmulator()` | ❌ Not available | -| Configure infrastructure | `.ConfigureInfrastructure(...)` | ❌ Not available | -| URL display customization | `.WithUrls(...)` | ❌ Lambda callback not available | -| Azure Blobs | `storage.AddBlobs("blobs")` | ❌ Not available | -| Azure Queues | `storage.AddQueues("queues")` | ❌ Not available | -| Azure Functions project | `AddAzureFunctionsProject("name")` | ❌ Not available | -| Role assignments | `.WithRoleAssignments(storage, ...)` | ❌ Not available | -| Host storage | `.WithHostStorage(storage)` | ❌ Not available | -| Project reference | `AddProject("frontend")` | ⚠️ `addProject("frontend")` | - -**Key Limitation:** This sample is entirely Azure-focused. None of the Azure-specific hosting -packages (Azure Storage, Functions, Container Apps) are available in the polyglot SDK. +**Setup:** +```bash +aspire add azure-appcontainers +aspire add azure-storage +aspire add azure-functions +``` + +**Convertible:** Mostly (after adding packages) +**Remaining Gaps:** +- `.ConfigureInfrastructure(...)` — Bicep infrastructure configuration using C# lambdas is not directly available. Default settings will be used. +- `.WithUrlForEndpoint` lambda callbacks — URL display text customization not available. --- ## Cross-Cutting Issues Summary -### Universally Available Features ✅ -These features work across all samples in the TypeScript polyglot SDK: -- `createBuilder()` — Create the distributed application builder -- `addRedis("name")` — Add Redis container resource -- `addPostgres("name")` — Add PostgreSQL container resource -- `addMySql("name")` — Add MySQL container resource -- `addSqlServer("name")` — Add SQL Server container resource -- `addContainer("name", "image", "tag")` — Add generic container resource -- `.addDatabase("name")` — Add database to a database server -- `.withDataVolume()` — Add data volume to container -- `.withLifetime(ContainerLifetime.Persistent)` — Set container lifetime -- `.withBindMount(source, target, isReadOnly)` — Add bind mount -- `.withEnvironment("key", "value")` — Set environment variable -- `.withArgs(...)` — Set container arguments -- `.withHttpEndpoint({ targetPort: N })` — Add HTTP endpoint -- `.withHttpHealthCheck("/path")` — Add HTTP health check -- `.withExternalHttpEndpoints()` — Expose endpoints externally -- `.withReference(resource)` — Add resource reference -- `.waitFor(resource)` — Wait for resource to be ready -- `.withReplicas(N)` — Set replica count -- `.withRedisCommander()` / `.withRedisInsight()` — Redis admin tools -- `.withPgAdmin()` — PostgreSQL admin tool -- `getEndpoint("name")` — Get endpoint reference -- `builder.executionContext` — Access execution context (run vs publish mode) -- `builder.build().run()` — Build and run the application - -### Universally Unavailable Features ❌ -These features are NOT available in any sample: -1. **`AddProject("name")` generic type parameter** — The TypeScript SDK uses `addProject("name")` without type-safe project binding. This means the AppHost server needs another mechanism to discover and bind .NET project references. -2. **`WithUrlForEndpoint` with lambda callback** — URL display customization (display text, display location) requires callbacks that aren't available in the polyglot SDK. -3. **Custom C# extension methods** — Any extension method defined in the sample's AppHost project (e.g., `AddOpenTelemetryCollector`, `AddTalkingClock`, `WithFriendlyUrls`) requires `[AspireExport]` annotation and NuGet packaging to be available. - -### Major Feature Category Gaps ❌ -1. **JavaScript/Node.js hosting** (`Aspire.Hosting.JavaScript`): `AddJavaScriptApp`, `AddViteApp`, `AddNodeApp`, `WithNpm`, `WithRunScript`, `PublishAsDockerFile` -2. **Python hosting** (`Aspire.Hosting.Python`): `AddUvicornApp`, `WithUv` -3. **Azure integrations** (`Aspire.Hosting.Azure.*`): `AddAzureStorage`, `AddAzureFunctionsProject`, `AddAzureContainerAppEnvironment`, `RunAsEmulator`, `ConfigureInfrastructure`, `WithRoleAssignments` -4. **Orleans** (`Aspire.Hosting.Orleans`): `AddOrleans`, `WithClustering`, `WithGrainStorage` -5. **Docker Compose** (`Aspire.Hosting.Docker`): `AddDockerComposeEnvironment` -6. **Dockerfile builds**: `AddDockerfile`, `WithBuildArg` -7. **HealthChecks UI**: `AddHealthChecksUI`, `WithHttpProbe` -8. **Parameters**: `AddParameter` with `publishValueAsDefault` -9. **Completion waiting**: `WaitForCompletion` (different from `WaitFor`) -10. **Dashboard features**: `WithHttpCommand`, `WithHostPort`, `WithExplicitStart`, `ExcludeFromManifest` - -### Sample Conversion Feasibility Matrix - -| Sample | Feasibility | Notes | -|--------|-------------|-------| -| Metrics | ⚠️ Partial | Container resources work; custom OTel collector extension missing | -| aspire-shop | ✅ Mostly | Core orchestration works; HTTP commands, URL customization missing | -| aspire-with-javascript | ❌ Minimal | JS/Vite hosting entirely unavailable | -| aspire-with-node | ⚠️ Partial | Redis works; Node.js app hosting unavailable | -| aspire-with-python | ❌ Minimal | Python/Vite hosting entirely unavailable | -| client-apps-integration | ⚠️ Partial | API project works; platform checks, desktop apps unavailable | -| container-build | ❌ Minimal | Dockerfile builds entirely unavailable | -| custom-resources | ❌ None | Custom resources require C# implementation | -| database-containers | ✅ Mostly | Core DB hosting works; PgWeb, creation scripts missing | -| database-migrations | ✅ Mostly | Works except WaitForCompletion | -| health-checks-ui | ⚠️ Partial | Core projects work; Compose, HealthChecks UI, probes missing | -| orleans-voting | ❌ Minimal | Redis works; Orleans entirely unavailable | -| volume-mount | ⚠️ Partial | SQL Server works; Azure Storage unavailable | -| aspire-with-azure-functions | ❌ Minimal | Azure integrations entirely unavailable | - -### Recommendations for Aspire Team - -1. **Priority: Export JavaScript/Node.js hosting** — `AddJavaScriptApp`, `AddNodeApp`, `AddViteApp` should be annotated with `[AspireExport]` to enable polyglot samples involving JS frontends. -2. **Priority: Export Dockerfile builds** — `AddDockerfile` and `WithBuildArg` are fundamental container capabilities that should be available in all languages. -3. **Add `WithUrlForEndpoint` with simple string overload** — Instead of requiring a lambda, provide `withUrlForEndpoint("http", { displayText: "My App" })` as a simpler API. -4. **Export Azure integrations** — Azure Storage, Functions, and Container Apps are common enough to warrant ATS export. -5. **Add `WaitForCompletion`** — This is a commonly used capability for migration/init patterns. -6. **Document `addProject` limitations** — Clarify how TypeScript apphosts discover and reference .NET projects without generic type parameters. -7. **Support `AddParameter`** — Parameters with default values are important for publish-mode configuration. -8. **Consider custom resource extensibility** — Provide a mechanism for TypeScript apphosts to define custom resource types or interact with custom C# extensions. +### Features Available After `aspire add` ✅ +These features work after adding the appropriate integration packages: +- `createBuilder()` — Create the distributed application builder (core) +- `addRedis("name")` — `aspire add redis` +- `addPostgres("name")` / `.withPgAdmin()` / `.withPgWeb()` — `aspire add postgres` +- `addMySql("name")` — `aspire add mysql` +- `addSqlServer("name")` — `aspire add sqlserver` +- `addJavaScriptApp()` / `addNodeApp()` / `addViteApp()` — `aspire add javascript` +- `addUvicornApp()` / `.withUv()` — `aspire add python` +- `addOrleans()` / `.withClustering()` / `.withGrainStorage()` — `aspire add orleans` +- `addDockerComposeEnvironment()` — `aspire add docker` +- `addHealthChecksUI()` / `.withHttpProbe()` — `aspire add docker` (or dedicated package) +- `addAzureStorage()` / `.addBlobs()` / `.addQueues()` — `aspire add azure-storage` +- `addAzureContainerAppEnvironment()` — `aspire add azure-appcontainers` +- `addAzureFunctionsProject()` — `aspire add azure-functions` +- Core capabilities (always available): `addContainer()`, `addProject()`, `addDockerfile()`, `addParameter()`, `.withBindMount()`, `.withEnvironment()`, `.withArgs()`, `.withHttpEndpoint()`, `.withHttpHealthCheck()`, `.withExternalHttpEndpoints()`, `.withReference()`, `.waitFor()`, `.waitForCompletion()`, `.withReplicas()`, `.withDataVolume()`, `.withLifetime()`, `.withOtlpExporter()`, `.withBuildArg()`, `getEndpoint()`, `builder.executionContext`, `builder.build().run()` + +### Remaining Gaps ❌ +These features have no polyglot equivalent regardless of packages: +1. **`.WithUrlForEndpoint` with lambda callback** — URL display customization (display text, display location) requires C# callbacks that can't be expressed in TypeScript. +2. **`.ConfigureInfrastructure` with lambda** — Bicep infrastructure configuration requires C# lambdas for accessing provisioning types. +3. **Custom C# extension methods** — Any extension method defined in the sample's AppHost project (e.g., `AddOpenTelemetryCollector`, `AddTalkingClock`, `WithFriendlyUrls`) requires `[AspireExport]` annotation and NuGet packaging. +4. **`.WithHttpCommand`** — Custom dashboard commands are not exposed through ATS capabilities. +5. **`.PublishAsDockerFile` / `.publishWithContainerFiles`** — Publish-time behaviors may not be available. + +### Sample Conversion Feasibility Matrix (with `aspire add`) + +| Sample | `aspire add` Commands | Feasibility | Remaining Gaps | +|--------|----------------------|-------------|----------------| +| Metrics | (none) | ⚠️ Partial | Custom OTel collector extension, URL callbacks | +| aspire-shop | `postgres`, `redis` | ✅ Mostly | HTTP commands, URL callbacks | +| aspire-with-javascript | `javascript` | ✅ Mostly | `PublishAsDockerFile`, `publishWithContainerFiles` | +| aspire-with-node | `javascript`, `redis` | ✅ Full | — | +| aspire-with-python | `javascript`, `python`, `redis` | ✅ Mostly | `publishWithContainerFiles` | +| client-apps-integration | (none) | ✅ Mostly | `withExplicitStart`, `excludeFromManifest` | +| container-build | (none) | ✅ Mostly | `withDeveloperCertificateTrust` | +| custom-resources | N/A | ❌ None | Custom C# resource types | +| database-containers | `postgres`, `mysql`, `sqlserver` | ✅ Full | — | +| database-migrations | `sqlserver` | ✅ Full | — | +| health-checks-ui | `redis`, `docker` | ✅ Mostly | Custom `WithFriendlyUrls` extension | +| orleans-voting | `redis`, `orleans` | ✅ Mostly | URL callbacks | +| volume-mount | `sqlserver`, `azure-storage` | ✅ Full | — | +| aspire-with-azure-functions | `azure-appcontainers`, `azure-storage`, `azure-functions` | ✅ Mostly | `ConfigureInfrastructure` lambda, URL callbacks | + +### Recommendations + +1. **Add `[AspireExport]` to `WithUrlForEndpoint`** — Provide a non-lambda overload (e.g., `withUrlForEndpoint("http", { displayText: "My App" })`) for display customization. +2. **Add `[AspireExport]` to `WithHttpCommand`** — Dashboard commands are useful for operations like database resets. +3. **Document `addProject` behavior** — Clarify how TypeScript apphosts discover and reference .NET projects without generic type parameters. +4. **Consider custom resource extensibility** — Provide a mechanism for TypeScript apphosts to interact with custom C# extensions that have `[AspireExport]` attributes. --- @@ -415,8 +311,15 @@ iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" # On Linux/macOS: curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging -# Navigate to sample directory and run +# Navigate to sample directory cd samples/ + +# Add required integration packages (see per-sample setup above) +aspire add redis +aspire add postgres +# ... etc. + +# Run the polyglot apphost aspire run ``` @@ -424,19 +327,23 @@ aspire run When running `aspire run` with an `apphost.ts` present: 1. The CLI detects the TypeScript apphost -2. It scaffolds a .NET AppHost server project -3. It generates the TypeScript SDK in `.modules/` -4. It starts both the .NET server and Node.js guest -5. The Aspire dashboard shows all declared resources -6. Resources start in dependency order (via `waitFor`) +2. It scaffolds a .NET AppHost server project (if not already present) +3. `aspire add` installs NuGet packages and triggers SDK regeneration +4. It generates the TypeScript SDK in `.modules/` with all available capabilities +5. It starts both the .NET server and Node.js guest +6. The Aspire dashboard shows all declared resources +7. Resources start in dependency order (via `waitFor`) ### Known Runtime Issues 1. **`.modules/` not pre-generated**: The TypeScript SDK is generated at runtime by the CLI. The `import ... from "./.modules/aspire.js"` will fail if run directly with `node` or `ts-node`. Always use `aspire run`. -2. **Project discovery**: `addProject("name")` may not automatically discover .NET project files. - The CLI may need additional configuration or a manifest to map project names to paths. -3. **Async chaining**: The TypeScript SDK uses `Thenable` wrappers for fluent async chaining. +2. **Must run `aspire add` first**: Integration APIs (like `addRedis`, `addPostgres`) are only + available after adding the corresponding packages with `aspire add`. Without them, the generated + SDK won't include those capabilities. +3. **Project discovery**: `addProject("name")` discovers .NET projects via the Aspire CLI's + project detection. Ensure project files are in the expected directory structure. +4. **Async chaining**: The TypeScript SDK uses `Thenable` wrappers for fluent async chaining. Single `await` at the end of a chain is the expected pattern, but complex branching (like conditional `withDataVolume`) may require intermediate `await` calls. diff --git a/samples/aspire-shop/AspireShop.AppHost/apphost.ts b/samples/aspire-shop/AspireShop.AppHost/apphost.ts index 6900fb49..b33268e0 100644 --- a/samples/aspire-shop/AspireShop.AppHost/apphost.ts +++ b/samples/aspire-shop/AspireShop.AppHost/apphost.ts @@ -1,3 +1,7 @@ +// Setup: Run the following commands to add required integrations: +// aspire add postgres +// aspire add redis + import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; const builder = await createBuilder(); @@ -22,19 +26,16 @@ const catalogDbManager = builder.addProject("catalogdbmanager") .withReference(catalogDb) .waitFor(catalogDb) .withHttpHealthCheck("/health"); -// POLYGLOT GAP: AddProject("catalogdbmanager") — generic type parameter for project reference is not available; use addProject("name") instead. -// POLYGLOT GAP: .WithHttpCommand("/reset-db", "Reset Database", commandOptions: new() { IconName = "DatabaseLightning" }) — custom HTTP commands are not available in the TypeScript SDK. +// POLYGLOT GAP: .WithHttpCommand("/reset-db", "Reset Database", ...) — custom HTTP commands are not available. const catalogService = builder.addProject("catalogservice") .withReference(catalogDb) .waitFor(catalogDbManager) .withHttpHealthCheck("/health"); -// POLYGLOT GAP: AddProject("catalogservice") — generic type parameter for project reference is not available. const basketService = builder.addProject("basketservice") .withReference(basketCache) .waitFor(basketCache); -// POLYGLOT GAP: AddProject("basketservice") — generic type parameter for project reference is not available. const frontend = builder.addProject("frontend") .withExternalHttpEndpoints() @@ -42,8 +43,6 @@ const frontend = builder.addProject("frontend") .withReference(basketService) .withReference(catalogService) .waitFor(catalogService); -// POLYGLOT GAP: AddProject("frontend") — generic type parameter for project reference is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("https", url => url.DisplayText = "Online Store (HTTPS)") — lambda URL customization is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("http", url => url.DisplayText = "Online Store (HTTP)") — lambda URL customization is not available. +// POLYGLOT GAP: .WithUrlForEndpoint callbacks for display text are not available. await builder.build().run(); diff --git a/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts index 91bffc82..04da61e0 100644 --- a/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts +++ b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts @@ -1,31 +1,39 @@ -import { createBuilder } from "./.modules/aspire.js"; +// Setup: Run the following commands to add required integrations: +// aspire add azure-appcontainers +// aspire add azure-storage +// aspire add azure-functions + +import { createBuilder, StorageBuiltInRole } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: AddAzureContainerAppEnvironment("env") — Azure Container App environment is not available in the TypeScript polyglot SDK. -// builder.addAzureContainerAppEnvironment("env"); +builder.addAzureContainerAppEnvironment("env"); -// POLYGLOT GAP: AddAzureStorage("storage").RunAsEmulator() — Azure Storage emulator integration is not available. -// POLYGLOT GAP: .ConfigureInfrastructure(infra => { ... }) — Bicep infrastructure configuration with -// Azure.Provisioning.Storage.StorageAccount and BlobService properties is not available. -// const storage = builder.addAzureStorage("storage").runAsEmulator(); +const storage = builder.addAzureStorage("storage") + .runAsEmulator(); +// POLYGLOT GAP: .ConfigureInfrastructure(infra => { ... }) — Bicep infrastructure configuration +// with Azure.Provisioning.Storage.StorageAccount is a C# lambda and not directly available. +// The storage account will use default settings. -// POLYGLOT GAP: storage.AddBlobs("blobs") — Azure Blob storage integration is not available. -// POLYGLOT GAP: storage.AddQueues("queues") — Azure Queue storage integration is not available. -// const blobs = storage.addBlobs("blobs"); -// const queues = storage.addQueues("queues"); +const blobs = storage.addBlobs("blobs"); +const queues = storage.addQueues("queues"); -// POLYGLOT GAP: AddAzureFunctionsProject("functions") — Azure Functions project integration is not available. -// POLYGLOT GAP: .WithRoleAssignments(storage, StorageBuiltInRole.StorageBlobDataContributor, ...) — Azure role assignments are not available. -// POLYGLOT GAP: .WithHostStorage(storage) — host storage configuration is not available. -// const functions = builder.addAzureFunctionsProject("functions") -// .withReference(queues).withReference(blobs).waitFor(storage) -// .withRoleAssignments(storage, ...).withHostStorage(storage); +const functions = builder.addAzureFunctionsProject("functions") + .withReference(queues) + .withReference(blobs) + .waitFor(storage) + .withRoleAssignments(storage, + StorageBuiltInRole.StorageAccountContributor, + StorageBuiltInRole.StorageBlobDataOwner, + StorageBuiltInRole.StorageQueueDataContributor) + .withHostStorage(storage); +// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayText = "Functions App") — lambda URL customization is not available. -// POLYGLOT GAP: AddProject("frontend") — generic type parameter for project reference is not available. -// POLYGLOT GAP: Full functionality requires Azure Storage, Queues, Blobs, and Functions project references above. const frontend = builder.addProject("frontend") + .withReference(queues) + .withReference(blobs) + .waitFor(functions) .withExternalHttpEndpoints(); -// POLYGLOT GAP: .withReference(queues).withReference(blobs).waitFor(functions) — cannot reference Azure resources (see gaps above). +// POLYGLOT GAP: .WithUrlForEndpoint callbacks for display text are not available. await builder.build().run(); diff --git a/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts b/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts index 1d90df8e..adb0d1fc 100644 --- a/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts +++ b/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts @@ -1,34 +1,42 @@ +// Setup: Run the following commands to add required integrations: +// aspire add javascript + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: AddProject("weatherapi") — generic type parameter for project reference is not available. const weatherApi = builder.addProject("weatherapi") .withExternalHttpEndpoints(); -// POLYGLOT GAP: AddJavaScriptApp() is not available in the TypeScript polyglot SDK. -// The following Angular, React, and Vue apps cannot be added directly. - -// POLYGLOT GAP: builder.AddJavaScriptApp("angular", "../AspireJavaScript.Angular", runScriptName: "start") -// .WithReference(weatherApi).WaitFor(weatherApi).WithHttpEndpoint(env: "PORT") -// .WithExternalHttpEndpoints().PublishAsDockerFile() -// AddJavaScriptApp, WithHttpEndpoint with env parameter, and PublishAsDockerFile are not available. - -// POLYGLOT GAP: builder.AddJavaScriptApp("react", "../AspireJavaScript.React", runScriptName: "start") -// .WithReference(weatherApi).WaitFor(weatherApi).WithEnvironment("BROWSER", "none") -// .WithHttpEndpoint(env: "PORT").WithExternalHttpEndpoints().PublishAsDockerFile() -// AddJavaScriptApp, WithHttpEndpoint with env parameter, and PublishAsDockerFile are not available. +const angular = builder.addJavaScriptApp("angular", "../AspireJavaScript.Angular", "start") + .withReference(weatherApi) + .waitFor(weatherApi) + .withHttpEndpoint({ env: "PORT" }) + .withExternalHttpEndpoints(); +// POLYGLOT GAP: .PublishAsDockerFile() — publish-time Dockerfile generation may not be available. -// POLYGLOT GAP: builder.AddJavaScriptApp("vue", "../AspireJavaScript.Vue").WithRunScript("start").WithNpm(installCommand: "ci") -// .WithReference(weatherApi).WaitFor(weatherApi).WithHttpEndpoint(env: "PORT") -// .WithExternalHttpEndpoints().PublishAsDockerFile() -// AddJavaScriptApp, WithRunScript, WithNpm, WithHttpEndpoint with env parameter, and PublishAsDockerFile are not available. +const react = builder.addJavaScriptApp("react", "../AspireJavaScript.React", "start") + .withReference(weatherApi) + .waitFor(weatherApi) + .withEnvironment("BROWSER", "none") + .withHttpEndpoint({ env: "PORT" }) + .withExternalHttpEndpoints(); +// POLYGLOT GAP: .PublishAsDockerFile() — publish-time Dockerfile generation may not be available. + +const vue = builder.addJavaScriptApp("vue", "../AspireJavaScript.Vue") + .withRunScript("start") + .withNpm({ installCommand: "ci" }) + .withReference(weatherApi) + .waitFor(weatherApi) + .withHttpEndpoint({ env: "PORT" }) + .withExternalHttpEndpoints(); +// POLYGLOT GAP: .PublishAsDockerFile() — publish-time Dockerfile generation may not be available. -// POLYGLOT GAP: builder.AddViteApp("reactvite", "../AspireJavaScript.Vite") -// .WithReference(weatherApi).WithEnvironment("BROWSER", "none") -// AddViteApp is not available in the TypeScript polyglot SDK. +const reactVite = builder.addViteApp("reactvite", "../AspireJavaScript.Vite") + .withReference(weatherApi) + .withEnvironment("BROWSER", "none"); -// POLYGLOT GAP: weatherApi.PublishWithContainerFiles(reactVite, "./wwwroot") -// PublishWithContainerFiles is not available in the TypeScript polyglot SDK. +// POLYGLOT GAP: weatherApi.publishWithContainerFiles(reactVite, "./wwwroot") — bundling Vite +// output into a project's wwwroot may not be available. await builder.build().run(); diff --git a/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts b/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts index 213a1699..65eaa20f 100644 --- a/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts +++ b/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts @@ -1,3 +1,7 @@ +// Setup: Run the following commands to add required integrations: +// aspire add javascript +// aspire add redis + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); @@ -5,17 +9,18 @@ const builder = await createBuilder(); const cache = await builder.addRedis("cache") .withRedisInsight(); -// POLYGLOT GAP: AddProject("weatherapi") — generic type parameter for project reference is not available. const weatherapi = builder.addProject("weatherapi") .withHttpHealthCheck("/health"); -// POLYGLOT GAP: AddNodeApp("frontend", "../NodeFrontend", "./app.js") is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: .WithNpm() — npm configuration is not available. -// POLYGLOT GAP: .WithRunScript("dev") — run script configuration is not available. -// POLYGLOT GAP: .WithHttpEndpoint(port: 5223, env: "PORT") — WithHttpEndpoint with env parameter is not available. -// The following Node.js app cannot be added directly: -// builder.AddNodeApp("frontend", "../NodeFrontend", "./app.js").WithNpm().WithRunScript("dev") -// .WithHttpEndpoint(port: 5223, env: "PORT").WithExternalHttpEndpoints().WithHttpHealthCheck("/health") -// .WithReference(weatherapi).WaitFor(weatherapi).WithReference(cache).WaitFor(cache) +const frontend = builder.addNodeApp("frontend", "../NodeFrontend", "./app.js") + .withNpm() + .withRunScript("dev") + .withHttpEndpoint({ port: 5223, env: "PORT" }) + .withExternalHttpEndpoints() + .withHttpHealthCheck("/health") + .withReference(weatherapi) + .waitFor(weatherapi) + .withReference(cache) + .waitFor(cache); await builder.build().run(); diff --git a/samples/aspire-with-python/apphost.ts b/samples/aspire-with-python/apphost.ts index 86a2de32..039b0a5e 100644 --- a/samples/aspire-with-python/apphost.ts +++ b/samples/aspire-with-python/apphost.ts @@ -1,15 +1,26 @@ +// Setup: Run the following commands to add required integrations: +// aspire add javascript +// aspire add python +// aspire add redis + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); const cache = builder.addRedis("cache"); -// POLYGLOT GAP: AddUvicornApp("app", "./app", "main:app") is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: .WithUv() — UV package manager configuration is not available. -// POLYGLOT GAP: .WithExternalHttpEndpoints().WithReference(cache).WaitFor(cache).WithHttpHealthCheck("/health") -// The Python/Uvicorn app cannot be added directly. +const app = builder.addUvicornApp("app", "./app", "main:app") + .withUv() + .withExternalHttpEndpoints() + .withReference(cache) + .waitFor(cache) + .withHttpHealthCheck("/health"); + +const frontend = builder.addViteApp("frontend", "./frontend") + .withReference(app) + .waitFor(app); -// POLYGLOT GAP: AddViteApp("frontend", "./frontend") is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: app.PublishWithContainerFiles(frontend, "./static") — PublishWithContainerFiles is not available. +// POLYGLOT GAP: app.publishWithContainerFiles(frontend, "./static") — bundling Vite output +// into the Python app's static directory may not be available. await builder.build().run(); diff --git a/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts b/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts index 4566a6e0..baa54451 100644 --- a/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts +++ b/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts @@ -1,19 +1,25 @@ +// Setup: No additional packages required (uses core project APIs). + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: AddProject("apiservice") — generic type parameter for project reference is not available. const apiService = builder.addProject("apiservice"); -// POLYGLOT GAP: OperatingSystem.IsWindows() — platform check is not available in the TypeScript apphost context. -// The following Windows-only projects cannot be conditionally added: -// if (OperatingSystem.IsWindows()) { -// builder.AddProject("winformsclient") -// .WithReference(apiService).WaitFor(apiService).WithExplicitStart().ExcludeFromManifest(); -// builder.AddProject("wpfclient") -// .WithReference(apiService).WaitFor(apiService).WithExplicitStart().ExcludeFromManifest(); -// } -// POLYGLOT GAP: .WithExplicitStart() — explicit start configuration is not available. -// POLYGLOT GAP: .ExcludeFromManifest() — manifest exclusion is not available. +// The C# version conditionally adds WinForms/WPF projects on Windows using OperatingSystem.IsWindows(). +// In TypeScript, we can use process.platform for the equivalent check. +if (process.platform === "win32") { + builder.addProject("winformsclient") + .withReference(apiService) + .waitFor(apiService) + .withExplicitStart() + .excludeFromManifest(); + + builder.addProject("wpfclient") + .withReference(apiService) + .waitFor(apiService) + .withExplicitStart() + .excludeFromManifest(); +} await builder.build().run(); diff --git a/samples/container-build/apphost.ts b/samples/container-build/apphost.ts index ad913160..c6a2ee1d 100644 --- a/samples/container-build/apphost.ts +++ b/samples/container-build/apphost.ts @@ -1,33 +1,36 @@ +// Setup: No additional packages required (uses core container and Dockerfile APIs). + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: builder.AddParameter("goversion", "1.25.4", publishValueAsDefault: true) — AddParameter with publishValueAsDefault is not available in the TypeScript SDK. -// Using a plain string as a workaround for the parameter value. -const goVersion = "1.25.4"; +const goVersion = builder.addParameter("goversion", { default: "1.25.4" }); const execCtx = await builder.executionContext.get(); const isRunMode = await execCtx.isRunMode.get(); const isPublishMode = !isRunMode; -// POLYGLOT GAP: AddDockerfile("ginapp", "./ginapp") and AddDockerfile("ginapp", "./ginapp", "Dockerfile.dev") — AddDockerfile is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: .WithBuildArg("GO_VERSION", goVersion) — WithBuildArg is not available. -// POLYGLOT GAP: .WithOtlpExporter() — OTLP exporter configuration is not available. -// POLYGLOT GAP: .WithDeveloperCertificateTrust(trust: true) — developer certificate trust is not available. -// -// The following Dockerfile-based container cannot be added directly: -// let ginapp; -// if (isPublishMode) { -// ginapp = builder.addDockerfile("ginapp", "./ginapp").withBuildArg("GO_VERSION", goVersion); -// } else { -// ginapp = builder.addDockerfile("ginapp", "./ginapp", "Dockerfile.dev") -// .withBuildArg("GO_VERSION", goVersion).withBindMount("./ginapp", "/app"); -// } -// ginapp.withHttpEndpoint({ targetPort: 5555, env: "PORT" }) -// .withHttpHealthCheck("/").withExternalHttpEndpoints() -// .withOtlpExporter().withDeveloperCertificateTrust(true); -// if (isPublishMode) { -// ginapp.withEnvironment("GIN_MODE", "release").withEnvironment("TRUSTED_PROXIES", "all"); -// } +let ginapp; +if (isPublishMode) { + ginapp = builder.addDockerfile("ginapp", "./ginapp") + .withBuildArg("GO_VERSION", goVersion); +} else { + ginapp = builder.addDockerfile("ginapp", "./ginapp", "Dockerfile.dev") + .withBuildArg("GO_VERSION", goVersion) + .withBindMount("./ginapp", "/app"); +} + +ginapp + .withHttpEndpoint({ targetPort: 5555, env: "PORT" }) + .withHttpHealthCheck("/") + .withExternalHttpEndpoints() + .withOtlpExporter(); +// POLYGLOT GAP: .withDeveloperCertificateTrust(true) — developer certificate trust may not be available. + +if (isPublishMode) { + ginapp + .withEnvironment("GIN_MODE", "release") + .withEnvironment("TRUSTED_PROXIES", "all"); +} await builder.build().run(); diff --git a/samples/custom-resources/CustomResources.AppHost/apphost.ts b/samples/custom-resources/CustomResources.AppHost/apphost.ts index 9f4df246..ba5f749a 100644 --- a/samples/custom-resources/CustomResources.AppHost/apphost.ts +++ b/samples/custom-resources/CustomResources.AppHost/apphost.ts @@ -1,13 +1,15 @@ +// Setup: No standard packages — this sample uses custom C# resource extensions. +// +// POLYGLOT GAP: AddTalkingClock and AddTestResource are custom C# resource extensions +// defined in CustomResources.AppHost. To use them from TypeScript, they would need +// [AspireExport] attributes and distribution as a NuGet package, then added via: +// aspire add + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: AddTalkingClock("talking-clock") is a custom resource extension (from CustomResources.AppHost) -// and is not available in the TypeScript polyglot SDK. -// builder.AddTalkingClock("talking-clock"); - -// POLYGLOT GAP: AddTestResource("test") is a custom resource extension (from CustomResources.AppHost) -// and is not available in the TypeScript polyglot SDK. -// builder.AddTestResource("test"); +// builder.addTalkingClock("talking-clock"); +// builder.addTestResource("test"); await builder.build().run(); diff --git a/samples/database-containers/DatabaseContainers.AppHost/apphost.ts b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts index 112dc9c0..99b6f9dd 100644 --- a/samples/database-containers/DatabaseContainers.AppHost/apphost.ts +++ b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts @@ -1,4 +1,12 @@ +// Setup: Run the following commands to add required integrations: +// aspire add postgres +// aspire add mysql +// aspire add sqlserver + import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; +import { readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; const builder = await createBuilder(); @@ -8,8 +16,8 @@ const postgres = await builder.addPostgres("postgres") .withEnvironment("POSTGRES_DB", todosDbName) .withBindMount("../DatabaseContainers.ApiService/data/postgres", "/docker-entrypoint-initdb.d") .withDataVolume() + .withPgWeb() .withLifetime(ContainerLifetime.Persistent); -// POLYGLOT GAP: .WithPgWeb() — PgWeb integration is not available in the TypeScript polyglot SDK. const todosDb = postgres.addDatabase(todosDbName); @@ -27,13 +35,12 @@ const sqlserver = await builder.addSqlServer("sqlserver") .withDataVolume() .withLifetime(ContainerLifetime.Persistent); -// POLYGLOT GAP: WithCreationScript(File.ReadAllText(initScriptPath)) — reading a file and passing its content -// via WithCreationScript is not available in the TypeScript polyglot SDK. -// In C#: var initScriptPath = Path.Join(Path.GetDirectoryName(typeof(Program).Assembly.Location), "init.sql"); -// var addressBookDb = sqlserver.AddDatabase("AddressBook").WithCreationScript(File.ReadAllText(initScriptPath)); -const addressBookDb = sqlserver.addDatabase("AddressBook"); +// Read the SQL creation script and apply it to the database +const __dirname = dirname(fileURLToPath(import.meta.url)); +const initSql = readFileSync(join(__dirname, "init.sql"), "utf-8"); +const addressBookDb = sqlserver.addDatabase("AddressBook") + .withCreationScript(initSql); -// POLYGLOT GAP: AddProject("apiservice") — generic type parameter for project reference is not available. const apiservice = builder.addProject("apiservice") .withReference(todosDb) .waitFor(todosDb) diff --git a/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts b/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts index 469d5650..d04f54aa 100644 --- a/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts +++ b/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts @@ -1,3 +1,6 @@ +// Setup: Run the following commands to add required integrations: +// aspire add sqlserver + import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; const builder = await createBuilder(); @@ -8,15 +11,12 @@ const sqlserver = await builder.addSqlServer("sqlserver") const db1 = sqlserver.addDatabase("db1"); -// POLYGLOT GAP: AddProject("migration") — generic type parameter for project reference is not available. const migrationService = builder.addProject("migration") .withReference(db1) .waitFor(db1); -// POLYGLOT GAP: AddProject("api") — generic type parameter for project reference is not available. -// POLYGLOT GAP: .WaitForCompletion(migrationService) — WaitForCompletion is not available in the TypeScript polyglot SDK; only WaitFor is available. const api = builder.addProject("api") .withReference(db1) - .waitFor(migrationService); + .waitForCompletion(migrationService); await builder.build().run(); diff --git a/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts index 0588914d..28effa7e 100644 --- a/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts +++ b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts @@ -1,34 +1,42 @@ -import { createBuilder } from "./.modules/aspire.js"; +// Setup: Run the following commands to add required integrations: +// aspire add redis +// aspire add docker + +import { createBuilder, ProbeType } from "./.modules/aspire.js"; const builder = await createBuilder(); -// POLYGLOT GAP: AddDockerComposeEnvironment("compose") — Docker Compose integration is not available in the TypeScript polyglot SDK. -// builder.AddDockerComposeEnvironment("compose"); +builder.addDockerComposeEnvironment("compose"); const cache = builder.addRedis("cache"); -// POLYGLOT GAP: AddProject("apiservice") — generic type parameter for project reference is not available. -// POLYGLOT GAP: .WithHttpProbe(ProbeType.Liveness, "/alive") — probe configuration is not available. -// POLYGLOT GAP: .WithFriendlyUrls(displayText: "API") — WithFriendlyUrls is a custom extension method not available in the TypeScript SDK. const apiService = builder.addProject("apiservice") - .withHttpHealthCheck("/health"); + .withHttpHealthCheck("/health") + .withHttpProbe(ProbeType.Liveness, "/alive"); +// POLYGLOT GAP: .WithFriendlyUrls(displayText: "API") — WithFriendlyUrls is a custom C# extension +// method defined in the AppHost project. It needs [AspireExport] to be available here. -// POLYGLOT GAP: AddProject("webfrontend") — generic type parameter for project reference is not available. -// POLYGLOT GAP: .WithHttpProbe(ProbeType.Liveness, "/alive") — probe configuration is not available. -// POLYGLOT GAP: .WithFriendlyUrls("Web Frontend") — WithFriendlyUrls is a custom extension method not available. const webFrontend = builder.addProject("webfrontend") .withReference(cache) .waitFor(cache) .withReference(apiService) .waitFor(apiService) + .withHttpProbe(ProbeType.Liveness, "/alive") .withHttpHealthCheck("/health") .withExternalHttpEndpoints(); +// POLYGLOT GAP: .WithFriendlyUrls("Web Frontend") — custom C# extension method. + +const healthChecksUI = builder.addHealthChecksUI("healthchecksui") + .withReference(apiService) + .withReference(webFrontend) + .withHttpProbe(ProbeType.Liveness, "/") + .withExternalHttpEndpoints(); +// POLYGLOT GAP: .WithFriendlyUrls("HealthChecksUI Dashboard", "http") — custom C# extension method. -// POLYGLOT GAP: AddHealthChecksUI("healthchecksui") — HealthChecks UI integration is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: .WithReference(apiService).WithReference(webFrontend) — references for health checks UI. -// POLYGLOT GAP: .WithFriendlyUrls("HealthChecksUI Dashboard", "http") — custom extension method not available. -// POLYGLOT GAP: .WithHttpProbe(ProbeType.Liveness, "/") — probe configuration is not available. -// POLYGLOT GAP: .WithExternalHttpEndpoints() — for the health checks UI resource. -// POLYGLOT GAP: .WithHostPort(7230) — conditional host port in run mode for health checks UI. +const execCtx = await builder.executionContext.get(); +const isRunMode = await execCtx.isRunMode.get(); +if (isRunMode) { + healthChecksUI.withHostPort(7230); +} await builder.build().run(); diff --git a/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts b/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts index 554e555e..5f0fbe18 100644 --- a/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts +++ b/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts @@ -1,23 +1,22 @@ +// Setup: Run the following commands to add required integrations: +// aspire add redis +// aspire add orleans + import { createBuilder } from "./.modules/aspire.js"; const builder = await createBuilder(); const redis = builder.addRedis("voting-redis"); -// POLYGLOT GAP: AddOrleans("voting-cluster") — Orleans integration is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: .WithClustering(redis) — Orleans clustering configuration is not available. -// POLYGLOT GAP: .WithGrainStorage("votes", redis) — Orleans grain storage configuration is not available. -// const orleans = builder.addOrleans("voting-cluster").withClustering(redis).withGrainStorage("votes", redis); +const orleans = builder.addOrleans("voting-cluster") + .withClustering(redis) + .withGrainStorage("votes", redis); -// POLYGLOT GAP: AddProject("voting-fe") — generic type parameter for project reference is not available. -// POLYGLOT GAP: .WithReference(orleans) — Orleans resource reference is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("https", u => u.DisplayText = "Voting App") — lambda URL customization is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("http", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly) — lambda URL customization is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("orleans-gateway", ...) — lambda URL customization is not available. -// POLYGLOT GAP: .WithUrlForEndpoint("orleans-silo", ...) — lambda URL customization is not available. const votingFe = builder.addProject("voting-fe") + .withReference(orleans) .waitFor(redis) .withReplicas(3) .withExternalHttpEndpoints(); +// POLYGLOT GAP: .WithUrlForEndpoint callbacks for display text/location are not available. await builder.build().run(); diff --git a/samples/volume-mount/VolumeMount.AppHost/apphost.ts b/samples/volume-mount/VolumeMount.AppHost/apphost.ts index 19cb1fe2..99794086 100644 --- a/samples/volume-mount/VolumeMount.AppHost/apphost.ts +++ b/samples/volume-mount/VolumeMount.AppHost/apphost.ts @@ -1,3 +1,7 @@ +// Setup: Run the following commands to add required integrations: +// aspire add sqlserver +// aspire add azure-storage + import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; const builder = await createBuilder(); @@ -8,15 +12,14 @@ const sqlserver = await builder.addSqlServer("sqlserver") const sqlDatabase = sqlserver.addDatabase("sqldb"); -// POLYGLOT GAP: AddAzureStorage("Storage").RunAsEmulator(emulator => emulator.WithDataVolume()) — Azure Storage emulator -// with callback configuration is not available in the TypeScript polyglot SDK. -// POLYGLOT GAP: .AddBlobs("BlobConnection") — Azure Blob storage integration is not available. -// const blobs = builder.addAzureStorage("Storage").runAsEmulator(...).addBlobs("BlobConnection"); +const blobs = builder.addAzureStorage("Storage") + .runAsEmulator(emulator => emulator.withDataVolume()) + .addBlobs("BlobConnection"); -// POLYGLOT GAP: AddProject("blazorweb") — generic type parameter for project reference is not available. const blazorweb = builder.addProject("blazorweb") .withReference(sqlDatabase) - .waitFor(sqlDatabase); -// POLYGLOT GAP: .withReference(blobs).waitFor(blobs) — Azure Blob resource reference cannot be added (see Azure Storage gap above). + .waitFor(sqlDatabase) + .withReference(blobs) + .waitFor(blobs); await builder.build().run(); From 8e2710f7d728cc3c677a37870b254ea75e6613e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:27:40 +0000 Subject: [PATCH 5/9] Address code review: add error handling, clarifying notes, and format fixes Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- samples/POLYGLOT_NOTES.md | 8 +++++++- .../ImageGallery.AppHost/apphost.ts | 4 ++++ .../DatabaseContainers.AppHost/apphost.ts | 11 +++++++++-- .../HealthChecksUI.AppHost/apphost.ts | 4 ++++ samples/volume-mount/VolumeMount.AppHost/apphost.ts | 3 +++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/samples/POLYGLOT_NOTES.md b/samples/POLYGLOT_NOTES.md index 25fcdc5b..f3457ca2 100644 --- a/samples/POLYGLOT_NOTES.md +++ b/samples/POLYGLOT_NOTES.md @@ -259,7 +259,13 @@ These features work after adding the appropriate integration packages: - `addAzureStorage()` / `.addBlobs()` / `.addQueues()` — `aspire add azure-storage` - `addAzureContainerAppEnvironment()` — `aspire add azure-appcontainers` - `addAzureFunctionsProject()` — `aspire add azure-functions` -- Core capabilities (always available): `addContainer()`, `addProject()`, `addDockerfile()`, `addParameter()`, `.withBindMount()`, `.withEnvironment()`, `.withArgs()`, `.withHttpEndpoint()`, `.withHttpHealthCheck()`, `.withExternalHttpEndpoints()`, `.withReference()`, `.waitFor()`, `.waitForCompletion()`, `.withReplicas()`, `.withDataVolume()`, `.withLifetime()`, `.withOtlpExporter()`, `.withBuildArg()`, `getEndpoint()`, `builder.executionContext`, `builder.build().run()` +- Core capabilities (always available): + - `addContainer()`, `addProject()`, `addDockerfile()`, `addParameter()` + - `.withBindMount()`, `.withEnvironment()`, `.withArgs()` + - `.withHttpEndpoint()`, `.withHttpHealthCheck()`, `.withExternalHttpEndpoints()` + - `.withReference()`, `.waitFor()`, `.waitForCompletion()`, `.withReplicas()` + - `.withDataVolume()`, `.withLifetime()`, `.withOtlpExporter()`, `.withBuildArg()` + - `getEndpoint()`, `builder.executionContext`, `builder.build().run()` ### Remaining Gaps ❌ These features have no polyglot equivalent regardless of packages: diff --git a/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts index 04da61e0..2bdd4049 100644 --- a/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts +++ b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts @@ -2,6 +2,10 @@ // aspire add azure-appcontainers // aspire add azure-storage // aspire add azure-functions +// +// Note: StorageBuiltInRole and withRoleAssignments are expected to be available +// after aspire add azure-storage. If StorageBuiltInRole is not exported in the +// generated SDK, the role assignment calls may need to be adjusted. import { createBuilder, StorageBuiltInRole } from "./.modules/aspire.js"; diff --git a/samples/database-containers/DatabaseContainers.AppHost/apphost.ts b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts index 99b6f9dd..23daa65d 100644 --- a/samples/database-containers/DatabaseContainers.AppHost/apphost.ts +++ b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts @@ -2,9 +2,12 @@ // aspire add postgres // aspire add mysql // aspire add sqlserver +// +// Note: This sample reads init.sql using Node.js fs APIs (the TypeScript +// equivalent of C#'s File.ReadAllText) and passes it to withCreationScript(). import { createBuilder, ContainerLifetime } from "./.modules/aspire.js"; -import { readFileSync } from "node:fs"; +import { readFileSync, existsSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; @@ -37,7 +40,11 @@ const sqlserver = await builder.addSqlServer("sqlserver") // Read the SQL creation script and apply it to the database const __dirname = dirname(fileURLToPath(import.meta.url)); -const initSql = readFileSync(join(__dirname, "init.sql"), "utf-8"); +const initSqlPath = join(__dirname, "init.sql"); +if (!existsSync(initSqlPath)) { + throw new Error(`SQL initialization script not found: ${initSqlPath}`); +} +const initSql = readFileSync(initSqlPath, "utf-8"); const addressBookDb = sqlserver.addDatabase("AddressBook") .withCreationScript(initSql); diff --git a/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts index 28effa7e..13c120d0 100644 --- a/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts +++ b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts @@ -1,6 +1,10 @@ // Setup: Run the following commands to add required integrations: // aspire add redis // aspire add docker +// +// Note: ProbeType, addDockerComposeEnvironment, addHealthChecksUI, and withHttpProbe +// are expected to be available after aspire add docker. If ProbeType is not exported +// in the generated SDK, the withHttpProbe calls may need to be removed. import { createBuilder, ProbeType } from "./.modules/aspire.js"; diff --git a/samples/volume-mount/VolumeMount.AppHost/apphost.ts b/samples/volume-mount/VolumeMount.AppHost/apphost.ts index 99794086..1b08c6c9 100644 --- a/samples/volume-mount/VolumeMount.AppHost/apphost.ts +++ b/samples/volume-mount/VolumeMount.AppHost/apphost.ts @@ -15,6 +15,9 @@ const sqlDatabase = sqlserver.addDatabase("sqldb"); const blobs = builder.addAzureStorage("Storage") .runAsEmulator(emulator => emulator.withDataVolume()) .addBlobs("BlobConnection"); +// Note: The emulator callback pattern above assumes the SDK supports arrow function +// callbacks for RunAsEmulator. If not, you may need to call runAsEmulator() without +// arguments and configure the data volume separately. const blazorweb = builder.addProject("blazorweb") .withReference(sqlDatabase) From 69126edd6e970fd998b0a186b12371e9df410310 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 20:33:47 +0000 Subject: [PATCH 6/9] Document that gap analysis is unvalidated; staging CLI required for apphost.ts The stable Aspire CLI (13.1.2 from NuGet) does not include TypeScript polyglot apphost support. The native staging binary from aspire.dev is required to detect and run apphost.ts files. Updated POLYGLOT_NOTES.md to clearly document this, mark all feasibility ratings as expected rather than confirmed, add a validation checklist, and note the container runtime prerequisite. Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- samples/POLYGLOT_NOTES.md | 95 +++++++++++++++---- .../database-migrations/.aspire/settings.json | 2 +- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/samples/POLYGLOT_NOTES.md b/samples/POLYGLOT_NOTES.md index f3457ca2..a92050a5 100644 --- a/samples/POLYGLOT_NOTES.md +++ b/samples/POLYGLOT_NOTES.md @@ -1,7 +1,14 @@ # Polyglot AppHost TypeScript Conversion Notes -This document logs all issues, gaps, limitations, and errors discovered while attempting to rewrite -each sample's `AppHost.cs` as a polyglot `apphost.ts` using the Aspire TypeScript SDK. +This document describes the conversion of each sample's `AppHost.cs` to a polyglot `apphost.ts` +using the Aspire TypeScript SDK, and documents expected gaps based on the +[Aspire Type System (ATS) spec](https://github.com/dotnet/aspire/blob/main/docs/specs/polyglot-apphost.md). + +> **⚠️ Validation Status:** These conversions have **not yet been validated** with `aspire run`. +> The gap analysis below is based on the ATS specification and the `[AspireExport]` attribute +> model. Actual API availability must be confirmed by running `aspire run` with the staging CLI, +> which generates the `.modules/aspire.js` SDK from the installed NuGet packages. Some APIs +> listed as available may not be exported yet, and some listed as gaps may already be supported. ## Overview @@ -13,17 +20,21 @@ which communicates with a .NET AppHost Server via JSON-RPC. To run the polyglot TypeScript apphosts: -1. Install the staging Aspire CLI: +1. **Install the staging Aspire CLI** (the stable NuGet CLI does not include TypeScript polyglot support): ```powershell + # Windows (PowerShell): iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" ``` - Or on Linux/macOS: ```bash + # Linux/macOS: curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging ``` + > The stable CLI (`dotnet tool install -g Aspire.Cli`) does **not** detect `apphost.ts` files. + > You must use the native staging binary from aspire.dev. 2. Node.js (v18+) or Bun must be installed -3. **Add integration packages** using `aspire add {package}` for each sample (see per-sample setup below) -4. Run `aspire run` from the sample directory containing `apphost.ts` +3. A container runtime (Docker or Podman) must be running — Aspire handles all container orchestration automatically +4. **Add integration packages** using `aspire add {package}` for each sample (see per-sample setup below) +5. Run `aspire run` from the sample directory containing `apphost.ts` ### Adding Integrations with `aspire add` @@ -46,6 +57,18 @@ The CLI will: - Regenerate the TypeScript SDK in `.modules/` with the new capabilities - Start the .NET AppHost server + Node.js/Bun guest runtime on `aspire run` +### How to Validate + +To confirm which APIs are actually available after `aspire add`, inspect the generated +`.modules/aspire.ts` file. It contains all exported builder classes, methods, enums, and DTOs. +Compare against the `apphost.ts` to identify any remaining gaps. + +```bash +cd samples// +# After aspire add and aspire run, check: +cat .modules/aspire.ts | grep -E "add(Redis|Postgres|MySql|SqlServer|Orleans|Container)" +``` + ### Skipped Sample - **standalone-dashboard**: This is a standalone console application, not an Aspire AppHost sample. It @@ -55,6 +78,10 @@ The CLI will: ## Per-Sample Setup and Gap Analysis +> **Note:** The per-sample gap analysis below is based on the ATS specification and has not been +> validated by running `aspire run` with the staging CLI. After validation with `aspire run`, +> the actual generated `.modules/aspire.ts` should be inspected to confirm which APIs are available. + Each sample requires specific `aspire add` commands to install its integration packages. Run these commands from the sample directory before using `aspire run`. @@ -244,8 +271,10 @@ aspire add azure-functions ## Cross-Cutting Issues Summary -### Features Available After `aspire add` ✅ -These features work after adding the appropriate integration packages: +### Features Available After `aspire add` (Expected) ✅ +These features are expected to work after adding the appropriate integration packages. +**This list has not been validated with `aspire run` — actual availability depends on which +C# APIs have `[AspireExport]` attributes in their NuGet packages.** - `createBuilder()` — Create the distributed application builder (core) - `addRedis("name")` — `aspire add redis` - `addPostgres("name")` / `.withPgAdmin()` / `.withPgWeb()` — `aspire add postgres` @@ -267,15 +296,19 @@ These features work after adding the appropriate integration packages: - `.withDataVolume()`, `.withLifetime()`, `.withOtlpExporter()`, `.withBuildArg()` - `getEndpoint()`, `builder.executionContext`, `builder.build().run()` -### Remaining Gaps ❌ -These features have no polyglot equivalent regardless of packages: +### Expected Remaining Gaps ❌ +These features are expected to have no polyglot equivalent regardless of packages +(based on the ATS spec — lambda callbacks and custom C# extensions cannot cross the JSON-RPC boundary): 1. **`.WithUrlForEndpoint` with lambda callback** — URL display customization (display text, display location) requires C# callbacks that can't be expressed in TypeScript. 2. **`.ConfigureInfrastructure` with lambda** — Bicep infrastructure configuration requires C# lambdas for accessing provisioning types. 3. **Custom C# extension methods** — Any extension method defined in the sample's AppHost project (e.g., `AddOpenTelemetryCollector`, `AddTalkingClock`, `WithFriendlyUrls`) requires `[AspireExport]` annotation and NuGet packaging. 4. **`.WithHttpCommand`** — Custom dashboard commands are not exposed through ATS capabilities. 5. **`.PublishAsDockerFile` / `.publishWithContainerFiles`** — Publish-time behaviors may not be available. -### Sample Conversion Feasibility Matrix (with `aspire add`) +### Sample Conversion Feasibility Matrix (Expected, with `aspire add`) + +> **Note:** These feasibility ratings are based on the ATS specification and have not been +> validated by running `aspire run`. After validation, some entries may change. | Sample | `aspire add` Commands | Feasibility | Remaining Gaps | |--------|----------------------|-------------|----------------| @@ -310,13 +343,17 @@ These features have no polyglot equivalent regardless of packages: To test any of these TypeScript apphosts: ```bash -# Install staging Aspire CLI -# On Windows: +# Install the STAGING Aspire CLI (required for TypeScript polyglot support) +# The stable CLI from NuGet (dotnet tool install -g Aspire.Cli) does NOT support apphost.ts. + +# On Windows (PowerShell): iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" # On Linux/macOS: curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging +# Ensure Docker (or Podman) is running — Aspire handles all container orchestration + # Navigate to sample directory cd samples/ @@ -331,25 +368,41 @@ aspire run ### Expected Behavior -When running `aspire run` with an `apphost.ts` present: -1. The CLI detects the TypeScript apphost -2. It scaffolds a .NET AppHost server project (if not already present) +When running `aspire run` with an `apphost.ts` present (staging CLI required): +1. The CLI detects the TypeScript apphost via its `apphost.ts` detection pattern +2. It scaffolds a .NET AppHost server project in a temp directory 3. `aspire add` installs NuGet packages and triggers SDK regeneration 4. It generates the TypeScript SDK in `.modules/` with all available capabilities -5. It starts both the .NET server and Node.js guest +5. It starts both the .NET server and Node.js guest (connected via JSON-RPC over Unix socket) 6. The Aspire dashboard shows all declared resources 7. Resources start in dependency order (via `waitFor`) +8. Containers are automatically pulled and started by the .NET AppHost server + +### Validation Checklist + +After running `aspire run` for each sample, update this section with results: + +- [ ] Verify `.modules/aspire.ts` is generated with expected builder classes +- [ ] Confirm each `aspire add` package produces the expected API methods +- [ ] Update per-sample gap analysis with actual findings +- [ ] Remove or update any `// POLYGLOT GAP:` comments that are resolved +- [ ] Note any new gaps discovered in the generated SDK ### Known Runtime Issues -1. **`.modules/` not pre-generated**: The TypeScript SDK is generated at runtime by the CLI. The +1. **Staging CLI required**: The stable Aspire CLI (`dotnet tool install -g Aspire.Cli@13.1.2`) + does **not** detect `apphost.ts` files. You must install the native staging binary from + `https://aspire.dev/install.sh` (or `.ps1`) with `--quality staging`. +2. **`.modules/` not pre-generated**: The TypeScript SDK is generated at runtime by the CLI. The `import ... from "./.modules/aspire.js"` will fail if run directly with `node` or `ts-node`. Always use `aspire run`. -2. **Must run `aspire add` first**: Integration APIs (like `addRedis`, `addPostgres`) are only +3. **Must run `aspire add` first**: Integration APIs (like `addRedis`, `addPostgres`) are only available after adding the corresponding packages with `aspire add`. Without them, the generated SDK won't include those capabilities. -3. **Project discovery**: `addProject("name")` discovers .NET projects via the Aspire CLI's +4. **Container runtime required**: Docker or Podman must be running. Aspire handles all container + orchestration automatically — no need to manually pull or start containers. +5. **Project discovery**: `addProject("name")` discovers .NET projects via the Aspire CLI's project detection. Ensure project files are in the expected directory structure. -4. **Async chaining**: The TypeScript SDK uses `Thenable` wrappers for fluent async chaining. +6. **Async chaining**: The TypeScript SDK uses `Thenable` wrappers for fluent async chaining. Single `await` at the end of a chain is the expected pattern, but complex branching (like conditional `withDataVolume`) may require intermediate `await` calls. diff --git a/samples/database-migrations/.aspire/settings.json b/samples/database-migrations/.aspire/settings.json index 6846cee4..0f241c2b 100644 --- a/samples/database-migrations/.aspire/settings.json +++ b/samples/database-migrations/.aspire/settings.json @@ -1,3 +1,3 @@ { "appHostPath": "../DatabaseMigrations.AppHost/DatabaseMigrations.AppHost.csproj" -} \ No newline at end of file +} From 66f81fef87259d3470872a1123dec5482530fc2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:04:32 +0000 Subject: [PATCH 7/9] Document staging CLI requirement and sandbox limitation Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- .aspire/settings.json | 3 +++ nuget.config | 1 + 2 files changed, 4 insertions(+) create mode 100644 .aspire/settings.json diff --git a/.aspire/settings.json b/.aspire/settings.json new file mode 100644 index 00000000..b44b9596 --- /dev/null +++ b/.aspire/settings.json @@ -0,0 +1,3 @@ +{ + "polyglot": "true" +} \ No newline at end of file diff --git a/nuget.config b/nuget.config index 248a5bb5..6e3f1bc7 100644 --- a/nuget.config +++ b/nuget.config @@ -3,5 +3,6 @@ + \ No newline at end of file From 4718ce1fc90ac9efae40545af752aeb7fc1c3d30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:05:11 +0000 Subject: [PATCH 8/9] Remove accidentally committed .aspire/settings.json; restore nuget.config Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- .aspire/settings.json | 3 --- nuget.config | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .aspire/settings.json diff --git a/.aspire/settings.json b/.aspire/settings.json deleted file mode 100644 index b44b9596..00000000 --- a/.aspire/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "polyglot": "true" -} \ No newline at end of file diff --git a/nuget.config b/nuget.config index 6e3f1bc7..248a5bb5 100644 --- a/nuget.config +++ b/nuget.config @@ -3,6 +3,5 @@ - \ No newline at end of file From 91cd4a2aa5bdd2c63c0a7486ab760cae148446d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:06:24 +0000 Subject: [PATCH 9/9] Fix CLI install command to use correct staging syntax: -q staging Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- samples/POLYGLOT_NOTES.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/samples/POLYGLOT_NOTES.md b/samples/POLYGLOT_NOTES.md index a92050a5..4135b038 100644 --- a/samples/POLYGLOT_NOTES.md +++ b/samples/POLYGLOT_NOTES.md @@ -21,16 +21,17 @@ which communicates with a .NET AppHost Server via JSON-RPC. To run the polyglot TypeScript apphosts: 1. **Install the staging Aspire CLI** (the stable NuGet CLI does not include TypeScript polyglot support): + ```bash + # Linux/macOS: + curl -sSL https://aspire.dev/install.sh | bash -s -- -q staging + ``` ```powershell # Windows (PowerShell): iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" ``` - ```bash - # Linux/macOS: - curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging - ``` > The stable CLI (`dotnet tool install -g Aspire.Cli`) does **not** detect `apphost.ts` files. > You must use the native staging binary from aspire.dev. + > See [Polyglot AppHost docs](https://aspiredev.netlify.app/app-host/polyglot-apphost/) for details. 2. Node.js (v18+) or Bun must be installed 3. A container runtime (Docker or Podman) must be running — Aspire handles all container orchestration automatically 4. **Add integration packages** using `aspire add {package}` for each sample (see per-sample setup below) @@ -346,12 +347,12 @@ To test any of these TypeScript apphosts: # Install the STAGING Aspire CLI (required for TypeScript polyglot support) # The stable CLI from NuGet (dotnet tool install -g Aspire.Cli) does NOT support apphost.ts. +# On Linux/macOS: +curl -sSL https://aspire.dev/install.sh | bash -s -- -q staging + # On Windows (PowerShell): iex "& { $(irm https://aspire.dev/install.ps1) } -Quality staging" -# On Linux/macOS: -curl -fsSL https://aspire.dev/install.sh | bash -s -- --quality staging - # Ensure Docker (or Podman) is running — Aspire handles all container orchestration # Navigate to sample directory @@ -391,8 +392,8 @@ After running `aspire run` for each sample, update this section with results: ### Known Runtime Issues 1. **Staging CLI required**: The stable Aspire CLI (`dotnet tool install -g Aspire.Cli@13.1.2`) - does **not** detect `apphost.ts` files. You must install the native staging binary from - `https://aspire.dev/install.sh` (or `.ps1`) with `--quality staging`. + does **not** detect `apphost.ts` files. You must install the native staging binary: + `curl -sSL https://aspire.dev/install.sh | bash -s -- -q staging` 2. **`.modules/` not pre-generated**: The TypeScript SDK is generated at runtime by the CLI. The `import ... from "./.modules/aspire.js"` will fail if run directly with `node` or `ts-node`. Always use `aspire run`.