From f96e3fe38dbc12d4aca463ee072267a68733c44a Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:50:40 -0500 Subject: [PATCH 01/14] docs: add integration AppHost language parity Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../docs/integrations/compute/docker.mdx | 462 +++- .../content/docs/integrations/compute/k3s.mdx | 63 +- .../docs/integrations/compute/kubernetes.mdx | 340 ++- .../client-integrations.mdx | 13 + .../hosting-integrations.mdx | 115 +- .../secure-communication.mdx | 7 +- .../integrations/devtools/browser-logs.mdx | 205 ++ .../integrations/devtools/dab/dab-host.mdx | 35 +- .../integrations/devtools/dev-tunnels.mdx | 267 ++- .../devtools/flagd/flagd-connect.mdx | 7 +- .../devtools/flagd/flagd-host.mdx | 57 +- .../integrations/devtools/goff/goff-host.mdx | 35 +- .../devtools/k6/k6-get-started.mdx | 7 +- .../docs/integrations/devtools/k6/k6-host.mdx | 35 +- .../devtools/mailpit/mailpit-host.mdx | 28 +- .../sql-projects/sql-projects-get-started.mdx | 7 +- .../sql-projects/sql-projects-host.mdx | 35 +- .../integrations/dotnet/blazor-connect.mdx | 86 +- .../integrations/dotnet/blazor-hosting.mdx | 267 ++- .../dotnet/csharp-file-based-apps.mdx | 231 +- .../dotnet/dotnet-tool-resources.mdx | 705 +++++- .../integrations/dotnet/launch-profiles.mdx | 150 ++ .../content/docs/integrations/dotnet/maui.mdx | 25 + .../integrations/dotnet/project-resources.mdx | 759 +++++- .../docs/integrations/frameworks/bun-apps.mdx | 260 +++ .../frameworks/dapr/dapr-host.mdx | 42 +- .../frameworks/deno/deno-get-started.mdx | 7 +- .../frameworks/deno/deno-host.mdx | 28 +- .../frameworks/dotnet/dotnet-host.mdx | 185 +- .../frameworks/go/go-get-started.mdx | 2 +- .../integrations/frameworks/go/go-host.mdx | 617 ++++- .../frameworks/java/java-get-started.mdx | 7 +- .../frameworks/java/java-host.mdx | 49 +- .../integrations/frameworks/javascript.mdx | 2077 ++++++++++++++++- .../frameworks/nodejs-extensions.mdx | 21 +- .../docs/integrations/frameworks/orleans.mdx | 428 +++- .../frameworks/perl/perl-get-started.mdx | 7 +- .../frameworks/perl/perl-host.mdx | 56 +- .../powershell/powershell-get-started.mdx | 7 +- .../frameworks/powershell/powershell-host.mdx | 35 +- .../docs/integrations/frameworks/python.mdx | 1025 +++++++- .../frameworks/rust/rust-get-started.mdx | 7 +- .../frameworks/rust/rust-host.mdx | 35 +- .../integrations/frameworks/wpf-winforms.mdx | 183 +- ...ion-apphost-language-parity.vitest.test.ts | 108 + 45 files changed, 8866 insertions(+), 261 deletions(-) create mode 100644 src/frontend/tests/unit/integration-apphost-language-parity.vitest.test.ts diff --git a/src/frontend/src/content/docs/integrations/compute/docker.mdx b/src/frontend/src/content/docs/integrations/compute/docker.mdx index 5bd8af141..cf857fd80 100644 --- a/src/frontend/src/content/docs/integrations/compute/docker.mdx +++ b/src/frontend/src/content/docs/integrations/compute/docker.mdx @@ -66,6 +66,34 @@ This updates your `aspire.config.json` with the Docker hosting integration packa } ``` + + + +```bash title="Terminal" +aspire add docker +``` + + + + +```bash title="Terminal" +aspire add docker +``` + + + + +```bash title="Terminal" +aspire add docker +``` + + + + +```bash title="Terminal" +aspire add docker +``` + @@ -73,7 +101,12 @@ This updates your `aspire.config.json` with the Docker hosting integration packa The following example demonstrates how to add a Docker Compose environment to your app model: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -147,10 +180,13 @@ When a Docker Compose environment is present, all resources are automatically pu ### Add Docker Compose environment resource with properties You can configure various properties of the Docker Compose environment using the `WithProperties` method: - + ```csharp title="AppHost.cs" builder.AddDockerComposeEnvironment("compose") @@ -206,7 +316,12 @@ The `DashboardEnabled` property determines whether to include an Aspire dashboar You can customize the generated Docker Compose file using the `ConfigureComposeFile` method: - + ```csharp title="AppHost.cs" builder.AddDockerComposeEnvironment("compose") @@ -244,7 +359,12 @@ The `ConfigureComposeFile` callback runs after Aspire generates the Docker Compo The Docker hosting integration includes an Aspire dashboard for telemetry visualization. You can configure or disable it using the `WithDashboard` method: - + ```csharp title="AppHost.cs" // Enable dashboard with custom configuration @@ -313,6 +433,108 @@ await api.withHttpEndpoint({ name: "http", targetPort: 80 }); const apiEndpoint = await api.getEndpoint("http"); const apiHost = await compose.getHostAddressExpression(apiEndpoint); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + compose = builder.add_docker_compose_env("compose") + api = builder.add_container("api", "nginx:alpine") + api.with_http_endpoint(name="http", target_port=80) + api_endpoint = api.get_endpoint("http") + api_host = compose.get_host_address_expression(api_endpoint) + builder.run() +```` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + stringPtr := func(value string) *string { return &value } + + targetPort := 80.0 + compose := builder.AddDockerComposeEnvironment("compose") + api := builder.AddContainer("api", "nginx:alpine").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Name: stringPtr("http"), TargetPort: &targetPort}) + apiEndpoint := api.GetEndpoint("http") + _ = compose.GetHostAddressExpression(apiEndpoint) + + if err := compose.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var compose = builder.addDockerComposeEnvironment("compose"); + var api = builder.addContainer("api", "nginx:alpine") + .withHttpEndpoint(new WithHttpEndpointOptions().name("http").targetPort(80)); + var apiHost = compose.getHostAddressExpression(api.getEndpoint("http")); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::json; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let compose = builder.add_docker_compose_environment("compose")?; + let api = builder.add_container("api", json!("nginx:alpine"))?; + api.with_http_endpoint(None, Some(80.0), Some("http"), None, None)?; + let api_endpoint = api.get_endpoint("http")?; + let _api_host = compose.get_host_address_expression(&api_endpoint)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -328,7 +550,12 @@ The Docker hosting integration captures environment variables from your app mode For advanced scenarios, use `ConfigureEnvFile` to customize the generated `.env` file: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -371,7 +598,12 @@ This is useful when you need to add custom environment variables to the generate To customize the generated Docker Compose service for a specific resource, use the `PublishAsDockerComposeService` method. This is optional — all resources are automatically included in the Docker Compose output. Use this method only when you need to modify the generated service definition: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -428,7 +660,12 @@ Use `AddDockerfileBuilder` to create a container resource from a Dockerfile gene [`ASPIREDOCKERFILEBUILDER001`](/diagnostics/aspiredockerfilebuilder001/) when you choose to use them. - + ```csharp title="AppHost.cs" using Aspire.Hosting.ApplicationModel.Docker; @@ -522,6 +759,90 @@ await container.withImagePullPolicy(ImagePullPolicy.Always); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + container = builder.add_container("mycontainer", "myimage:latest") + container.with_image_pull_policy("Always") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + container := builder.AddContainer("mycontainer", "myimage:latest").WithImagePullPolicy(aspire.ImagePullPolicyAlways) + + if err := container.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var container = builder.addContainer("mycontainer", "myimage:latest") + .withImagePullPolicy(ImagePullPolicy.ALWAYS); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::json; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let container = builder.add_container("mycontainer", json!("myimage:latest"))?; + let _container = container.with_image_pull_policy(ImagePullPolicy::Always)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -549,6 +870,90 @@ await app.withImagePullPolicy(ImagePullPolicy.Never); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + app = builder.add_container("myapp", "my-local-image:dev") + app.with_image_pull_policy("Never") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + appResource := builder.AddContainer("myapp", "my-local-image:dev").WithImagePullPolicy(aspire.ImagePullPolicyNever) + + if err := appResource.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var app = builder.addContainer("myapp", "my-local-image:dev") + .withImagePullPolicy(ImagePullPolicy.NEVER); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::json; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let app = builder.add_container("myapp", json!("my-local-image:dev"))?; + let _app = app.with_image_pull_policy(ImagePullPolicy::Never)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -579,7 +984,12 @@ When deploying containers, you can customize how container images are named and Use `WithRemoteImageName` and `WithRemoteImageTag` to customize the image reference: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -614,7 +1024,12 @@ await api.withRemoteImageTag("v1.0.0"); For more complex scenarios, use `WithImagePushOptions` to register a callback that can dynamically configure push options: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -660,7 +1075,12 @@ await api For asynchronous operations (such as retrieving configuration from external sources), use the async overload: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -721,7 +1141,12 @@ You can configure your Aspire application to push container images to registries Use the `AddContainerRegistry` method to define a container registry and the `WithContainerRegistry` method to associate resources with that registry: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -770,7 +1195,12 @@ For GitHub Container Registry, the registry endpoint is `ghcr.io` and the reposi For more flexible configuration, especially in CI/CD pipelines, you can use parameters with environment variables: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); diff --git a/src/frontend/src/content/docs/integrations/compute/k3s.mdx b/src/frontend/src/content/docs/integrations/compute/k3s.mdx index c23e7adbb..242f77adc 100644 --- a/src/frontend/src/content/docs/integrations/compute/k3s.mdx +++ b/src/frontend/src/content/docs/integrations/compute/k3s.mdx @@ -51,7 +51,12 @@ If this command fails with a permission error, you may need to enable privileged To start building an Aspire app that uses k3s, install the [📦 CommunityToolkit.Aspire.Hosting.K3s](https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.K3s) NuGet package: - + ```bash title="Terminal" @@ -119,7 +124,12 @@ The examples below show how to use the k3s integration APIs. They assume you alr Then adapt the snippets below to your project structure and configuration. ::: - + ```csharp title="AppHost.cs" @@ -158,7 +168,12 @@ await builder.build().run(); All cluster options are available as fluent builder methods: - + ```csharp title="AppHost.cs" @@ -219,7 +234,12 @@ The available options are: ## Persist cluster state across runs - + ```csharp title="AppHost.cs" @@ -250,7 +270,12 @@ With persistent containers, `agentCount` must stay constant across runs. Decreas `AddHelmRelease` runs `helm upgrade --install --wait` inside an `alpine/helm` container — no host-side `helm` binary is required: - + ```csharp title="AppHost.cs" @@ -305,7 +330,12 @@ Use `WithHelmValuesFile` for structured overrides (values with commas, braces, o | Directory (no `kustomization.yaml`) | `kubectl apply -f ` (all YAML files, lexicographic order) | | Directory containing `kustomization.yaml` | `kubectl apply -k ` (Kustomize) | - + ```csharp title="AppHost.cs" @@ -340,7 +370,12 @@ await monitoring.waitForCompletion(appConfig); `AddServiceEndpoint` starts an in-process WebSocket port-forward bound to `0.0.0.0:{allocatedPort}` — no `NodePort` or `LoadBalancer` configuration is required. The endpoint transitions to `Running` only after the target service has a ready pod. - + ```csharp title="AppHost.cs" @@ -391,7 +426,12 @@ Scheme is inferred from the port: `443` and `8443` resolve to `https`, all other Both `K3sClusterResource` and `K3sServiceEndpointResource` implement `IResourceWithConnectionString`, so the standard `WithReference` overload handles credential injection automatically: - + ```csharp title="AppHost.cs" @@ -431,7 +471,12 @@ await api.withReference(podinfoWeb); k3s pods run on the internal pod network (`10.42.0.0/16`). Flannel masquerades outbound pod traffic through the k3s container's DCP network IP, so pods can reach DCP services using `host.docker.internal` and the host-mapped port: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/compute/kubernetes.mdx b/src/frontend/src/content/docs/integrations/compute/kubernetes.mdx index 4fe5b1b88..aa5deffa7 100644 --- a/src/frontend/src/content/docs/integrations/compute/kubernetes.mdx +++ b/src/frontend/src/content/docs/integrations/compute/kubernetes.mdx @@ -72,6 +72,34 @@ This updates your `aspire.config.json` with the Kubernetes hosting integration p } ``` + + + +```bash title="Terminal" +aspire add kubernetes +``` + + + + +```bash title="Terminal" +aspire add kubernetes +``` + + + + +```bash title="Terminal" +aspire add kubernetes +``` + + + + +```bash title="Terminal" +aspire add kubernetes +``` + @@ -110,16 +138,113 @@ const api = await builder await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + k8s = builder.add_kubernetes_env("k8s") + api = builder.add_node_app("api", "./api", "src/index.ts") + api.with_http_endpoint(env="PORT").with_external_http_endpoints() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + envName := "PORT" + k8s := builder.AddKubernetesEnvironment("k8s") + api := builder.AddNodeApp("api", "./api", "src/index.ts").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Env: &envName}).WithExternalHttpEndpoints() + + if err := k8s.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var k8s = builder.addKubernetesEnvironment("k8s"); + var api = builder.addNodeApp("api", "./api", "src/index.ts") + .withHttpEndpoint(new WithHttpEndpointOptions().env("PORT")) + .withExternalHttpEndpoints(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _k8s = builder.add_kubernetes_environment("k8s")?; + let api = builder.add_node_app("api", "./api", "src/index.ts")?; + api.with_http_endpoint(None, None, None, Some("PORT"), None)?; + api.with_external_http_endpoints()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + ## Configure Helm chart options You can configure the generated Helm chart using the `WithHelm` method, which provides a fluent `HelmChartOptions` builder: - + ```csharp title="AppHost.cs" @@ -191,7 +390,9 @@ The `WithHelm` method is the single configuration entry point for Helm chart set A vanilla Kubernetes cluster doesn't know which container registry to use for your application images. Use `AddContainerRegistry` and `WithContainerRegistry` to specify a registry that is accessible from both your local machine and from the cluster: - + ```csharp title="AppHost.cs" @@ -208,6 +409,77 @@ const k8s = await builder.addKubernetesEnvironment('k8s'); await k8s.withContainerRegistry(registry); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + registry = builder.add_container_registry("registry", "myregistry.example.com:5000") + k8s = builder.add_kubernetes_env("k8s") + k8s.with_container_registry(registry) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + registry := builder.AddContainerRegistry("registry", "myregistry.example.com:5000") + k8s := builder.AddKubernetesEnvironment("k8s").WithContainerRegistry(registry) + + if err := registry.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := k8s.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var registry = builder.addContainerRegistry( + "registry", + AspireUnion.of("myregistry.example.com:5000")); + var k8s = builder.addKubernetesEnvironment("k8s") + .withContainerRegistry(registry); + + builder.build().run(); +} +``` + @@ -219,7 +491,10 @@ await k8s.withContainerRegistry(registry); To create durable storage in a Kubernetes cluster, model it as a first-class `KubernetesPersistentVolumeResource` with `AddPersistentVolume`. Then configure its storage class, capacity, and access modes, and bind it to workloads. At publish time, the volume renders as a `v1.PersistentVolumeClaim`, and any workload bound to it is promoted to a `StatefulSet`. - + ```csharp title="AppHost.cs" @@ -251,6 +526,43 @@ await pg.withDataVolume({ name: 'pg-data' }); await pg.withKubernetesPersistentVolume(pgData); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + k8s = builder.add_kubernetes_env("k8s") + pg_data = k8s.add_persistent_volume("pg-data") + pg_data.with_storage_class("managed-csi").with_capacity("20Gi") + pg = builder.add_postgres("pg") + pg.with_data_volume(name="pg-data") + pg.with_kubernetes_persistent_volume(pg_data) + builder.run() +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var k8s = builder.addKubernetesEnvironment("k8s"); + var pgData = k8s.addPersistentVolume("pg-data") + .withStorageClass("managed-csi") + .withCapacity("20Gi"); + var pg = builder.addPostgres("pg") + .withDataVolume(new WithDataVolumeOptions().name("pg-data")) + .withKubernetesPersistentVolume(pgData); + + builder.build().run(); +} +``` + @@ -262,7 +574,12 @@ await pg.withKubernetesPersistentVolume(pgData); Use `PublishAsKubernetesService` to modify the generated Kubernetes resources for individual services: - + ```csharp title="AppHost.cs" @@ -302,7 +619,12 @@ The API surface differs by language: - **C#**: add subclasses of `BaseKubernetesResource` directly to the `AdditionalResources` collection. Several built-in types (such as `ConfigMap`) are available in the `Aspire.Hosting.Kubernetes.Resources` namespace, and you can subclass `BaseKubernetesResource` to model any custom resource definition (CRD). - **TypeScript**: call `addManifest` on the resource. It takes the manifest's `apiVersion`, `kind`, and `metadata.name`, and returns a handle for setting labels, annotations, a namespace, and arbitrary field values using dot-notation paths. - + Define a class for the custom resource by deriving from `BaseKubernetesResource`, then add an instance to `AdditionalResources`. The example below models a cert-manager `Certificate` CRD: diff --git a/src/frontend/src/content/docs/integrations/custom-integrations/client-integrations.mdx b/src/frontend/src/content/docs/integrations/custom-integrations/client-integrations.mdx index 7212e4972..494a384b9 100644 --- a/src/frontend/src/content/docs/integrations/custom-integrations/client-integrations.mdx +++ b/src/frontend/src/content/docs/integrations/custom-integrations/client-integrations.mdx @@ -4,6 +4,7 @@ description: Learn how to create a custom Aspire client integration for an exist --- import { Aside, Badge, Steps } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; import { Kbd } from 'starlight-kbd/components'; import ThemeImage from '@components/ThemeImage.astro'; import maildevWithNewsletterDashboard from '@assets/integrations/custom-integrations/maildev-with-newsletterservice-dashboard.png'; @@ -564,6 +565,15 @@ The most notable changes in the preceding code are: Back in the `AppHost.cs` file, update it to configure the scalar API reference endpoint: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -580,6 +590,9 @@ builder.AddProject("newsletterservic builder.Build().Run(); ``` + + + ## Run the sample Now that you've created the MailKit client integration and updated the Newsletter service to use it, you can run the sample. From your IDE, select or run [`aspire run`](/reference/cli/commands/aspire-run/) from the root directory of the solution to start the application—you should see the [Aspire dashboard](/dashboard/overview/): diff --git a/src/frontend/src/content/docs/integrations/custom-integrations/hosting-integrations.mdx b/src/frontend/src/content/docs/integrations/custom-integrations/hosting-integrations.mdx index e48cb4c80..3a1f9cbc2 100644 --- a/src/frontend/src/content/docs/integrations/custom-integrations/hosting-integrations.mdx +++ b/src/frontend/src/content/docs/integrations/custom-integrations/hosting-integrations.mdx @@ -26,6 +26,12 @@ Aspire improves the development experience by providing reusable building blocks Consider the code below: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -39,6 +45,65 @@ builder.AddProject("inventoryservice") .WithReference(db); ``` + + + +```typescript title="apphost.mts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const redis = await builder.addRedis('cache'); +const postgres = await builder.addPostgres('pgserver'); +const db = await postgres.addDatabase('inventorydb'); + +const inventory = await builder.addProject( + 'inventoryservice', + '../InventoryService/InventoryService.csproj' +); +await inventory.withReference(redis); +await inventory.withReference(db); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + redis = builder.add_redis("cache") + postgres = builder.add_postgres("pgserver") + db = postgres.add_database("inventorydb") + inventory = builder.add_project( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + ) + inventory.with_reference(redis).with_reference(db) +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var redis = builder.addRedis("cache"); + var db = builder.addPostgres("pgserver").addDatabase("inventorydb"); + var inventory = builder.addProject( + "inventoryservice", + "../InventoryService/InventoryService.csproj") + .withReference(redis) + .withReference(db); +} +``` + + + + In the preceding code there are four resources represented: @@ -184,6 +249,15 @@ This is because Aspire treats project references in the AppHost as if they're se The `MailDev.Hosting` class library contains the resource type and extension methods for adding the resource to the AppHost. You should first think about the experience that you want to give developers when using your custom resource. In the case of this custom resource, you would want developers to be able to write code like the following: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -193,6 +267,9 @@ builder.AddProject("newsletterservice") .WithReference(maildev); ``` + + + To achieve this, you need a custom resource named `MailDevResource` which implements `IResourceWithConnectionString` so that consumers can use it with the `WithReference` extension to inject the connection details for the MailDev server as a connection string. MailDev is available as a container resource, so you'll also want to derive from `ContainerResource` so that we can make use of various pre-existing container-focused extensions in Aspire. @@ -311,7 +388,12 @@ This example uses MailDev version 2.2.1. Be sure to check the [MailDev Docker Hu Now that the basic structure for the custom resource is complete, test it in an AppHost. The `[AspireExport]` attributes make the same C# implementation available to both AppHost languages. - + Open the `AppHost.cs` file in the `MailDevResource.AppHost` project and update it with the following code: @@ -414,6 +496,14 @@ In order to test the end-to-end scenario, you need a .NET project which we can i After the project has been added and references have been updated, open the `AppHost.cs` of the `MailDevResource.AppHost.csproj` project, and update the source file to look like the following: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -425,6 +515,29 @@ builder.AddProject("newsletterservic builder.Build().Run(); ``` + + + +After referencing the local integration from `aspire.config.json`, run `aspire restore` to generate its TypeScript API: + +```typescript title="apphost.mts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const maildev = await builder.addMailDev('maildev'); +const newsletter = await builder.addProject( + 'newsletterservice', + '../MailDevResource.NewsletterService/MailDevResource.NewsletterService.csproj' +); +await newsletter.withReference(maildev); + +await builder.build().run(); +``` + + + + After updating the `AppHost.cs` file, launch the AppHost again. Then verify that the Newsletter Service started and that the environment variable `ConnectionStrings__maildev` was added to the process. From the **Resources** page, find the `newsletterservice` row, and select the **...** button on the **Actions** column, then select **View details**. In the **Environment Variables** section, you should see the following: diff --git a/src/frontend/src/content/docs/integrations/custom-integrations/secure-communication.mdx b/src/frontend/src/content/docs/integrations/custom-integrations/secure-communication.mdx index 1674de3c2..a2dcf6093 100644 --- a/src/frontend/src/content/docs/integrations/custom-integrations/secure-communication.mdx +++ b/src/frontend/src/content/docs/integrations/custom-integrations/secure-communication.mdx @@ -186,7 +186,12 @@ The preceding code updates the `AddMailDev` extension method to include the `use Now that the resource includes username and password parameters, update the AppHost to provide them. Mark the password parameter as secret so tooling and deployment environments can handle it appropriately. - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx b/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx index 73b27f809..b5049f84d 100644 --- a/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/browser-logs.mdx @@ -77,6 +77,34 @@ This updates your `aspire.config.json` with the browser logs hosting integration } ``` + + + +```bash title="Terminal" +aspire add browsers +``` + + + + +```bash title="Terminal" +aspire add browsers +``` + + + + +```bash title="Terminal" +aspire add browsers +``` + + + + +```bash title="Terminal" +aspire add browsers +``` + @@ -110,6 +138,89 @@ builder.addViteApp("web", "../web") await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + web = builder.add_vite_app("web", "../web") + web.with_browser_logs() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + web := builder.AddViteApp("web", "../web").WithBrowserLogs() + + if err := web.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var web = builder.addViteApp("web", "../web") + .withBrowserLogs(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let web = builder.add_vite_app("web", "../web", None)?; + let _web = web.with_browser_logs(None, None, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -146,6 +257,100 @@ builder.addViteApp("web", "../web") userDataMode: BrowserUserDataMode.Isolated, }); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + web = builder.add_vite_app("web", "../web") + web.with_browser_logs( + browser="msedge", + user_data_mode="Isolated", + ) + builder.run() +```` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + browser := "msedge" + mode := aspire.BrowserUserDataModeIsolated + web := builder.AddViteApp("web", "../web").WithBrowserLogs(&aspire.WithBrowserLogsOptions{Browser: &browser, UserDataMode: &mode}) + + if err := web.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var web = builder.addViteApp("web", "../web") + .withBrowserLogs(new WithBrowserLogsOptions() + .browser("msedge") + .userDataMode(BrowserUserDataMode.ISOLATED)); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let web = builder.add_vite_app("web", "../web", None)?; + let _web = web.with_browser_logs( + Some("msedge"), + None, + Some(BrowserUserDataMode::Isolated), + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + diff --git a/src/frontend/src/content/docs/integrations/devtools/dab/dab-host.mdx b/src/frontend/src/content/docs/integrations/devtools/dab/dab-host.mdx index 02dd1d502..5ab27a1b0 100644 --- a/src/frontend/src/content/docs/integrations/devtools/dab/dab-host.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/dab/dab-host.mdx @@ -26,7 +26,12 @@ This article is the AppHost API reference for the [📦 CommunityToolkit.Aspire. ## Installation - + ```bash title="Terminal" @@ -64,7 +69,12 @@ This adds the package to `aspire.config.json`. After `aspire restore`, `.aspire/ The integration uses `dab-config.json` by default. The file must exist when the AppHost builds the application model. - + ```csharp title="AppHost.cs" @@ -151,7 +161,12 @@ The REST route for this entity is `/api/Product`; GraphQL requests use `/graphql Pass every configuration path to `AddDataAPIBuilder` or `addDataAPIBuilder`. Each file is mounted read-only under `/App` in the container. - + ```csharp title="AppHost.cs" @@ -186,7 +201,12 @@ await builder.build().run(); The DAB resource is a standard container resource, so you can replace the registry, image, or tag: - + ```csharp title="AppHost.cs" @@ -223,7 +243,12 @@ await builder.build().run(); The resource exports its primary endpoint, host, port, and URI expression for use in custom AppHost expressions: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx b/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx index 9b469aa79..174c17ce4 100644 --- a/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/dev-tunnels.mdx @@ -73,6 +73,34 @@ This updates your `aspire.config.json` with the Dev Tunnels hosting integration } ``` + + + +```bash title="Terminal" +aspire add devtunnels +``` + + + + +```bash title="Terminal" +aspire add devtunnels +``` + + + + +```bash title="Terminal" +aspire add devtunnels +``` + + + + +```bash title="Terminal" +aspire add devtunnels +``` + @@ -80,7 +108,9 @@ This updates your `aspire.config.json` with the Dev Tunnels hosting integration In the AppHost project, add a dev tunnel and configure it to expose specific resources: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -102,6 +132,74 @@ const web = await builder.addNodeApp("web", "../web", "index.js"); const tunnel = await builder.addDevTunnel("my-tunnel") .withReference(web); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + web = builder.add_node_app("web", "../web", "index.js") + tunnel = builder.add_dev_tunnel("my-tunnel") + tunnel.with_reference(web) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + web := builder.AddNodeApp("web", "../web", "index.js") + tunnel := builder.AddDevTunnel("my-tunnel").WithReference(web) + + if err := web.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := tunnel.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var web = builder.addNodeApp("web", "../web", "index.js"); + var tunnel = builder.addDevTunnel("my-tunnel").withReference(web); + + builder.build().run(); +} +``` + @@ -111,7 +209,9 @@ When you run the AppHost, the dev tunnel is created to expose the web applicatio To allow anonymous (public) access to the entire tunnel, chain a call to the `WithAnonymousAccess` method: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -135,6 +235,76 @@ const tunnel = await builder.addDevTunnel("public-api") .withReference(web) .withAnonymousAccess(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + web = builder.add_node_app("web", "../web", "index.js") + tunnel = builder.add_dev_tunnel("public-api") + tunnel.with_reference(web).with_anonymous_access() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + web := builder.AddNodeApp("web", "../web", "index.js") + tunnel := builder.AddDevTunnel("public-api").WithReference(web).WithAnonymousAccess() + + if err := web.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := tunnel.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var web = builder.addNodeApp("web", "../web", "index.js"); + var tunnel = builder.addDevTunnel("public-api") + .withReference(web) + .withAnonymousAccess(); + + builder.build().run(); +} +``` + @@ -142,7 +312,9 @@ const tunnel = await builder.addDevTunnel("public-api") To configure other options for the dev tunnel, provide the `DevTunnelOptions` to the `AddDevTunnel` method: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -171,6 +343,88 @@ const web = await builder.addNodeApp("web", "../web", "index.js"); const tunnel = await builder.addDevTunnel("qa", "my-tunnel-id", false, "QA environment tunnel", ["qa", "testing"]) .withReference(web); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + web = builder.add_node_app("web", "../web", "index.js") + tunnel = builder.add_dev_tunnel( + "qa", + tunnel_id="my-tunnel-id", + allow_anonymous=False, + description="QA environment tunnel", + labels=["qa", "testing"], + ) + tunnel.with_reference(web) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + tunnelID := "my-tunnel-id" + allowAnonymous := false + description := "QA environment tunnel" + web := builder.AddNodeApp("web", "../web", "index.js") + tunnel := builder.AddDevTunnel("qa", &aspire.AddDevTunnelOptions{TunnelId: &tunnelID, AllowAnonymous: &allowAnonymous, Description: &description, Labels: []string{"qa", "testing"}}).WithReference(web) + + if err := web.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := tunnel.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var web = builder.addNodeApp("web", "../web", "index.js"); + var options = new AddDevTunnelOptions() + .tunnelId("my-tunnel-id") + .allowAnonymous(false) + .description("QA environment tunnel") + .labels(new String[] { "qa", "testing" }); + var tunnel = builder.addDevTunnel("qa", options).withReference(web); + + builder.build().run(); +} +``` + @@ -178,7 +432,12 @@ const tunnel = await builder.addDevTunnel("qa", "my-tunnel-id", false, "QA envir To allow anonymous access to specific endpoints, use the appropriate `WithReference` overload: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); diff --git a/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-connect.mdx b/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-connect.mdx index 17d23c8b5..8348d2f90 100644 --- a/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-connect.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-connect.mdx @@ -255,7 +255,12 @@ dotnet add package OpenFeature.Providers.Ofrep In your AppHost, retrieve the OFREP endpoint from the flagd resource and pass it to your consuming project as an environment variable: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-host.mdx b/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-host.mdx index 4dc4f7066..b4e4e849b 100644 --- a/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-host.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/flagd/flagd-host.mdx @@ -55,7 +55,12 @@ Or, choose a manual installation approach: Once you've installed the hosting integration in your AppHost project, you can add a flagd resource. The flagd container requires a sync source. Use `WithBindFileSync` to mount a directory of flag-definition files into the container: - + ```csharp title="AppHost.cs" @@ -113,7 +118,12 @@ await builder.build().run(); To specify the flag-definition filename explicitly, pass both the `fileSource` directory and the `filename` to `WithBindFileSync`: - + ```csharp title="AppHost.cs" @@ -191,6 +201,14 @@ For more information on flag configuration, see the [flagd flag definitions docu To enable debug logging for the flagd container, call `WithLogLevel`: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -205,17 +223,48 @@ builder.AddProject("api") builder.Build().Run(); ``` + + + +`WithLogLevel` is excluded from ATS exports. Set the equivalent `FLAGD_DEBUG` environment variable instead: + +```typescript title="apphost.mts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const flagd = await builder.addFlagd('flagd'); +await flagd.withBindFileSync('./flags/'); +await flagd.withEnvironment('FLAGD_DEBUG', 'true'); + +const api = await builder.addProject( + 'api', + '../ExampleProject/ExampleProject.csproj' +); +await api.withReference(flagd); + +await builder.build().run(); +``` + + + + Debug logging provides verbose output for troubleshooting flag evaluation issues. Currently, only the `Debug` log level is supported. :::note -The flagd hosting source explicitly marks `WithLogLevel` with `[AspireExportIgnore]`, so the generated TypeScript API doesn't include `withLogLevel`. In a TypeScript AppHost, set the `FLAGD_DEBUG` environment variable directly instead. +The flagd hosting source explicitly marks `WithLogLevel` with `[AspireExportIgnore]`, so generated SDKs don't include a direct logging method. ::: ## Customize ports To use fixed host ports instead of randomly assigned ones, pass the `port` and `ofrepPort` parameters to `AddFlagd`: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/goff/goff-host.mdx b/src/frontend/src/content/docs/integrations/devtools/goff/goff-host.mdx index 038e0edb3..da83e7796 100644 --- a/src/frontend/src/content/docs/integrations/devtools/goff/goff-host.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/goff/goff-host.mdx @@ -53,7 +53,12 @@ Or, choose a manual installation approach: Once you've installed the hosting integration in your AppHost project, you can add a GO Feature Flag relay proxy resource as shown in the following example: - + ```csharp title="AppHost.cs" @@ -110,7 +115,12 @@ When you reference a GO Feature Flag resource from the AppHost, Aspire makes sev To point the relay proxy to a specific configuration file inside the container, pass the `pathToConfigFile` parameter to `AddGoFeatureFlag`: - + ```csharp title="AppHost.cs" @@ -157,7 +167,12 @@ The `pathToConfigFile` parameter sets the `--config` argument passed to the rela To persist relay proxy data (such as cached flag evaluations) across container restarts, add a named data volume: - + ```csharp title="AppHost.cs" @@ -231,7 +246,12 @@ This instructs the relay proxy to load flags from the `flags.yaml` file inside t To configure the log level for the GO Feature Flag container resource, call `WithLogLevel`: - + ```csharp title="AppHost.cs" @@ -281,7 +301,12 @@ The `WithLogLevel` method sets the `LOGLEVEL` environment variable on the contai To use a specific host port for the relay proxy HTTP endpoint, pass the `port` parameter to `AddGoFeatureFlag`: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/k6/k6-get-started.mdx b/src/frontend/src/content/docs/integrations/devtools/k6/k6-get-started.mdx index 771e3f58a..19c2412a0 100644 --- a/src/frontend/src/content/docs/integrations/devtools/k6/k6-get-started.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/k6/k6-get-started.mdx @@ -59,7 +59,12 @@ Add `CommunityToolkit.Aspire.Hosting.k6` to your AppHost. See [Set up k6 in the Mount the directory that contains your tests, then select the script to run: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/k6/k6-host.mdx b/src/frontend/src/content/docs/integrations/devtools/k6/k6-host.mdx index 5958b0e2d..52ae33c52 100644 --- a/src/frontend/src/content/docs/integrations/devtools/k6/k6-host.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/k6/k6-host.mdx @@ -29,7 +29,12 @@ This reference describes the Community Toolkit k6 hosting integration APIs for t Add [📦 CommunityToolkit.Aspire.Hosting.k6](https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.k6) to the AppHost: - + ```bash title="Terminal" @@ -71,7 +76,12 @@ The command adds the package to `aspire.config.json`: Use `AddK6` / `addK6` to add a Grafana k6 container resource. The resource uses the `docker.io/grafana/k6` image, exposes its HTTP API as the `http` endpoint, and is visible in the dashboard. - + ```csharp title="AppHost.cs" @@ -115,7 +125,12 @@ The examples read `PrimaryEndpoint` / `primaryEndpoint()` when an AppHost API ne The optional `enableBrowserExtensions` parameter selects the k6 image variant with browser support. The optional `port` parameter selects the host port for the HTTP API. - + ```csharp title="AppHost.cs" @@ -147,7 +162,12 @@ The default image does not include browser support. Enable it only for tests tha Mount the script directory with the standard container `WithBindMount` / `withBindMount` API before selecting a path in the container: - + ```csharp title="AppHost.cs" @@ -174,7 +194,12 @@ await k6.withScript('/scripts/main.js', 25, '45s'); `WithK6OtlpEnvironment` / `withK6OtlpEnvironment` copies every existing `OTEL_*` environment variable for the resource to a corresponding `K6_OTEL_*` variable. This enables the k6 OpenTelemetry output to use the OTLP configuration that Aspire has already set for the resource. - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/mailpit/mailpit-host.mdx b/src/frontend/src/content/docs/integrations/devtools/mailpit/mailpit-host.mdx index ad142aa27..28ce1e798 100644 --- a/src/frontend/src/content/docs/integrations/devtools/mailpit/mailpit-host.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/mailpit/mailpit-host.mdx @@ -55,7 +55,12 @@ Or, choose a manual installation approach: Once you've installed the hosting integration in your AppHost project, you can add a Mailpit resource as shown in the following example: - + ```csharp title="AppHost.cs" @@ -121,7 +126,12 @@ You can open the Mailpit web UI from the Aspire dashboard by clicking the HTTP e Add a data volume to the Mailpit resource to persist captured emails across container restarts: - + ```csharp title="AppHost.cs" @@ -166,7 +176,12 @@ The data volume is mounted at `/data` and the integration configures Mailpit to Use a bind mount when you need direct access to Mailpit's database files from the host: - + ```csharp title="AppHost.cs" @@ -201,7 +216,12 @@ The bind mount uses the same `/data` container path and `/data/mailpit.db` datab Pass `httpPort` and `smtpPort` to bind Mailpit's endpoints to fixed host ports: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-get-started.mdx b/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-get-started.mdx index 23c68f7ff..70d72aa94 100644 --- a/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-get-started.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-get-started.mdx @@ -57,7 +57,12 @@ aspire add CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects Then add the SQL Server database and a SQL project resource. Update the DACPAC path to match your database project's output. - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-host.mdx b/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-host.mdx index 4e631857d..e1b9fa298 100644 --- a/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-host.mdx +++ b/src/frontend/src/content/docs/integrations/devtools/sql-projects/sql-projects-host.mdx @@ -35,7 +35,12 @@ Add [📦 CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects](https://www.nuget Or add it manually: - + ```csharp title="AppHost.cs" @@ -64,7 +69,12 @@ Run `aspire restore` after changing a TypeScript AppHost package configuration t In a C# AppHost, use `AddSqlProject` when the AppHost references an MSBuild SQL project. The generic project metadata overload resolves the project's `SqlTargetPath` or `TargetPath` output. Use the non-generic overload with `WithDacpac` when you already have a DACPAC. - + ```csharp title="AppHost.cs" @@ -142,7 +152,12 @@ The package resource uses `tools/.dacpac` by default; `WithDacpac` s Use a DACFx publish profile with `WithDacDeployOptions`, or configure `DacDeployOptions` directly in a C# AppHost. A publish-profile path is loaded as supplied, so use a path that is valid for the AppHost process. - + ```csharp title="AppHost.cs" @@ -181,7 +196,12 @@ await databaseProject.withReference(database); `WithSkipWhenDeployed` records a DACPAC checksum in the target database. On later deployments, the integration skips the DACPAC if the checksum is unchanged. It sets `DropExtendedPropertiesNotInSource` to `false` while this option is active. - + ```csharp title="AppHost.cs" @@ -219,7 +239,12 @@ Use this with a persistent database when avoiding an unchanged schema deployment `WithReference` establishes a parent relationship to the target, waits for that target to be ready, and then deploys the DACPAC. A SQL Server database target supplies the database name automatically. You can instead target any connection-string resource; its connection string must contain `Database` or `Initial Catalog` so DACFx can infer the target database. - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/dotnet/blazor-connect.mdx b/src/frontend/src/content/docs/integrations/dotnet/blazor-connect.mdx index 5c15a891d..d27cca222 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/blazor-connect.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/blazor-connect.mdx @@ -47,7 +47,9 @@ Common properties used in Blazor-hosted app models are: Use the AppHost to connect APIs to a Blazor WebAssembly project resource, then associate the client app with a Blazor Gateway. - + ```csharp title="AppHost.cs" @@ -90,6 +92,88 @@ await builder await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + weather_api = builder.add_project("weatherapi", "../WeatherApi/WeatherApi.csproj") + weather_api.with_http_endpoint(name="http") + blazor_app = builder.add_blazor_wasm_project("app", "../Client/Client.csproj") + blazor_app.with_reference(weather_api.get_endpoint("http")) + gateway = builder.add_blazor_gateway("gateway") + gateway.with_external_http_endpoints().with_blazor_client_app(blazor_app) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + endpointName := "http" + weatherAPI := builder.AddProject("weatherapi", "../WeatherApi/WeatherApi.csproj").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Name: &endpointName}) + blazorApp := builder.AddBlazorWasmProject("app", "../Client/Client.csproj").WithReference(weatherAPI.GetEndpoint("http")) + gateway := builder.AddBlazorGateway("gateway").WithExternalHttpEndpoints().WithBlazorClientApp(blazorApp) + + if err := weatherAPI.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := blazorApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := gateway.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var weatherApi = builder.addProject("weatherapi", "../WeatherApi/WeatherApi.csproj") + .withHttpEndpoint(new WithHttpEndpointOptions().name("http")); + var blazorApp = builder.addBlazorWasmProject("app", "../Client/Client.csproj") + .withReference(weatherApi.getEndpoint("http")); + var gateway = builder.addBlazorGateway("gateway") + .withExternalHttpEndpoints() + .withBlazorClientApp(blazorApp); + + builder.build().run(); +} +``` + diff --git a/src/frontend/src/content/docs/integrations/dotnet/blazor-hosting.mdx b/src/frontend/src/content/docs/integrations/dotnet/blazor-hosting.mdx index 17765c04a..eb787d4cb 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/blazor-hosting.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/blazor-hosting.mdx @@ -72,6 +72,34 @@ This updates your `aspire.config.json` with the Blazor hosting package: } ``` + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Blazor +``` + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Blazor +``` + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Blazor +``` + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Blazor +``` + @@ -79,7 +107,9 @@ This updates your `aspire.config.json` with the Blazor hosting package: Use `AddBlazorWasmProject` / `addBlazorWasmProject` to add a Blazor WebAssembly project to the AppHost model. - + ```csharp title="AppHost.cs" @@ -110,6 +140,75 @@ const blazorApp = await builder await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_project("api", "../MyApi/MyApi.csproj") + blazor_app = builder.add_blazor_wasm_project("app", "../MyBlazorApp/MyBlazorApp.csproj") + blazor_app.with_reference(api.get_endpoint("http")) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + api := builder.AddProject("api", "../MyApi/MyApi.csproj") + blazorApp := builder.AddBlazorWasmProject("app", "../MyBlazorApp/MyBlazorApp.csproj").WithReference(api.GetEndpoint("http")) + + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := blazorApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var api = builder.addProject("api", "../MyApi/MyApi.csproj"); + var blazorApp = builder.addBlazorWasmProject("app", "../MyBlazorApp/MyBlazorApp.csproj") + .withReference(api.getEndpoint("http")); + + builder.build().run(); +} +``` + @@ -144,6 +243,89 @@ const gateway = await builder await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + gateway = builder.add_blazor_gateway("gateway") + gateway.with_external_http_endpoints() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + gateway := builder.AddBlazorGateway("gateway").WithExternalHttpEndpoints() + + if err := gateway.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var gateway = builder.addBlazorGateway("gateway") + .withExternalHttpEndpoints(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let gateway = builder.add_blazor_gateway("gateway")?; + let _gateway = gateway.with_external_http_endpoints()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -155,7 +337,9 @@ await builder.build().run(); Use `WithBlazorClientApp` / `withBlazorClientApp` to bind the WebAssembly project resource to the Blazor Gateway: - + ```csharp title="AppHost.cs" @@ -199,6 +383,85 @@ await builder await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + weather_api = builder.add_project("weatherapi", "../WeatherApi/WeatherApi.csproj") + blazor_app = builder.add_blazor_wasm_project("app", "../MyBlazorApp/MyBlazorApp.csproj") + blazor_app.with_reference(weather_api.get_endpoint("http")) + gateway = builder.add_blazor_gateway("gateway") + gateway.with_external_http_endpoints().with_blazor_client_app(blazor_app) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + weatherAPI := builder.AddProject("weatherapi", "../WeatherApi/WeatherApi.csproj") + blazorApp := builder.AddBlazorWasmProject("app", "../MyBlazorApp/MyBlazorApp.csproj").WithReference(weatherAPI.GetEndpoint("http")) + gateway := builder.AddBlazorGateway("gateway").WithExternalHttpEndpoints().WithBlazorClientApp(blazorApp) + + if err := weatherAPI.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := blazorApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := gateway.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var weatherApi = builder.addProject("weatherapi", "../WeatherApi/WeatherApi.csproj"); + var blazorApp = builder.addBlazorWasmProject("app", "../MyBlazorApp/MyBlazorApp.csproj") + .withReference(weatherApi.getEndpoint("http")); + var gateway = builder.addBlazorGateway("gateway") + .withExternalHttpEndpoints() + .withBlazorClientApp(blazorApp); + + builder.build().run(); +} +``` + diff --git a/src/frontend/src/content/docs/integrations/dotnet/csharp-file-based-apps.mdx b/src/frontend/src/content/docs/integrations/dotnet/csharp-file-based-apps.mdx index 96deeea8d..5c76cba37 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/csharp-file-based-apps.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/csharp-file-based-apps.mdx @@ -71,6 +71,86 @@ await builder.addCSharpApp("worker", "../worker/Program.cs"); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + worker = builder.add_c_sharp_app("worker", "../worker/Program.cs") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + worker := builder.AddCSharpApp("worker", "../worker/Program.cs") + + if err := worker.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var worker = builder.addCSharpApp("worker", "../worker/Program.cs"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _worker = builder.add_c_sharp_app("worker", "../worker/Program.cs", None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -110,6 +190,94 @@ await builder.addCSharpApp("frontend", "../frontend/"); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_c_sharp_app("api", "../api/Api.csproj") + frontend = builder.add_c_sharp_app("frontend", "../frontend/") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + api := builder.AddCSharpApp("api", "../api/Api.csproj") + frontend := builder.AddCSharpApp("frontend", "../frontend/") + + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := frontend.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var api = builder.addCSharpApp("api", "../api/Api.csproj"); + var frontend = builder.addCSharpApp("frontend", "../frontend/"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _api = builder.add_c_sharp_app("api", "../api/Api.csproj", None)?; + let _frontend = builder.add_c_sharp_app("frontend", "../frontend/", None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -125,7 +293,10 @@ When pointing to a `.csproj` file or directory, `AddCSharpApp` behaves similarly File-based apps integrate fully with Aspire's resource model. You can reference other resources and configure service discovery just like any other project resource: - + ```csharp title="AppHost.cs" @@ -164,6 +335,41 @@ await builder.addCSharpApp("worker", "../worker/Program.cs") await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + cache = builder.add_redis("cache") + pg = builder.add_postgres("pg") + db = pg.add_database("mydb") + worker = builder.add_c_sharp_app("worker", "../worker/Program.cs") + worker.with_reference(cache).with_reference(db).with_external_http_endpoints() + builder.run() +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var cache = builder.addRedis("cache"); + var db = builder.addPostgres("pg").addDatabase("mydb"); + var worker = builder.addCSharpApp("worker", "../worker/Program.cs") + .withReference(cache) + .withReference(db) + .withExternalHttpEndpoints(); + + builder.build().run(); +} +``` + @@ -173,7 +379,12 @@ The file-based app receives connection strings and service discovery information The C# `AddCSharpApp` method accepts an optional `Action` callback to configure launch settings: - + ```csharp title="AppHost.cs" @@ -213,6 +424,15 @@ The available options are the same as those used with `AddProject`: You can also write the AppHost itself as a file-based C# app using the `#:sdk` directive: + + + ```csharp title="apphost.cs" #:sdk Aspire.AppHost.Sdk@%ASPIRE_VERSION% #:package Aspire.Hosting.Redis@%ASPIRE_VERSION% @@ -229,10 +449,13 @@ builder.AddCSharpApp("worker", "../worker/Program.cs") builder.Build().Run(); ``` + + + Use `#:package` directives to add NuGet package references directly in the source file instead of a `.csproj`. :::note -A TypeScript AppHost (`apphost.mts`) is already a file-based script with no project file required. The `#:sdk` and `#:package` directives are specific to the C# file-based AppHost feature and have no TypeScript equivalent. +Guest-language AppHosts already use source entry files without an AppHost project file. The `#:sdk` and `#:package` directives are specific to the C# file-based AppHost feature and have no TypeScript, Python, Go, Java, or Rust equivalent. ::: @@ -276,7 +499,7 @@ dotnet_diagnostic.ASPIRECSHARPAPPS001.severity = none ``` :::note -`ASPIRECSHARPAPPS001` is a C# compiler diagnostic. TypeScript AppHosts call `addCSharpApp` without any diagnostic suppression needed. +`ASPIRECSHARPAPPS001` is a C# compiler diagnostic. Generated guest-language SDKs call their `addCSharpApp` equivalent without diagnostic suppression. ::: diff --git a/src/frontend/src/content/docs/integrations/dotnet/dotnet-tool-resources.mdx b/src/frontend/src/content/docs/integrations/dotnet/dotnet-tool-resources.mdx index 406cb1a1b..6840233bd 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/dotnet-tool-resources.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/dotnet-tool-resources.mdx @@ -76,6 +76,86 @@ const efTool = await builder.addDotnetTool("ef", "dotnet-ef"); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + efTool = builder.add_dotnet_tool("ef", "dotnet-ef") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + efTool := builder.AddDotnetTool("ef", "dotnet-ef") + + if err := efTool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var efTool = builder.addDotnetTool("ef", "dotnet-ef"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -113,6 +193,89 @@ await efTool.withArgs(["migrations", "list"]); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef") + ef_tool.with_args(["migrations", "list"]) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + efTool := builder.AddDotnetTool("ef", "dotnet-ef").WithArgs([]string{"migrations", "list"}) + + if err := efTool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var efTool = builder.addDotnetTool("ef", "dotnet-ef") + .withArgs(new String[] { "migrations", "list" }); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef")?; + let _ef_tool = ef_tool.with_args(vec!["migrations".to_string(), "list".to_string()])?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -144,6 +307,88 @@ const efTool = await builder.addDotnetTool("ef", "dotnet-ef"); await efTool.withToolVersion("9.0.1"); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef") + ef_tool.with_tool_version("9.0.1") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + efTool := builder.AddDotnetTool("ef", "dotnet-ef").WithToolVersion("9.0.1") + + if err := efTool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var efTool = builder.addDotnetTool("ef", "dotnet-ef").withToolVersion("9.0.1"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef")?; + let _ef_tool = ef_tool.with_tool_version("9.0.1")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -165,6 +410,88 @@ const efTool = await builder.addDotnetTool("ef", "dotnet-ef"); await efTool.withToolVersion("10.0.*"); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef") + ef_tool.with_tool_version("10.0.*") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + efTool := builder.AddDotnetTool("ef", "dotnet-ef").WithToolVersion("10.0.*") + + if err := efTool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var efTool = builder.addDotnetTool("ef", "dotnet-ef").withToolVersion("10.0.*"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef")?; + let _ef_tool = ef_tool.with_tool_version("10.0.*")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -188,6 +515,88 @@ const efTool = await builder.addDotnetTool("ef", "dotnet-ef"); await efTool.withToolPrerelease(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef") + ef_tool.with_tool_prerelease() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + efTool := builder.AddDotnetTool("ef", "dotnet-ef").WithToolPrerelease() + + if err := efTool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var efTool = builder.addDotnetTool("ef", "dotnet-ef").withToolPrerelease(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let ef_tool = builder.add_dotnet_tool("ef", "dotnet-ef")?; + let _ef_tool = ef_tool.with_tool_prerelease()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -215,6 +624,89 @@ const tool = await builder.addDotnetTool("my-tool", "my-custom-tool"); await tool.withToolSource("https://my-private-feed.example.com/nuget/v3/index.json"); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + tool = builder.add_dotnet_tool("my-tool", "my-custom-tool") + tool.with_tool_source("https://my-private-feed.example.com/nuget/v3/index.json") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + tool := builder.AddDotnetTool("my-tool", "my-custom-tool").WithToolSource("https://my-private-feed.example.com/nuget/v3/index.json") + + if err := tool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var tool = builder.addDotnetTool("my-tool", "my-custom-tool") + .withToolSource("https://my-private-feed.example.com/nuget/v3/index.json"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let tool = builder.add_dotnet_tool("my-tool", "my-custom-tool")?; + let _tool = tool.with_tool_source("https://my-private-feed.example.com/nuget/v3/index.json")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -240,6 +732,91 @@ await tool.withToolSource("./local-packages"); await tool.withToolIgnoreExistingFeeds(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + tool = builder.add_dotnet_tool("my-tool", "my-custom-tool") + tool.with_tool_source("./local-packages").with_tool_ignore_existing_feeds() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + tool := builder.AddDotnetTool("my-tool", "my-custom-tool").WithToolSource("./local-packages").WithToolIgnoreExistingFeeds() + + if err := tool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var tool = builder.addDotnetTool("my-tool", "my-custom-tool") + .withToolSource("./local-packages") + .withToolIgnoreExistingFeeds(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let tool = builder.add_dotnet_tool("my-tool", "my-custom-tool")?; + let tool = tool.with_tool_source("./local-packages")?; + let _tool = tool.with_tool_ignore_existing_feeds()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -263,6 +840,89 @@ const tool = await builder.addDotnetTool("my-tool", "my-custom-tool"); await tool.withToolIgnoreFailedSources(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + tool = builder.add_dotnet_tool("my-tool", "my-custom-tool") + tool.with_tool_ignore_failed_sources() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + tool := builder.AddDotnetTool("my-tool", "my-custom-tool").WithToolIgnoreFailedSources() + + if err := tool.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var tool = builder.addDotnetTool("my-tool", "my-custom-tool") + .withToolIgnoreFailedSources(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let tool = builder.add_dotnet_tool("my-tool", "my-custom-tool")?; + let _tool = tool.with_tool_ignore_failed_sources()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -270,7 +930,10 @@ await tool.withToolIgnoreFailedSources(); The following is a complete example using Entity Framework Core CLI to run database migrations: - + ```csharp title="AppHost.cs" @@ -316,6 +979,44 @@ await efMigrations.waitFor(postgres); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + postgres = builder.add_postgres("postgres") + appdb = postgres.add_database("appdb") + api = builder.add_project("api", "../Api/Api.csproj") + api.with_reference(appdb) + migrations = builder.add_dotnet_tool("ef-migrate", "dotnet-ef") + migrations.with_args(["database", "update", "--project", "../Api"]) + migrations.with_reference(appdb).wait_for(postgres) + builder.run() +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var postgres = builder.addPostgres("postgres"); + var appdb = postgres.addDatabase("appdb"); + var api = builder.addProject("api", "../Api/Api.csproj").withReference(appdb); + var migrations = builder.addDotnetTool("ef-migrate", "dotnet-ef") + .withArgs(new String[] { "database", "update", "--project", "../Api" }) + .withReference(appdb) + .waitFor(postgres); + + builder.build().run(); +} +``` + @@ -351,7 +1052,7 @@ Be aware of the following limitations when using .NET tool resources: ## Suppress the experimental diagnostic -The `ASPIREDOTNETTOOL` diagnostic applies to C# AppHosts only. The TypeScript AppHost SDK does not require any suppression. +The `ASPIREDOTNETTOOL` diagnostic applies to C# AppHosts only. Generated TypeScript, Python, Go, Java, and Rust AppHost SDKs do not require suppression. ### Suppress in code diff --git a/src/frontend/src/content/docs/integrations/dotnet/launch-profiles.mdx b/src/frontend/src/content/docs/integrations/dotnet/launch-profiles.mdx index 6e49894e8..26f10f8a5 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/launch-profiles.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/launch-profiles.mdx @@ -6,6 +6,7 @@ description: Learn how Aspire integrates with .NET launch profiles for C# AppHos import { Image } from 'astro:assets'; import { Aside, Steps } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; import dotnetIcon from '@assets/icons/dotnet.svg'; + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -125,6 +129,121 @@ builder.AddProject( builder.Build().Run(); ``` + + + +```typescript title="apphost.mts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +await builder.addProject( + 'inventoryservice', + '../InventoryService/InventoryService.csproj', + { launchProfileOrOptions: 'mylaunchprofile' } +); + +await builder.build().run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_project( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + launch_profile_or_options="mylaunchprofile", + ) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + inventory := builder.AddProject( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + &aspire.AddProjectOptions{LaunchProfileOrOptions: "mylaunchprofile"}, + ) + if err := inventory.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var inventory = builder.addProject( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + "mylaunchprofile"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::json; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _inventory = builder.add_project( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + Some(json!("mylaunchprofile")), + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + The preceding code launches the `inventoryservice` resource using the options from the `mylaunchprofile` launch profile. The launch profile precedence logic is as follows: @@ -142,6 +261,14 @@ To force a service project to launch without a launch profile, set the `launchPr When adding an ASP.NET Core project to the AppHost, Aspire parses the _launchSettings.json_ file, selects the appropriate launch profile, and automatically generates endpoints in the application model based on the URLs in the `applicationUrl` field. To modify the automatically injected endpoints, use `WithEndpoint`: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -151,6 +278,29 @@ builder.AddProject("inventoryservice") builder.Build().Run(); ``` + + + +```typescript title="apphost.mts" +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +await builder + .addProject( + 'inventoryservice', + '../InventoryService/InventoryService.csproj' + ) + .withEndpointCallback('https', async endpoint => { + await endpoint.isProxied.set(false); + }); + +await builder.build().run(); +``` + + + + The preceding code disables the reverse proxy that Aspire deploys in front of the .NET application and allows the application to respond directly to requests over HTTP(S). ## See also diff --git a/src/frontend/src/content/docs/integrations/dotnet/maui.mdx b/src/frontend/src/content/docs/integrations/dotnet/maui.mdx index a03239a9c..dd44296f9 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/maui.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/maui.mdx @@ -6,6 +6,7 @@ description: Learn how to use the Aspire .NET MAUI integration to orchestrate .N import { Image } from 'astro:assets'; import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; import LearnMore from '@components/LearnMore.astro'; import mauiIcon from '@assets/icons/maui-icon.png'; @@ -66,6 +67,15 @@ TypeScript AppHost support for this integration is not yet available. To orchestrate a .NET MAUI application in your AppHost, call `AddMauiProject` with the name and path to the `.csproj` file: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -78,6 +88,9 @@ var mauiapp = builder.AddMauiProject("mauiapp", "../YourMauiApp/YourMauiApp.cspr builder.Build().Run(); ``` + + + @@ -145,6 +158,15 @@ You can add multiple device configurations for the same MAUI project. Each devic The following example shows all platform configurations together in a single AppHost: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -173,6 +195,9 @@ mauiapp.AddAndroidEmulator() builder.Build().Run(); ``` + + + ## Configure the .NET MAUI app To configure your .NET MAUI app to discover and connect to Aspire-managed services: diff --git a/src/frontend/src/content/docs/integrations/dotnet/project-resources.mdx b/src/frontend/src/content/docs/integrations/dotnet/project-resources.mdx index 5352bfd26..2ed25d14c 100644 --- a/src/frontend/src/content/docs/integrations/dotnet/project-resources.mdx +++ b/src/frontend/src/content/docs/integrations/dotnet/project-resources.mdx @@ -1,7 +1,7 @@ --- title: Project resources seoTitle: "Add C# project resources to your Aspire AppHost" -description: Learn how to add .NET projects as resources in your Aspire AppHost — with examples for both AppHost.cs (C#) and apphost.mts (TypeScript). +description: Add .NET project resources from C#, TypeScript, Python, Go, Java, and Rust AppHosts, including references, launch profiles, endpoints, and replicas. --- import AppHostTabs from '@components/AppHostTabs.astro'; @@ -23,7 +23,7 @@ import proxyWithRandomPorts from '@assets/fundamentals/networking/proxy-with-ran data-zoom-off /> -This article is the reference for modeling .NET projects as first-class Aspire resources in your AppHost. It enumerates the AppHost APIs — with examples for both `AppHost.cs` and `apphost.mts` — that you use to add, configure, and connect a .NET project resource in your [`AppHost`](/get-started/app-host/) project. +This article is the reference for modeling .NET projects as first-class Aspire resources in your AppHost. It shows the C# API and the generated TypeScript, Python, Go, Java, and Rust APIs that add, configure, and connect a .NET project resource. ## When to use project resources @@ -33,7 +33,7 @@ Use project resources when you need to: - Connect the project to other Aspire resources with `withReference`, service discovery, and generated connection strings. - Reuse launch profile settings and ASP.NET Core endpoint discovery from `launchSettings.json`. -In a **C# AppHost**, `AddProject` references the target project through a generated `Projects.*` type, created from a `ProjectReference` in the AppHost `.csproj`. In a **TypeScript AppHost**, `addProject(name, path)` takes the path to the `.csproj` file directly — no generated type or project file reference is needed. +In a **C# AppHost**, `AddProject` references the target project through a generated `Projects.*` type, created from a `ProjectReference` in the AppHost `.csproj`. Generated guest-language SDKs take the path to the `.csproj` file directly, so no generated project type or AppHost project reference is needed. For single-file apps or quick experiments that don't need a `ProjectReference`, use [C# file-based apps](/integrations/dotnet/csharp-file-based-apps/) instead. @@ -77,6 +77,86 @@ const api = await builder.addProject("api", "../ApiService/ApiService.csproj"); await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_project("api", "../ApiService/ApiService.csproj") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + api := builder.AddProject("api", "../ApiService/ApiService.csproj") + + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var api = builder.addProject("api", "../ApiService/ApiService.csproj"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _api = builder.add_project("api", "../ApiService/ApiService.csproj", None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -84,7 +164,10 @@ await builder.build().run(); Project resources participate fully in the Aspire application model. You can reference other resources, flow connection strings and service discovery information, and expose endpoints like any other Aspire resource: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -118,6 +201,41 @@ await builder.addProject("api", "../ApiService/ApiService.csproj") await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + cache = builder.add_redis("cache") + postgres = builder.add_postgres("postgres") + db = postgres.add_database("appdb") + api = builder.add_project("api", "../ApiService/ApiService.csproj") + api.with_reference(cache).with_reference(db).with_external_http_endpoints() + builder.run() +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var cache = builder.addRedis("cache"); + var db = builder.addPostgres("postgres").addDatabase("appdb"); + var api = builder.addProject("api", "../ApiService/ApiService.csproj") + .withReference(cache) + .withReference(db) + .withExternalHttpEndpoints(); + + builder.build().run(); +} +``` + @@ -175,6 +293,98 @@ await builder.addProject("inventoryservice", "../InventoryService/InventoryServi await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + inventory = builder.add_project( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + launch_profile_or_options="https", + ) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + inventory := builder.AddProject("inventoryservice", "../InventoryService/InventoryService.csproj", &aspire.AddProjectOptions{LaunchProfileOrOptions: "https"}) + + if err := inventory.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var inventory = builder.addProject( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + "https"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::json; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _inventory = builder.add_project( + "inventoryservice", + "../InventoryService/InventoryService.csproj", + Some(json!("https")), + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -187,7 +397,12 @@ The `launchProfileName` argument has the highest precedence. When you don't spec To force a project resource to run without a launch profile, pass `launchProfileName: null`: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -222,6 +437,92 @@ await builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend .withHttpEndpoint({ port: 5066 }) .withHttpsEndpoint({ port: 7239 }); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + frontend.with_http_endpoint(port=5066).with_https_endpoint(port=7239) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + httpPort, httpsPort := 5066.0, 7239.0 + frontend := builder.AddProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: &httpPort}).WithHttpsEndpoint(&aspire.WithHttpsEndpointOptions{Port: &httpsPort}) + + if err := frontend.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var frontend = builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + .withHttpEndpoint(new WithHttpEndpointOptions().port(5066)) + .withHttpsEndpoint(new WithHttpsEndpointOptions().port(7239)); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj", None)?; + frontend.with_http_endpoint(Some(5066.0), None, None, None, None)?; + frontend.with_https_endpoint(Some(7239.0), None, None, None, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -248,7 +549,12 @@ With a Kestrel endpoint configured, remove any `applicationUrl` from _launchSett AppHost throws an exception. - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -281,7 +587,12 @@ await builder.build().run(); For ASP.NET Core projects, Aspire reads the selected launch profile and can create endpoints from the `applicationUrl` field in _launchSettings.json_. You can then customize those endpoints with methods like `WithEndpoint`: - + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -328,6 +639,92 @@ await builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend .withHttpEndpoint({ port: 5066 }) .withReplicas(2); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + frontend.with_http_endpoint(port=5066).with_replicas(2) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + port := 5066.0 + frontend := builder.AddProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: &port}).WithReplicas(2) + + if err := frontend.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var frontend = builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + .withHttpEndpoint(new WithHttpEndpointOptions().port(5066)) + .withReplicas(2); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj", None)?; + frontend.with_http_endpoint(Some(5066.0), None, None, None, None)?; + frontend.with_replicas(2.0)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -359,6 +756,90 @@ builder.AddProject("frontend") await builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj") .withHttpEndpoint({ port: 5066 }); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + frontend.with_http_endpoint(port=5066) + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + port := 5066.0 + frontend := builder.AddProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: &port}) + + if err := frontend.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var frontend = builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + .withHttpEndpoint(new WithHttpEndpointOptions().port(5066)); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj", None)?; + let _frontend = frontend.with_http_endpoint(Some(5066.0), None, None, None, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -387,6 +868,89 @@ builder.AddProject("frontend") await builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj") .withHttpEndpoint(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + frontend.with_http_endpoint() + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + frontend := builder.AddProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj").WithHttpEndpoint() + + if err := frontend.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var frontend = builder.addProject("frontend", "../Networking_Frontend/Networking_Frontend.csproj") + .withHttpEndpoint(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let frontend = builder.add_project("frontend", "../Networking_Frontend/Networking_Frontend.csproj", None)?; + let _frontend = frontend.with_http_endpoint(None, None, None, None, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -427,6 +991,95 @@ await builder.addProject("apiservice", "../Networking.ApiService/Networking.ApiS await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_project("apiservice", "../Networking.ApiService/Networking.ApiService.csproj") + api.with_https_endpoint().with_https_endpoint(port=19227, name="admin").with_endpoints_in_env(["https"]) + builder.run() +```` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + adminPort := 19227.0 + adminName := "admin" + api := builder.AddProject("apiservice", "../Networking.ApiService/Networking.ApiService.csproj").WithHttpsEndpoint().WithHttpsEndpoint(&aspire.WithHttpsEndpointOptions{Port: &adminPort, Name: &adminName}).WithEndpointsInEnvironment([]string{"https"}) + + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var api = builder.addProject("apiservice", "../Networking.ApiService/Networking.ApiService.csproj") + .withHttpsEndpoint() + .withHttpsEndpoint(new WithHttpsEndpointOptions().port(19227).name("admin")) + .withEndpointsInEnvironment(new String[] { "https" }); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let api = builder.add_project("apiservice", "../Networking.ApiService/Networking.ApiService.csproj", None)?; + api.with_https_endpoint(None, None, None, None, None)?; + api.with_https_endpoint(Some(19227.0), None, Some("admin"), None, None)?; + api.with_endpoints_in_environment(vec!["https".to_string()])?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + @@ -438,11 +1091,11 @@ This keeps the `admin` endpoint out of the environment variables while still def ## How project references work -The C# and TypeScript AppHosts differ in how they reference .NET projects: +The C# API and generated guest-language APIs differ in how they reference .NET projects: - **C# AppHost**: A `ProjectReference` in the AppHost `.csproj` causes the Aspire SDK to generate a `Projects.*` type during build. `AddProject("api")` uses that generated type to locate and launch the project. For the SDK-level build behavior behind this generation step, see [Aspire SDK](/get-started/aspire-sdk/). -- **TypeScript AppHost**: No `ProjectReference` is needed. `addProject("api", "../ApiService/ApiService.csproj")` points directly to the `.csproj` file. The TypeScript SDK resolves the project path at startup. +- **Generated guest-language AppHosts**: No `ProjectReference` is needed. Pass the `.csproj` path directly to the generated `addProject` equivalent. The SDK resolves the project path at startup. ## Exclude a project from orchestration @@ -493,6 +1146,94 @@ const microservice2 = await builder.addProject("micro2", "../Microservice2/Prese await builder.build().run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + microservice1 = builder.add_project("micro1", "../Microservice1/Presentation.Api/Presentation.Api.csproj") + microservice2 = builder.add_project("micro2", "../Microservice2/Presentation.Api/Presentation.Api.csproj") + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + microservice1 := builder.AddProject("micro1", "../Microservice1/Presentation.Api/Presentation.Api.csproj") + microservice2 := builder.AddProject("micro2", "../Microservice2/Presentation.Api/Presentation.Api.csproj") + + if err := microservice1.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if err := microservice2.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + app, err := builder.Build() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) + } +} +``` + + + + +```java title="AppHost.java" +import aspire.*; + +void main() throws Exception { + var builder = DistributedApplication.CreateBuilder(); + + var microservice1 = builder.addProject("micro1", "../Microservice1/Presentation.Api/Presentation.Api.csproj"); + var microservice2 = builder.addProject("micro2", "../Microservice2/Presentation.Api/Presentation.Api.csproj"); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let _microservice1 = builder.add_project("micro1", "../Microservice1/Presentation.Api/Presentation.Api.csproj", None)?; + let _microservice2 = builder.add_project("micro2", "../Microservice2/Presentation.Api/Presentation.Api.csproj", None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + diff --git a/src/frontend/src/content/docs/integrations/frameworks/bun-apps.mdx b/src/frontend/src/content/docs/integrations/frameworks/bun-apps.mdx index b3834e478..76aaebcae 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/bun-apps.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/bun-apps.mdx @@ -75,6 +75,68 @@ await builder.addBunApp('bun-api', '../bun-app', 'server.ts'); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + bun_app = builder.add_bun_app("bun-api", "../bun-app", "server.ts") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp := builder.AddBunApp("bun-api", "../bun-app", "server.ts") +if err := bunApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var bunApp = builder.addBunApp("bun-api", "../bun-app", "server.ts"); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let bun_app = builder.add_bun_app("bun-api", "../bun-app", "server.ts")?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -113,6 +175,72 @@ await bunApp.withHttpEndpoint({ port: 3000, env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + bun_app = builder.add_bun_app("bun-api", "../bun-app", "server.ts") + bun_app.with_http_endpoint(port=3000, env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp := builder.AddBunApp("bun-api", "../bun-app", "server.ts") +if err := bunApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp = bunApp.WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: aspire.Float64Ptr(3000), Env: aspire.StringPtr("PORT")}) + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var bunApp = builder.addBunApp("bun-api", "../bun-app", "server.ts") + .withHttpEndpoint(new WithHttpEndpointOptions().port(3000).env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let bun_app = builder.add_bun_app("bun-api", "../bun-app", "server.ts")?; +bun_app.with_http_endpoint(Some(3000.0), None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -158,6 +286,72 @@ await bunApp.withHttpEndpoint({ port: 3000, env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + bun_app = builder.add_bun_app("bun-api", "../bun-app", "src/http/server.ts") + bun_app.with_http_endpoint(port=3000, env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp := builder.AddBunApp("bun-api", "../bun-app", "src/http/server.ts") +if err := bunApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp = bunApp.WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: aspire.Float64Ptr(3000), Env: aspire.StringPtr("PORT")}) + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var bunApp = builder.addBunApp("bun-api", "../bun-app", "src/http/server.ts") + .withHttpEndpoint(new WithHttpEndpointOptions().port(3000).env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let bun_app = builder.add_bun_app("bun-api", "../bun-app", "src/http/server.ts")?; +bun_app.with_http_endpoint(Some(3000.0), None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -195,6 +389,72 @@ await bunApp.withHttpEndpoint({ port: 3000, env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + bun_app = builder.add_bun_app("bun-api", "../bun-app", "server.ts") + bun_app.with_http_endpoint(port=3000, env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp := builder.AddBunApp("bun-api", "../bun-app", "server.ts") +if err := bunApp.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + +bunApp = bunApp.WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: aspire.Float64Ptr(3000), Env: aspire.StringPtr("PORT")}) + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var bunApp = builder.addBunApp("bun-api", "../bun-app", "server.ts") + .withHttpEndpoint(new WithHttpEndpointOptions().port(3000).env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let bun_app = builder.add_bun_app("bun-api", "../bun-app", "server.ts")?; +bun_app.with_http_endpoint(Some(3000.0), None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + diff --git a/src/frontend/src/content/docs/integrations/frameworks/dapr/dapr-host.mdx b/src/frontend/src/content/docs/integrations/frameworks/dapr/dapr-host.mdx index a2a7c4457..294360b17 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/dapr/dapr-host.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/dapr/dapr-host.mdx @@ -27,7 +27,12 @@ This article is the AppHost API reference for the [📦 CommunityToolkit.Aspire. ## Installation - + ```bash title="Terminal" @@ -69,7 +74,12 @@ The Dapr TypeScript exports are available, but the source test for referencing D Add Dapr, create state-store and pub/sub component resources, then reference the components from a sidecar: - + ```csharp title="AppHost.cs" @@ -122,7 +132,12 @@ await builder.build().run(); `AddDaprStateStore` and `AddDaprPubSub` create generic component resources. Use the generic component API when you need another Dapr component type: - + ```csharp title="AppHost.cs" @@ -155,7 +170,12 @@ await builder.build().run(); Use `DaprSidecarOptions` or its TypeScript equivalent to configure the app ID, app endpoint, Dapr endpoints, health probes, logging, metrics, component paths, and other `dapr run` settings: - + ```csharp title="AppHost.cs" @@ -207,7 +227,12 @@ await builder.build().run(); Use `WithMetadata` or `withMetadata` for static values. The hosting integration also exposes overloads for endpoint references, reference expressions, and parameters so metadata can follow Aspire-managed resources. - + ```csharp title="AppHost.cs" @@ -260,7 +285,12 @@ Each Dapr sidecar is a hidden resource named from its app resource, such as `api Install [📦 CommunityToolkit.Aspire.Hosting.Azure.Dapr](https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.Azure.Dapr), then enable Dapr components on the Azure Container Apps environment: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/frameworks/deno/deno-get-started.mdx b/src/frontend/src/content/docs/integrations/frameworks/deno/deno-get-started.mdx index 006438265..2f2808360 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/deno/deno-get-started.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/deno/deno-get-started.mdx @@ -40,7 +40,12 @@ The integration is a hosting integration: install it in the AppHost, then model Add `CommunityToolkit.Aspire.Hosting.Deno` to your AppHost. Then add a Deno task or script resource: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/frameworks/deno/deno-host.mdx b/src/frontend/src/content/docs/integrations/frameworks/deno/deno-host.mdx index a7933e540..8f5220e7c 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/deno/deno-host.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/deno/deno-host.mdx @@ -38,7 +38,12 @@ The Deno executable must be available on `PATH` when Aspire runs the resource. `AddDenoApp` / `addDenoApp` creates an executable resource that runs `deno run`, followed by permission flags, the script path, and application arguments. - + ```csharp title="AppHost.cs" @@ -84,7 +89,12 @@ Script resources set `DENO_ENV` to `development` in the development environment `AddDenoTask` / `addDenoTask` runs `deno task `, using the task configuration in the resource's working directory. - + ```csharp title="AppHost.cs" @@ -124,7 +134,12 @@ await builder.build().run(); `WithDenoPackageInstallation` / `withDenoPackageInstallation` adds a `deno install` setup resource named `-deno-install`. The application waits for that setup resource to complete. - + ```csharp title="AppHost.cs" @@ -160,7 +175,12 @@ In C#, an overload also accepts a callback that configures the installer resourc Deno resources are executable resources with endpoint, environment, argument, wait, and service-discovery support. Use the standard AppHost APIs to pass a listening port, configure additional environment variables, and define a probe: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/frameworks/dotnet/dotnet-host.mdx b/src/frontend/src/content/docs/integrations/frameworks/dotnet/dotnet-host.mdx index 21243c7ef..cda13949e 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/dotnet/dotnet-host.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/dotnet/dotnet-host.mdx @@ -21,7 +21,7 @@ import csharpIcon from '@assets/icons/csharp.svg'; classOverride="float-inline-left icon" /> -This article is the reference for the Aspire Dotnet hosting integration. It enumerates the AppHost APIs — with examples for both `AppHost.cs` and `apphost.mts` — that you use to add C# projects and file-based C# apps **by path** in your [`AppHost`](/get-started/app-host/) project. +This article is the reference for the Aspire Dotnet hosting integration. It shows how to add C# projects and file-based C# apps **by path** from each AppHost language where the generated API can express the operation. If you're new to the Dotnet integration, start with the [Get started with the .NET / C# app integration](/integrations/frameworks/dotnet/dotnet-get-started/) guide. @@ -31,7 +31,16 @@ If you're new to the Dotnet integration, start with the [Get started with the .N If your C# AppHost needs to reference the resource type directly — for example, to declare a strongly typed variable or a method parameter — import it from the `Aspire.Hosting.Dotnet` namespace, matching the pattern used by the Go, Python, and JavaScript hosting integrations: -```csharp + + + +```csharp title="AppHost.cs" using Aspire.Hosting.Dotnet; var builder = DistributedApplication.CreateBuilder(args); @@ -40,6 +49,9 @@ IResourceBuilder api = builder.AddDotnetProject("api", "../api/api.csproj"); ``` + + + :::note[Prerequisites] The **.NET SDK** must be available on the `PATH` of the machine running the AppHost. File-based C# apps (`.cs`) require **.NET 10 or later**. ::: @@ -92,6 +104,38 @@ This updates your `aspire.config.json` with the Dotnet hosting integration packa } ``` + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Dotnet +``` + + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Dotnet +``` + + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Dotnet +``` + + + + + +```bash title="Terminal" +aspire add Aspire.Hosting.Dotnet +``` + @@ -127,6 +171,74 @@ await api.withExternalHttpEndpoints(); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_dotnet_project("api", "../api/api.csproj") + api.with_http_endpoint(port=8080) + api.with_external_http_endpoints() + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddDotnetProject("api", "../api/api.csproj").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: aspire.Float64Ptr(8080)}).WithExternalHttpEndpoints() +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addDotnetProject("api", "../api/api.csproj") + .withHttpEndpoint(new WithHttpEndpointOptions().port(8080)) + .withExternalHttpEndpoints(); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_dotnet_project("api", "../api/api.csproj", None)?; +api.with_http_endpoint(Some(8080.0), None, None, None, None)?; +api.with_external_http_endpoints()?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -160,6 +272,68 @@ await builder.addDotnetProject('inventoryservice', '../InventoryService.cs'); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_dotnet_project("inventoryservice", "../InventoryService.cs") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddDotnetProject("inventoryservice", "../InventoryService.cs") +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +builder.addDotnetProject("inventoryservice", "../InventoryService.cs"); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let _api = builder.add_dotnet_project("inventoryservice", "../InventoryService.cs", None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -167,7 +341,12 @@ await builder.build().run(); Pass a configuration action to set additional options such as the launch profile: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/frameworks/go/go-get-started.mdx b/src/frontend/src/content/docs/integrations/frameworks/go/go-get-started.mdx index fba14adb3..4a527fe8c 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/go/go-get-started.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/go/go-get-started.mdx @@ -71,7 +71,7 @@ architecture-beta 2. ### Optionally try the Go AppHost templates - The `Aspire.Hosting.Go` integration works from C# and TypeScript AppHosts. Aspire also includes experimental Go AppHost and Go starter template support in the Aspire CLI. These templates use experimental Go AppHost APIs instead of the `Aspire.Hosting.Go` package. + The `Aspire.Hosting.Go` integration works from C#, TypeScript, Python, Go, Java, and Rust AppHosts. The generated Python, Go, Java, and Rust AppHost SDKs and the Go AppHost templates are experimental. -This article is the reference for the Aspire Go hosting integration. It enumerates the AppHost APIs — with examples for both `AppHost.cs` and `apphost.mts` — that you use to orchestrate Go applications in your [`AppHost`](/get-started/app-host/) project. +This article is the reference for the Aspire Go hosting integration. It shows the equivalent generated APIs for TypeScript, Python, Go, Java, and Rust AppHosts alongside C# examples. If you're new to the Go integration, start with the [Get started with the Go integration](/integrations/frameworks/go/go-get-started/) guide. @@ -82,6 +82,38 @@ This updates your `aspire.config.json` with the Go hosting integration package: } ``` + + + + +```bash title="Terminal" +aspire add go +``` + + + + + +```bash title="Terminal" +aspire add go +``` + + + + + +```bash title="Terminal" +aspire add go +``` + + + + + +```bash title="Terminal" +aspire add go +``` + @@ -127,6 +159,74 @@ await api.withExternalHttpEndpoints(); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api") + api.with_http_endpoint(port=8080, env="PORT") + api.with_external_http_endpoints() + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api").WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: aspire.Float64Ptr(8080), Env: aspire.StringPtr("PORT")}).WithExternalHttpEndpoints() +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api") + .withHttpEndpoint(new WithHttpEndpointOptions().port(8080).env("PORT")) + .withExternalHttpEndpoints(); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_go_app("api", "./api", None, None, None, None, None)?; +api.with_http_endpoint(Some(8080.0), None, None, Some("PORT"), None)?; +api.with_external_http_endpoints()?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -211,6 +311,71 @@ await api.withHttpEndpoint({ env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api", package_path="./cmd/server") + api.with_http_endpoint(env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api", &aspire.AddGoAppOptions{PackagePath: aspire.StringPtr("./cmd/server")}).WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Env: aspire.StringPtr("PORT")}) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api", new AddGoAppOptions().packagePath("./cmd/server")) + .withHttpEndpoint(new WithHttpEndpointOptions().env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_go_app("api", "./api", Some("./cmd/server"), None, None, None, None)?; +api.with_http_endpoint(None, None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -257,6 +422,95 @@ const api = await builder.addGoApp('api', './api', { await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app( + "api", + "./api", + package_path="./cmd/server", + build_tags=["netgo", "integration"], + ld_flags="-X main.version=1.2.3 -s -w", + gc_flags="all=-N -l", + race_detector=True, + ) + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api", &aspire.AddGoAppOptions{ + PackagePath: aspire.StringPtr("./cmd/server"), + BuildTags: []string{"netgo", "integration"}, + LdFlags: aspire.StringPtr("-X main.version=1.2.3 -s -w"), + GcFlags: aspire.StringPtr("all=-N -l"), + RaceDetector: aspire.BoolPtr(true), +}) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api", new AddGoAppOptions() + .packagePath("./cmd/server") + .buildTags(new String[] { "netgo", "integration" }) + .ldFlags("-X main.version=1.2.3 -s -w") + .gcFlags("all=-N -l") + .raceDetector(true)); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let _api = builder.add_go_app( + "api", + "./api", + Some("./cmd/server"), + Some(vec!["netgo".into(), "integration".into()]), + Some("-X main.version=1.2.3 -s -w"), + Some("all=-N -l"), + Some(true), +)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -299,6 +553,74 @@ await api.withHttpEndpoint({ env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api") + api.with_app_args(["--config", "dev.yaml"]) + api.with_http_endpoint(env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api").WithAppArgs([]any{"--config", "dev.yaml"}).WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Env: aspire.StringPtr("PORT")}) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api") + .withAppArgs(new Object[] { "--config", "dev.yaml" }) + .withHttpEndpoint(new WithHttpEndpointOptions().env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_go_app("api", "./api", None, None, None, None, None)?; +api.with_app_args(vec![serde_json::json!("--config"), serde_json::json!("dev.yaml")])?; +api.with_http_endpoint(None, None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -342,6 +664,77 @@ await api.withHttpEndpoint({ env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api") + api.with_mod_tidy().with_mod_vendor().with_mod_download().with_vet_tool() + api.with_http_endpoint(env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api").WithModTidy().WithModVendor().WithModDownload().WithVetTool().WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Env: aspire.StringPtr("PORT")}) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api") + .withModTidy() + .withModVendor() + .withModDownload() + .withVetTool() + .withHttpEndpoint(new WithHttpEndpointOptions().env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_go_app("api", "./api", None, None, None, None, None)?; +api.with_mod_tidy()?.with_mod_vendor()?.with_mod_download()?.with_vet_tool()?; +api.with_http_endpoint(None, None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -398,6 +791,74 @@ await api.withHttpEndpoint({ port: 8080, env: 'PORT' }); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api") + api.with_delve_server() + api.with_http_endpoint(port=8080, env="PORT") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api").WithDelveServer().WithHttpEndpoint(&aspire.WithHttpEndpointOptions{Port: aspire.Float64Ptr(8080), Env: aspire.StringPtr("PORT")}) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api") + .withDelveServer() + .withHttpEndpoint(new WithHttpEndpointOptions().port(8080).env("PORT")); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_go_app("api", "./api", None, None, None, None, None)?; +api.with_delve_server(None)?; +api.with_http_endpoint(Some(8080.0), None, None, Some("PORT"), None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -433,6 +894,93 @@ await api.withDelveServer({ }); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api") + api.with_delve_server(options={ + "AcceptMultiClient": True, + "ContinueOnStart": True, + "Log": True, + "LogOutput": "rpc,dap,debugger", + }) + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api").WithDelveServer(&aspire.DelveServerOptions{ + AcceptMultiClient: aspire.BoolPtr(true), + ContinueOnStart: aspire.BoolPtr(true), + Log: aspire.BoolPtr(true), + LogOutput: aspire.StringPtr("rpc,dap,debugger"), +}) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var options = new DelveServerOptions(); +options.setAcceptMultiClient(true); +options.setContinueOnStart(true); +options.setLog(true); +options.setLogOutput("rpc,dap,debugger"); + +var api = builder.addGoApp("api", "./api").withDelveServer(options); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let options = DelveServerOptions { + accept_multi_client: Some(true), + continue_on_start: Some(true), + log: Some(true), + log_output: Some("rpc,dap,debugger".into()), + ..Default::default() +}; +let api = builder.add_go_app("api", "./api", None, None, None, None, None)?; +api.with_delve_server(Some(options))?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -503,6 +1051,71 @@ await api.withGoPrivate(['github.com/myorg'], 'github.com'); await builder.build().run(); ``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_go_app("api", "./api") + api.with_go_private(["github.com/myorg"], "github.com") + builder.run() +``` + + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddGoApp("api", "./api").WithGoPrivate([]string{"github.com/myorg"}, "github.com") +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +distributedApp, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := distributedApp.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + + +```java title="AppHost.java" +var builder = DistributedApplication.CreateBuilder(); + +var api = builder.addGoApp("api", "./api") + .withGoPrivate(new String[] { "github.com/myorg" }, "github.com"); + +builder.build().run(); +``` + + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +let api = builder.add_go_app("api", "./api", None, None, None, None, None)?; +api.with_go_private(vec!["github.com/myorg".into()], "github.com", None, None)?; + +let app = builder.build()?; +app.run(None)?; +``` + @@ -513,7 +1126,7 @@ await builder.build().run(); ## Experimental Go AppHost templates -The `Aspire.Hosting.Go` integration can be used from C# and TypeScript AppHosts. Aspire also includes experimental Go AppHost and Go starter template support in the Aspire CLI. The Go AppHost templates use experimental Go AppHost APIs instead of the `Aspire.Hosting.Go` package. To enable Go AppHost language support for CLI templates, enable the Go feature flag: +The `Aspire.Hosting.Go` integration can be used from C#, TypeScript, Python, Go, Java, and Rust AppHosts. The generated Python, Go, Java, and Rust AppHost SDKs are experimental. Aspire also includes experimental Go AppHost and Go starter templates in the Aspire CLI. To enable Go AppHost language support for CLI templates, enable the Go feature flag: ```bash title="Aspire CLI" aspire config set features:experimentalPolyglot:go true --global diff --git a/src/frontend/src/content/docs/integrations/frameworks/java/java-get-started.mdx b/src/frontend/src/content/docs/integrations/frameworks/java/java-get-started.mdx index 422976706..27f1d3149 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/java/java-get-started.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/java/java-get-started.mdx @@ -40,7 +40,12 @@ Build helpers create setup resources that the executable application waits for d Add `CommunityToolkit.Aspire.Hosting.Java` to the AppHost, then configure an executable Java application. This short example runs a Spring Boot application through its Maven wrapper: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/frameworks/java/java-host.mdx b/src/frontend/src/content/docs/integrations/frameworks/java/java-host.mdx index a94f3724b..7b5fd98c5 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/java/java-host.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/java/java-host.mdx @@ -41,7 +41,12 @@ Executable Java resources require the Java command and, when used, the Maven or `WithMavenGoal` / `withMavenGoal` and `WithGradleTask` / `withGradleTask` replace the executable command with the respective wrapper in run mode. Maven defaults to `mvnw` (`mvnw.cmd` on Windows), and Gradle defaults to `gradlew` (`gradlew.bat` on Windows). - + ```csharp title="AppHost.cs" @@ -84,7 +89,12 @@ Do not combine a Maven goal or Gradle task with a JAR path; the integration reje Provide a JAR path relative to the resource working directory. The resource runs `java -jar ` followed by the application arguments. - + ```csharp title="AppHost.cs" @@ -132,7 +142,12 @@ The TypeScript export is named `addJavaAppWithJar`. The Java executable resource `WithMavenBuild` / `withMavenBuild` and `WithGradleBuild` / `withGradleBuild` add a child setup resource in run mode. The Java app waits for it to finish. Maven runs `clean package` by default and Gradle runs `clean build` by default; passing arguments replaces those defaults. - + ```csharp title="AppHost.cs" @@ -171,7 +186,12 @@ await builder.build().run(); Use `WithWrapperPath` / `withWrapperPath` before a build or launch method to use a wrapper at a relative or absolute path instead of the platform default. - + ```csharp title="AppHost.cs" @@ -207,7 +227,12 @@ await builder.build().run(); `WithJvmArgs` / `withJvmArgs` appends JVM options to `JAVA_TOOL_OPTIONS`, which works with JAR, Maven, Gradle, and container launches. `WithOtelAgent` / `withOtelAgent` configures the OTLP exporter; when you provide an agent JAR path, it also appends `-javaagent:` to `JAVA_TOOL_OPTIONS`. - + ```csharp title="AppHost.cs" @@ -247,7 +272,12 @@ Download the agent JAR before configuring its path. `WithOtelAgent` without a pa `AddJavaContainerApp` / `addJavaContainerApp` models an existing container image and configures the OTLP exporter. Pass an optional image tag; configure endpoints and environment variables with standard container-resource APIs. - + ```csharp title="AppHost.cs" @@ -286,7 +316,12 @@ await builder.build().run(); Java executable and container resources support standard AppHost endpoints, environment variables, health checks, waits, and service discovery. Pass Spring Boot's listening port through an endpoint environment variable, add a health-check path, and reference the resource from consumers: - + ```csharp title="AppHost.cs" diff --git a/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx b/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx index f98358e1c..94dd90d70 100644 --- a/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx +++ b/src/frontend/src/content/docs/integrations/frameworks/javascript.mdx @@ -21,7 +21,7 @@ import jsIcon from '@assets/icons/javascript.svg'; data-zoom-off /> -This article is the reference for the Aspire JavaScript hosting integration. It enumerates the AppHost APIs — with examples for both `AppHost.cs` and `apphost.mts` — that you use to orchestrate JavaScript and TypeScript applications in your [`AppHost`](/get-started/app-host/) project. +This article is the reference for the Aspire JavaScript hosting integration. It shows the equivalent generated APIs for TypeScript, Python, Go, Java, and Rust AppHosts alongside C# examples. ## Hosting integration @@ -39,7 +39,7 @@ The integration exposes a number of app resource types: ## Framework examples -The following TypeScript AppHost examples show validated ways to wire common JavaScript frameworks into Aspire for both local development and Docker Compose deployment. Each sample assumes: +The following AppHost example shows validated ways to wire common JavaScript frameworks into Aspire for both local development and Docker Compose deployment. It assumes: - A backend API app lives in `./frameworks/api` and listens on the `PORT` environment variable. - The framework app lives in `./frameworks/`. @@ -47,7 +47,12 @@ The following TypeScript AppHost examples show validated ways to wire common Jav For production deployment choices, see [Deploy JavaScript apps](/deployment/javascript-apps/). -Start with a shared builder, Docker Compose deployment target, and API resource: +The complete AppHost configuration is shown together because every framework resource shares the same builder, Docker Compose deployment target, and API resource: + + + ```typescript title="apphost.mts" import { createBuilder } from './.aspire/modules/aspire.mjs'; @@ -61,19 +66,460 @@ const api = await builder .withExternalHttpEndpoints(); const apiEndpoint = await api.getEndpoint('http'); -``` -### Vite +await builder + .addViteApp('vite', './frameworks/vite', { runScriptName: 'dev' }) + .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) + .withExternalHttpEndpoints(); -Plain Vite apps that produce static browser files use `addViteApp` and `publishAsStaticWebsite`. The `apiPath` / `apiTarget` options configure the deployed static website to proxy `/api` requests to the backend. +await builder + .addViteApp('react', './frameworks/react', { runScriptName: 'dev' }) + .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) + .withExternalHttpEndpoints(); -```typescript title="apphost.mts" await builder - .addViteApp('vite', './frameworks/vite', { runScriptName: 'dev' }) + .addViteApp('vue', './frameworks/vue', { runScriptName: 'dev' }) + .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('astro', './frameworks/astro', { runScriptName: 'dev' }) + .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('angular', './frameworks/angular', { runScriptName: 'dev' }) .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) .withExternalHttpEndpoints(); + +await builder + .addNextJsApp('nextjs', './frameworks/nextjs', { runScriptName: 'dev' }) + .withEnvironment('API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('nuxt', './frameworks/nuxt', { runScriptName: 'dev' }) + .publishAsPackageScript({ scriptName: 'start' }) + .withEnvironment('API_URL', apiEndpoint) + .withEnvironment('NUXT_API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('sveltekit', './frameworks/sveltekit', { + runScriptName: 'dev', + }) + .publishAsNodeServer('build/index.js', { outputPath: 'build' }) + .withEnvironment('API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('tanstack-start', './frameworks/tanstack-start', { + runScriptName: 'dev', + }) + .publishAsNodeServer('.output/server/index.mjs', { outputPath: '.output' }) + .withEnvironment('API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('astro-ssr', './frameworks/astro-ssr', { + runScriptName: 'dev', + }) + .publishAsPackageScript({ scriptName: 'start' }) + .withEnvironment('API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('remix', './frameworks/remix', { runScriptName: 'dev' }) + .publishAsPackageScript({ + scriptName: 'start', + runScriptArguments: '-- --port "$PORT"', + }) + .withEnvironment('API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder + .addViteApp('qwik', './frameworks/qwik', { runScriptName: 'dev' }) + .publishAsPackageScript({ scriptName: 'start' }) + .withEnvironment('API_URL', apiEndpoint) + .withExternalHttpEndpoints(); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddDockerComposeEnvironment("compose"); + +var api = builder.AddNodeApp("api", "./frameworks/api", "server.js") + .WithHttpEndpoint(port: 3001, env: "PORT") + .WithExternalHttpEndpoints(); + +var apiEndpoint = api.GetEndpoint("http"); + +#pragma warning disable ASPIREJAVASCRIPT001 +builder.AddViteApp("vite", "./frameworks/vite", runScriptName: "dev") + .PublishAsStaticWebsite(apiPath: "/api", apiTarget: api) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("react", "./frameworks/react", runScriptName: "dev") + .PublishAsStaticWebsite(apiPath: "/api", apiTarget: api) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("vue", "./frameworks/vue", runScriptName: "dev") + .PublishAsStaticWebsite(apiPath: "/api", apiTarget: api) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("astro", "./frameworks/astro", runScriptName: "dev") + .PublishAsStaticWebsite(apiPath: "/api", apiTarget: api) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("angular", "./frameworks/angular", runScriptName: "dev") + .PublishAsStaticWebsite(apiPath: "/api", apiTarget: api) + .WithExternalHttpEndpoints(); + +builder.AddNextJsApp("nextjs", "./frameworks/nextjs", runScriptName: "dev") + .WithEnvironment("API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("nuxt", "./frameworks/nuxt", runScriptName: "dev") + .PublishAsPackageScript(scriptName: "start") + .WithEnvironment("API_URL", apiEndpoint) + .WithEnvironment("NUXT_API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("sveltekit", "./frameworks/sveltekit", runScriptName: "dev") + .PublishAsNodeServer(entryPoint: "build/index.js", outputPath: "build") + .WithEnvironment("API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); + +builder.AddViteApp( + "tanstack-start", + "./frameworks/tanstack-start", + runScriptName: "dev") + .PublishAsNodeServer( + entryPoint: ".output/server/index.mjs", + outputPath: ".output") + .WithEnvironment("API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("astro-ssr", "./frameworks/astro-ssr", runScriptName: "dev") + .PublishAsPackageScript(scriptName: "start") + .WithEnvironment("API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("remix", "./frameworks/remix", runScriptName: "dev") + .PublishAsPackageScript( + scriptName: "start", + runScriptArguments: "-- --port \"$PORT\"") + .WithEnvironment("API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); + +builder.AddViteApp("qwik", "./frameworks/qwik", runScriptName: "dev") + .PublishAsPackageScript(scriptName: "start") + .WithEnvironment("API_URL", apiEndpoint) + .WithExternalHttpEndpoints(); +#pragma warning restore ASPIREJAVASCRIPT001 + +builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_docker_compose_env("compose") + + api = builder.add_node_app("api", "./frameworks/api", "server.js") + api.with_http_endpoint(port=3001, env="PORT") + api.with_external_http_endpoints() + api_endpoint = api.get_endpoint("http") + + for name in ("vite", "react", "vue", "astro", "angular"): + app = builder.add_vite_app(name, f"./frameworks/{name}") + app.publish_as_static_website(api_path="/api", api_target=api) + app.with_external_http_endpoints() + + nextjs = builder.add_next_js_app("nextjs", "./frameworks/nextjs") + nextjs.with_environment("API_URL", api_endpoint) + nextjs.with_external_http_endpoints() + + nuxt = builder.add_vite_app("nuxt", "./frameworks/nuxt") + nuxt.publish_as_package_script(script_name="start") + nuxt.with_environment("API_URL", api_endpoint) + nuxt.with_environment("NUXT_API_URL", api_endpoint) + nuxt.with_external_http_endpoints() + + sveltekit = builder.add_vite_app("sveltekit", "./frameworks/sveltekit") + sveltekit.publish_as_node_server("build/index.js", output_path="build") + sveltekit.with_environment("API_URL", api_endpoint) + sveltekit.with_external_http_endpoints() + + tanstack = builder.add_vite_app( + "tanstack-start", "./frameworks/tanstack-start" + ) + tanstack.publish_as_node_server( + ".output/server/index.mjs", output_path=".output" + ) + tanstack.with_environment("API_URL", api_endpoint) + tanstack.with_external_http_endpoints() + + astro_ssr = builder.add_vite_app("astro-ssr", "./frameworks/astro-ssr") + astro_ssr.publish_as_package_script(script_name="start") + astro_ssr.with_environment("API_URL", api_endpoint) + astro_ssr.with_external_http_endpoints() + + remix = builder.add_vite_app("remix", "./frameworks/remix") + remix.publish_as_package_script( + script_name="start", + run_script_arguments='-- --port "$PORT"', + ) + remix.with_environment("API_URL", api_endpoint) + remix.with_external_http_endpoints() + + qwik = builder.add_vite_app("qwik", "./frameworks/qwik") + qwik.publish_as_package_script(script_name="start") + qwik.with_environment("API_URL", api_endpoint) + qwik.with_external_http_endpoints() + + builder.run() +``` + + + + +```go title="apphost.go" +builder, err := aspire.CreateBuilder() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} + +compose := builder.AddDockerComposeEnvironment("compose") +if err := compose.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + +api := builder.AddNodeApp("api", "./frameworks/api", "server.js"). + WithHttpEndpoint(&aspire.WithHttpEndpointOptions{ + Port: aspire.Float64Ptr(3001), + Env: aspire.StringPtr("PORT"), + }). + WithExternalHttpEndpoints() +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +apiEndpoint := api.GetEndpoint("http") +var apiTarget aspire.ResourceWithServiceDiscovery = api + +for _, name := range []string{"vite", "react", "vue", "astro", "angular"} { + app := builder.AddViteApp( + name, + "./frameworks/"+name, + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, + ).PublishAsStaticWebsite(&aspire.PublishAsStaticWebsiteOptions{ + ApiPath: aspire.StringPtr("/api"), + ApiTarget: &apiTarget, + }).WithExternalHttpEndpoints() + if err := app.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + +} + +nextjs := builder.AddNextJsApp( + "nextjs", + "./frameworks/nextjs", + &aspire.AddNextJsAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).WithEnvironment("API_URL", apiEndpoint).WithExternalHttpEndpoints() +if err := nextjs.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +nuxt := builder.AddViteApp( + "nuxt", + "./frameworks/nuxt", + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).PublishAsPackageScript(&aspire.PublishAsPackageScriptOptions{ + ScriptName: aspire.StringPtr("start"), +}).WithEnvironment("API_URL", apiEndpoint). + WithEnvironment("NUXT_API_URL", apiEndpoint). + WithExternalHttpEndpoints() +if err := nuxt.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +sveltekit := builder.AddViteApp( + "sveltekit", + "./frameworks/sveltekit", + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).PublishAsNodeServer( + "build/index.js", + &aspire.PublishAsNodeServerOptions{OutputPath: aspire.StringPtr("build")}, +).WithEnvironment("API_URL", apiEndpoint).WithExternalHttpEndpoints() +if err := sveltekit.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +tanstack := builder.AddViteApp( + "tanstack-start", + "./frameworks/tanstack-start", + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).PublishAsNodeServer( + ".output/server/index.mjs", + &aspire.PublishAsNodeServerOptions{OutputPath: aspire.StringPtr(".output")}, +).WithEnvironment("API_URL", apiEndpoint).WithExternalHttpEndpoints() +if err := tanstack.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +astroSSR := builder.AddViteApp( + "astro-ssr", + "./frameworks/astro-ssr", + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).PublishAsPackageScript(&aspire.PublishAsPackageScriptOptions{ + ScriptName: aspire.StringPtr("start"), +}).WithEnvironment("API_URL", apiEndpoint).WithExternalHttpEndpoints() +if err := astroSSR.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +remix := builder.AddViteApp( + "remix", + "./frameworks/remix", + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).PublishAsPackageScript(&aspire.PublishAsPackageScriptOptions{ + ScriptName: aspire.StringPtr("start"), + RunScriptArguments: aspire.StringPtr(`-- --port "$PORT"`), +}).WithEnvironment("API_URL", apiEndpoint).WithExternalHttpEndpoints() +if err := remix.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +qwik := builder.AddViteApp( + "qwik", + "./frameworks/qwik", + &aspire.AddViteAppOptions{RunScriptName: aspire.StringPtr("dev")}, +).PublishAsPackageScript(&aspire.PublishAsPackageScriptOptions{ + ScriptName: aspire.StringPtr("start"), +}).WithEnvironment("API_URL", apiEndpoint).WithExternalHttpEndpoints() +if err := qwik.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} + + +app, err := builder.Build() +if err != nil { + log.Fatal(aspire.FormatError(err)) +} +if err := app.Run(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + +```rust title="apphost.rs" +let builder = create_builder(None)?; + +builder.add_docker_compose_environment("compose")?; + +let api = builder.add_node_app("api", "./frameworks/api", "server.js")?; +api.with_http_endpoint(Some(3001.0), None, None, Some("PORT"), None)?; +api.with_external_http_endpoints()?; +let api_endpoint = api.get_endpoint("http")?; +let api_target = + IResourceWithServiceDiscovery::new(api.handle().clone(), api.client().clone()); + +for name in ["vite", "react", "vue", "astro", "angular"] { + let app = builder.add_vite_app( + name, + &format!("./frameworks/{name}"), + Some("dev"), + )?; + app.publish_as_static_website( + Some("/api"), + Some(&api_target), + None, + None, + None, + )?; + app.with_external_http_endpoints()?; +} + +let nextjs = + builder.add_next_js_app("nextjs", "./frameworks/nextjs", Some("dev"))?; +nextjs.with_environment("API_URL", api_endpoint.handle().to_json())?; +nextjs.with_external_http_endpoints()?; + +let nuxt = builder.add_vite_app("nuxt", "./frameworks/nuxt", Some("dev"))?; +nuxt.publish_as_package_script(Some("start"), None)?; +nuxt.with_environment("API_URL", api_endpoint.handle().to_json())?; +nuxt.with_environment("NUXT_API_URL", api_endpoint.handle().to_json())?; +nuxt.with_external_http_endpoints()?; + +let sveltekit = + builder.add_vite_app("sveltekit", "./frameworks/sveltekit", Some("dev"))?; +sveltekit.publish_as_node_server("build/index.js", Some("build"))?; +sveltekit.with_environment("API_URL", api_endpoint.handle().to_json())?; +sveltekit.with_external_http_endpoints()?; + +let tanstack = builder.add_vite_app( + "tanstack-start", + "./frameworks/tanstack-start", + Some("dev"), +)?; +tanstack.publish_as_node_server( + ".output/server/index.mjs", + Some(".output"), +)?; +tanstack.with_environment("API_URL", api_endpoint.handle().to_json())?; +tanstack.with_external_http_endpoints()?; + +let astro_ssr = + builder.add_vite_app("astro-ssr", "./frameworks/astro-ssr", Some("dev"))?; +astro_ssr.publish_as_package_script(Some("start"), None)?; +astro_ssr.with_environment("API_URL", api_endpoint.handle().to_json())?; +astro_ssr.with_external_http_endpoints()?; + +let remix = + builder.add_vite_app("remix", "./frameworks/remix", Some("dev"))?; +remix.publish_as_package_script(Some("start"), Some(r#"-- --port "$PORT""#))?; +remix.with_environment("API_URL", api_endpoint.handle().to_json())?; +remix.with_external_http_endpoints()?; + +let qwik = builder.add_vite_app("qwik", "./frameworks/qwik", Some("dev"))?; +qwik.publish_as_package_script(Some("start"), None)?; +qwik.with_environment("API_URL", api_endpoint.handle().to_json())?; +qwik.with_external_http_endpoints()?; + +let app = builder.build()?; +app.run(None)?; +``` + + + + +### Vite + +Plain Vite apps that produce static browser files use `addViteApp` and `publishAsStaticWebsite`. The `apiPath` / `apiTarget` options configure the deployed static website to proxy `/api` requests to the backend. + + ```typescript title="Vite — src/weather.ts" export async function loadWeather() { const response = await fetch('/api/weather'); @@ -85,12 +531,6 @@ export async function loadWeather() { React apps created with Vite use the same static website pattern as other Vite browser apps. -```typescript title="apphost.mts" -await builder - .addViteApp('react', './frameworks/react', { runScriptName: 'dev' }) - .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) - .withExternalHttpEndpoints(); -``` ```tsx title="React — src/App.tsx" export async function loadWeather() { @@ -103,12 +543,6 @@ export async function loadWeather() { Vue apps created with Vite also use `publishAsStaticWebsite`. -```typescript title="apphost.mts" -await builder - .addViteApp('vue', './frameworks/vue', { runScriptName: 'dev' }) - .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api }) - .withExternalHttpEndpoints(); -``` ```vue title="Vue — src/App.vue"