diff --git a/samples/Metrics/MetricsApp.AppHost/apphost.ts b/samples/Metrics/MetricsApp.AppHost/apphost.ts new file mode 100644 index 00000000..905651c8 --- /dev/null +++ b/samples/Metrics/MetricsApp.AppHost/apphost.ts @@ -0,0 +1,33 @@ +// 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(); + +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. + +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. + +// 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: .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 new file mode 100644 index 00000000..4135b038 --- /dev/null +++ b/samples/POLYGLOT_NOTES.md @@ -0,0 +1,409 @@ +# Polyglot AppHost TypeScript Conversion Notes + +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 + +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** (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" + ``` + > 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) +5. 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 (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` + +### 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 + configures OpenTelemetry directly and does not use `DistributedApplication.CreateBuilder()`. + +--- + +## 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`. + +### 1. Metrics (`samples/Metrics/MetricsApp.AppHost/apphost.ts`) + +**Setup:** No additional packages required (uses core container and project APIs). + +**Convertible:** Partially +**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 +**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`) + +**Setup:** +```bash +aspire add javascript +``` + +**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`) + +**Setup:** +```bash +aspire add javascript +aspire add redis +``` + +**Convertible:** Fully (after `aspire add javascript` + `aspire add redis`) +**Remaining Gaps:** None expected. + +--- + +### 5. aspire-with-python (`samples/aspire-with-python/apphost.ts`) + +**Setup:** +```bash +aspire add javascript +aspire add python +aspire add redis +``` + +**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`) + +**Setup:** No additional packages required. + +**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`) + +**Setup:** No additional packages required (uses core Dockerfile and parameter APIs). + +**Convertible:** Mostly +**Remaining Gaps:** +- `.withDeveloperCertificateTrust(true)` — Developer certificate trust may not be available. + +--- + +### 8. custom-resources (`samples/custom-resources/CustomResources.AppHost/apphost.ts`) + +**Setup:** N/A — This sample uses custom C# resource extensions (`AddTalkingClock`, `AddTestResource`). + +**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`) + +**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`) + +**Setup:** +```bash +aspire add sqlserver +``` + +**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`) + +**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`) + +**Setup:** +```bash +aspire add redis +aspire add orleans +``` + +**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`) + +**Setup:** +```bash +aspire add sqlserver +aspire add azure-storage +``` + +**Convertible:** Fully (after adding packages) +**Remaining Gaps:** None expected. + +--- + +### 14. aspire-with-azure-functions (`samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts`) + +**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 + +### 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` +- `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()` + +### 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 (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 | +|--------|----------------------|-------------|----------------| +| 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. + +--- + +## Environment and Testing Notes + +### Running Polyglot AppHosts + +To test any of these TypeScript apphosts: + +```bash +# 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" + +# Ensure Docker (or Podman) is running — Aspire handles all container orchestration + +# 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 +``` + +### Expected Behavior + +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 (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. **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: + `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`. +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. +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. +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/aspire-shop/AspireShop.AppHost/apphost.ts b/samples/aspire-shop/AspireShop.AppHost/apphost.ts new file mode 100644 index 00000000..b33268e0 --- /dev/null +++ b/samples/aspire-shop/AspireShop.AppHost/apphost.ts @@ -0,0 +1,48 @@ +// 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(); + +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: .WithHttpCommand("/reset-db", "Reset Database", ...) — custom HTTP commands are not available. + +const catalogService = builder.addProject("catalogservice") + .withReference(catalogDb) + .waitFor(catalogDbManager) + .withHttpHealthCheck("/health"); + +const basketService = builder.addProject("basketservice") + .withReference(basketCache) + .waitFor(basketCache); + +const frontend = builder.addProject("frontend") + .withExternalHttpEndpoints() + .withHttpHealthCheck("/health") + .withReference(basketService) + .withReference(catalogService) + .waitFor(catalogService); +// 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 new file mode 100644 index 00000000..2bdd4049 --- /dev/null +++ b/samples/aspire-with-azure-functions/ImageGallery.AppHost/apphost.ts @@ -0,0 +1,43 @@ +// Setup: Run the following commands to add required integrations: +// 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"; + +const builder = await createBuilder(); + +builder.addAzureContainerAppEnvironment("env"); + +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. + +const blobs = storage.addBlobs("blobs"); +const queues = storage.addQueues("queues"); + +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. + +const frontend = builder.addProject("frontend") + .withReference(queues) + .withReference(blobs) + .waitFor(functions) + .withExternalHttpEndpoints(); +// 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 new file mode 100644 index 00000000..adb0d1fc --- /dev/null +++ b/samples/aspire-with-javascript/AspireJavaScript.AppHost/apphost.ts @@ -0,0 +1,42 @@ +// Setup: Run the following commands to add required integrations: +// aspire add javascript + +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const weatherApi = builder.addProject("weatherapi") + .withExternalHttpEndpoints(); + +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. + +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. + +const reactVite = builder.addViteApp("reactvite", "../AspireJavaScript.Vite") + .withReference(weatherApi) + .withEnvironment("BROWSER", "none"); + +// 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 new file mode 100644 index 00000000..65eaa20f --- /dev/null +++ b/samples/aspire-with-node/AspireWithNode.AppHost/apphost.ts @@ -0,0 +1,26 @@ +// 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(); + +const cache = await builder.addRedis("cache") + .withRedisInsight(); + +const weatherapi = builder.addProject("weatherapi") + .withHttpHealthCheck("/health"); + +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 new file mode 100644 index 00000000..039b0a5e --- /dev/null +++ b/samples/aspire-with-python/apphost.ts @@ -0,0 +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"); + +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: 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 new file mode 100644 index 00000000..baa54451 --- /dev/null +++ b/samples/client-apps-integration/ClientAppsIntegration.AppHost/apphost.ts @@ -0,0 +1,25 @@ +// Setup: No additional packages required (uses core project APIs). + +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const apiService = builder.addProject("apiservice"); + +// 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 new file mode 100644 index 00000000..c6a2ee1d --- /dev/null +++ b/samples/container-build/apphost.ts @@ -0,0 +1,36 @@ +// Setup: No additional packages required (uses core container and Dockerfile APIs). + +import { createBuilder } from "./.modules/aspire.js"; + +const builder = await createBuilder(); + +const goVersion = builder.addParameter("goversion", { default: "1.25.4" }); + +const execCtx = await builder.executionContext.get(); +const isRunMode = await execCtx.isRunMode.get(); +const isPublishMode = !isRunMode; + +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 new file mode 100644 index 00000000..ba5f749a --- /dev/null +++ b/samples/custom-resources/CustomResources.AppHost/apphost.ts @@ -0,0 +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(); + +// 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 new file mode 100644 index 00000000..23daa65d --- /dev/null +++ b/samples/database-containers/DatabaseContainers.AppHost/apphost.ts @@ -0,0 +1,61 @@ +// Setup: Run the following commands to add required integrations: +// 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, existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +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() + .withPgWeb() + .withLifetime(ContainerLifetime.Persistent); + +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); + +// Read the SQL creation script and apply it to the database +const __dirname = dirname(fileURLToPath(import.meta.url)); +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); + +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/.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 +} diff --git a/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts b/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts new file mode 100644 index 00000000..d04f54aa --- /dev/null +++ b/samples/database-migrations/DatabaseMigrations.AppHost/apphost.ts @@ -0,0 +1,22 @@ +// Setup: Run the following commands to add required integrations: +// aspire add sqlserver + +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"); + +const migrationService = builder.addProject("migration") + .withReference(db1) + .waitFor(db1); + +const api = builder.addProject("api") + .withReference(db1) + .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 new file mode 100644 index 00000000..13c120d0 --- /dev/null +++ b/samples/health-checks-ui/HealthChecksUI.AppHost/apphost.ts @@ -0,0 +1,46 @@ +// 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"; + +const builder = await createBuilder(); + +builder.addDockerComposeEnvironment("compose"); + +const cache = builder.addRedis("cache"); + +const apiService = builder.addProject("apiservice") + .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. + +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. + +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 new file mode 100644 index 00000000..5f0fbe18 --- /dev/null +++ b/samples/orleans-voting/OrleansVoting.AppHost/apphost.ts @@ -0,0 +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"); + +const orleans = builder.addOrleans("voting-cluster") + .withClustering(redis) + .withGrainStorage("votes", redis); + +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 new file mode 100644 index 00000000..1b08c6c9 --- /dev/null +++ b/samples/volume-mount/VolumeMount.AppHost/apphost.ts @@ -0,0 +1,28 @@ +// 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(); + +const sqlserver = await builder.addSqlServer("sqlserver") + .withDataVolume() + .withLifetime(ContainerLifetime.Persistent); + +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) + .waitFor(sqlDatabase) + .withReference(blobs) + .waitFor(blobs); + +await builder.build().run();