From 6efaa72dc598a87b413e56cd319222aedef60f1c Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:23:39 -0500 Subject: [PATCH 1/6] Add TypeScript-first multilingual diagnostics examples Cover all 51 shared AppHost example groups across 38 diagnostics pages, preserve explicit C#-only and Rust limitations, and add registry-driven content and browser coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../content/docs/diagnostics/aspire001.mdx | 8 +- .../content/docs/diagnostics/aspire006.mdx | 133 +++- .../docs/diagnostics/aspireacadomains001.mdx | 208 ++++++- .../docs/diagnostics/aspireacanaming001.mdx | 117 +++- .../docs/diagnostics/aspireacanaming002.mdx | 121 +++- .../content/docs/diagnostics/aspireats001.mdx | 2 +- .../docs/diagnostics/aspireazure001.mdx | 8 +- .../docs/diagnostics/aspireazure002.mdx | 310 +++++++++- .../docs/diagnostics/aspireazure003.mdx | 140 ++++- .../docs/diagnostics/aspireblazor001.mdx | 129 +++- .../docs/diagnostics/aspirebrowserlogs001.mdx | 100 ++- .../diagnostics/aspirecertificates001.mdx | 158 ++++- .../docs/diagnostics/aspirecommand001.mdx | 116 +++- .../docs/diagnostics/aspirecompute001.mdx | 331 +++++++++- .../docs/diagnostics/aspirecompute002.mdx | 170 +++++- .../docs/diagnostics/aspirecompute003.mdx | 291 ++++++++- .../diagnostics/aspirecontainerruntime001.mdx | 6 +- .../aspirecontainershellexecution001.mdx | 10 +- .../docs/diagnostics/aspirecosmosdb001.mdx | 158 ++++- .../docs/diagnostics/aspirecsharpapps001.mdx | 127 +++- .../aspiredockerfilebuilder001.mdx | 352 +++++++++-- .../diagnostics/aspiredotnetproject001.mdx | 123 +++- .../docs/diagnostics/aspiredotnettool001.mdx | 107 +++- .../docs/diagnostics/aspiredurabletask001.mdx | 361 ++++++++++- .../docs/diagnostics/aspireexport001.mdx | 2 + .../docs/diagnostics/aspireexport002.mdx | 2 + .../docs/diagnostics/aspireexport003.mdx | 2 + .../docs/diagnostics/aspireexport004.mdx | 2 + .../docs/diagnostics/aspireexport005.mdx | 2 + .../docs/diagnostics/aspireexport006.mdx | 2 + .../docs/diagnostics/aspireexport007.mdx | 2 + .../docs/diagnostics/aspireexport008.mdx | 2 + .../docs/diagnostics/aspireexport009.mdx | 2 + .../docs/diagnostics/aspireexport010.mdx | 2 + .../docs/diagnostics/aspireexport013.mdx | 2 + .../docs/diagnostics/aspireexport015.mdx | 2 + .../docs/diagnostics/aspireexport016.mdx | 2 + .../docs/diagnostics/aspireextension001.mdx | 10 +- .../docs/diagnostics/aspirefilesystem001.mdx | 6 + .../diagnostics/aspirehostingpython001.mdx | 130 +++- .../docs/diagnostics/aspireinteraction001.mdx | 214 ++++++- .../docs/diagnostics/aspirejavascript001.mdx | 330 +++++++++- .../content/docs/diagnostics/aspiremcp001.mdx | 222 ++++++- .../docs/diagnostics/aspirepersistence001.mdx | 229 ++++++- .../docs/diagnostics/aspirepipelines001.mdx | 140 +++++ .../docs/diagnostics/aspirepipelines002.mdx | 4 + .../docs/diagnostics/aspirepipelines003.mdx | 135 +++++ .../docs/diagnostics/aspirepipelines004.mdx | 4 + .../docs/diagnostics/aspirepostgres001.mdx | 245 +++++++- .../docs/diagnostics/aspireprobes001.mdx | 572 +++++++++++++++++- .../diagnostics/aspireprocesscommand001.mdx | 144 ++++- .../docs/diagnostics/aspireprojects001.mdx | 113 +++- .../diagnostics/aspireproxyendpoints001.mdx | 124 +++- .../docs/diagnostics/aspirepublishers001.mdx | 6 +- .../docs/diagnostics/aspireradius003.mdx | 120 +++- .../docs/diagnostics/aspireradius004.mdx | 120 +++- .../docs/diagnostics/aspireradius006.mdx | 120 +++- .../docs/diagnostics/aspireradius057.mdx | 121 +++- .../docs/diagnostics/aspireterminal001.mdx | 110 +++- .../docs/diagnostics/aspireusersecrets001.mdx | 189 +++++- .../docs/diagnostics/aspirewatch001.mdx | 149 ++++- .../src/content/docs/diagnostics/overview.mdx | 39 +- .../tests/e2e/diagnostics-language.spec.ts | 161 +++++ .../unit/diagnostics-language.vitest.test.ts | 256 ++++++++ 64 files changed, 7425 insertions(+), 200 deletions(-) create mode 100644 src/frontend/tests/e2e/diagnostics-language.spec.ts create mode 100644 src/frontend/tests/unit/diagnostics-language.vitest.test.ts diff --git a/src/frontend/src/content/docs/diagnostics/aspire001.mdx b/src/frontend/src/content/docs/diagnostics/aspire001.mdx index e3146af4e..553dec1f2 100644 --- a/src/frontend/src/content/docs/diagnostics/aspire001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspire001.mdx @@ -4,7 +4,7 @@ seoTitle: "ASPIRE001: The code language isn't fully supported by Aspire" description: "Learn what causes the Aspire compiler warning ASPIRE001 — the code language isn't fully supported by Aspire — and how to fix it in your AppHost." --- -import { Aside, Badge } from '@astrojs/starlight/components'; +import { Badge } from '@astrojs/starlight/components'; The 'CODELANGUAGE' language isn't fully supported by Aspire - some code generation targets will not run, so will require manual authoring. -This diagnostic warning is reported when using a code language other than C#. +This MSBuild warning concerns the .NET AppHost SDK's C# code-generation targets when used in a project written in another .NET language. It doesn't mean that TypeScript AppHosts are unsupported: TypeScript AppHosts use a generated SDK rather than these C# project code-generation targets. The same distinction applies to the [experimental Python, Go, Java, and Rust AppHosts](/diagnostics/overview/#experimental-apphost-examples). ## To correct this warning -In your Aspire project, use C#. +Use C# for a .NET AppHost project that relies on these code-generation targets. To author your AppHost in TypeScript instead, follow [Build your first Aspire app](/get-started/first-app/?aspire-lang=typescript). ## Suppress the warning @@ -36,7 +36,7 @@ Suppress the warning with either of the following methods: - Add the following `PropertyGroup` to your project file: - ```xml title=C# project file" + ```xml title="C# project file" $(NoWarn);ASPIRE001 diff --git a/src/frontend/src/content/docs/diagnostics/aspire006.mdx b/src/frontend/src/content/docs/diagnostics/aspire006.mdx index 86712a522..68df65495 100644 --- a/src/frontend/src/content/docs/diagnostics/aspire006.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspire006.mdx @@ -5,6 +5,7 @@ description: Learn what causes Aspire compiler error ASPIRE006 — invalid AppHo --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; The resource name 'NAME' is invalid: CONSTRAINT -This diagnostic error is reported when a resource's name is invalid, such as a name with consecutive hyphen (`-`) characters. +This C# analyzer diagnostic is reported when a resource's name is invalid, such as a name with consecutive hyphen (`-`) characters. The same resource naming constraints apply across AppHost languages. Generated SDKs reject an invalid name at runtime rather than through this C# analyzer. This error shouldn't be suppressed, as invalid model names throw an exception at runtime. ## Example -The following code generates `ASPIRE006`: +The following examples intentionally use an invalid resource name. The C# example generates `ASPIRE006`; the generated SDK examples fail when the AppHost runs. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const bbsContainer = await builder.addContainer('bbs--server', { image: 'coldwall/mystic' }); +await bbsContainer.withEndpoint({ port: 19991, targetPort: 23 }); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + var bbsContainer = builder.AddContainer("bbs--server", "coldwall/mystic") - .WithEndpoint(19991, 23); + .WithEndpoint(19991, 23); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + bbs_container = builder.add_container("bbs--server", "coldwall/mystic") + bbs_container.with_endpoint(port=19991, target_port=23) + + 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)) + } + + bbsContainer := builder. + AddContainer("bbs--server", "coldwall/mystic"). + WithEndpoint(&aspire.WithEndpointOptions{ + Port: aspire.Float64Ptr(19991), + TargetPort: aspire.Float64Ptr(23), + }) + if err := bbsContainer.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var bbsContainer = builder.addContainer("bbs--server", "coldwall/mystic"); + bbsContainer.withEndpoint( + new WithEndpointOptions().port(19991).targetPort(23)); + + 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 bbs_container = builder.add_container( + "bbs--server", + "coldwall/mystic".into(), + )?; + bbs_container.with_endpoint( + Some(19991.0), + Some(23.0), + None, + None, + None, + None, + None, + None, + )?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + ## To correct this error -Use a valid name. For more information, see [Resource naming conventions](/architecture/resource-model/#resource-basics). +Use a valid name, such as `bbs-server`, in every AppHost language. For more information, see [Resource naming conventions](/architecture/resource-model/#resource-basics). diff --git a/src/frontend/src/content/docs/diagnostics/aspireacadomains001.mdx b/src/frontend/src/content/docs/diagnostics/aspireacadomains001.mdx index 44d4ed331..2b3a57064 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireacadomains001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireacadomains001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler error ASPIREACADOMAINS001 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples configure a custom domain across supported AppHost languages. Before Aspire 13.5, the C# call generated `ASPIREACADOMAINS001`; generated AppHost SDKs don't emit this C# compiler diagnostic. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +await builder.addAzureContainerAppEnvironment('env'); +const customDomain = await builder.addParameter('customDomain'); +const certificateName = await builder.addParameter('certificateName'); + +const api = await builder.addProject('api', '../ApiService/ApiService.csproj'); +await api.withExternalHttpEndpoints(); +await api.publishAsAzureContainerApp(async (infra, app) => { + await app.configureCustomDomain(customDomain, certificateName); +}); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("env"); var customDomain = builder.AddParameter("customDomain"); var certificateName = builder.AddParameter("certificateName"); builder.AddProject("api") - .WithExternalHttpEndpoints() - .PublishAsAzureContainerApp((infra, app) => - { - app.ConfigureCustomDomain(customDomain, certificateName); - }); + .WithExternalHttpEndpoints() + .PublishAsAzureContainerApp((infra, app) => + { + app.ConfigureCustomDomain(customDomain, certificateName); + }); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + + +with create_builder() as builder: + builder.add_azure_container_app_env("env") + custom_domain = builder.add_parameter("customDomain") + certificate_name = builder.add_parameter("certificateName") + + api = builder.add_project("api", "../ApiService/ApiService.csproj") + api.with_external_http_endpoints() + api.publish_as_azure_container_app( + lambda _infra, app: app.configure_custom_domain( + custom_domain, + certificate_name, + ) + ) + + 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)) + } + + env := builder.AddAzureContainerAppEnvironment("env") + customDomain := builder.AddParameter("customDomain") + certificateName := builder.AddParameter("certificateName") + api := builder. + AddProject("api", "../ApiService/ApiService.csproj"). + WithExternalHttpEndpoints(). + PublishAsAzureContainerApp( + func(_ aspire.AzureResourceInfrastructure, app aspire.ContainerApp) { + if err := app.ConfigureCustomDomain(customDomain, certificateName); err != nil { + log.Fatal(aspire.FormatError(err)) + } + }) + for _, err := range []error{ + env.Err(), + customDomain.Err(), + certificateName.Err(), + api.Err(), + } { + if 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzureContainerAppEnvironment("env"); + var customDomain = builder.addParameter("customDomain"); + var certificateName = builder.addParameter("certificateName"); + + var api = builder.addProject( + "api", + "../ApiService/ApiService.csproj"); + api.withExternalHttpEndpoints(); + api.publishAsAzureContainerApp( + (_infra, app) -> app.configureCustomDomain( + customDomain, + certificateName)); + + builder.build().run(); +} +``` + + + + +The generated Rust callback receives raw handle values. Reconstruct the public +`ContainerApp` wrapper before configuring the custom domain: + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + builder.add_azure_container_app_environment("env")?; + let custom_domain = builder.add_parameter( + "customDomain", + None, + None, + None, + )?; + let certificate_name = builder.add_parameter( + "certificateName", + None, + None, + None, + )?; + + let api = builder.add_project( + "api", + "../ApiService/ApiService.csproj", + None, + )?; + api.with_external_http_endpoints()?; + + let client = api.client().clone(); + api.publish_as_azure_container_app(move |args| { + let handle = serde_json::from_value(args[1].clone()) + .expect("Container App callback argument"); + let app = ContainerApp::new(handle, client.clone()); + app.configure_custom_domain(&custom_domain, &certificate_name) + .expect("configure custom domain"); + Value::Null + })?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this error If you use Aspire 13.5 or later, you don't need to suppress this diagnostic and can remove any existing `ASPIREACADOMAINS001` suppressions. If you use an Aspire version earlier than 13.5, suppress the diagnostic with one of the following methods: +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + - Set the severity of the rule in the _.editorconfig_ file. ```ini title=".editorconfig" diff --git a/src/frontend/src/content/docs/diagnostics/aspireacanaming001.mdx b/src/frontend/src/content/docs/diagnostics/aspireacanaming001.mdx index 8d8bb65d2..d994b0ab0 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireacanaming001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireacanaming001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIREACANAMING001 an --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples enable compact resource naming. The C# call generates `ASPIREACANAMING001`; generated AppHost SDKs expose the same experimental hosting operation without emitting this C# compiler diagnostic. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const env = await builder.addAzureContainerAppEnvironment('env'); +await env.withCompactResourceNaming(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -30,8 +55,98 @@ builder.AddAzureContainerAppEnvironment("env") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + env = builder.add_azure_container_app_env("env") + env.with_compact_resource_naming() + + 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)) + } + + env := builder. + AddAzureContainerAppEnvironment("env"). + WithCompactResourceNaming() + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzureContainerAppEnvironment("env") + .withCompactResourceNaming(); + + 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 env = builder.add_azure_container_app_environment("env")?; + env.with_compact_resource_naming()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireacanaming002.mdx b/src/frontend/src/content/docs/diagnostics/aspireacanaming002.mdx index a0e47f299..ffbe84e5f 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireacanaming002.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireacanaming002.mdx @@ -1,10 +1,12 @@ --- title: Compiler Error ASPIREACANAMING002 -seoTitle: "ASPIREACANAMING002: WithUniqueResourceNaming is for evaluation" +seoTitle: 'ASPIREACANAMING002: WithUniqueResourceNaming is for evaluation' description: Learn what causes the Aspire compiler error ASPIREACANAMING002 and how to fix it so your AppHost builds cleanly. --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples enable unique resource naming. The C# call generates `ASPIREACANAMING002`; generated AppHost SDKs expose the same experimental hosting operation without emitting this C# compiler diagnostic. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const env1 = await builder.addAzureContainerAppEnvironment('cae1'); +await env1.withUniqueResourceNaming(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); var env1 = builder.AddAzureContainerAppEnvironment("cae1") .WithUniqueResourceNaming(); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + env = builder.add_azure_container_app_env("cae1") + env.with_unique_resource_naming() + + 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)) + } + + env := builder. + AddAzureContainerAppEnvironment("cae1"). + WithUniqueResourceNaming() + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzureContainerAppEnvironment("cae1") + .withUniqueResourceNaming(); + + 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 env = builder.add_azure_container_app_environment("cae1")?; + env.with_unique_resource_naming()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the error with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireats001.mdx b/src/frontend/src/content/docs/diagnostics/aspireats001.mdx index c19279a5f..4ce598ef1 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireats001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireats001.mdx @@ -25,7 +25,7 @@ import LearnMore from '@components/LearnMore.astro'; ## If you still see this warning -Upgrade the Aspire CLI, AppHost SDK, and hosting integration packages to Aspire 13.4 or later. After upgrading, remove any `ASPIREATS001` suppressions from _.editorconfig_ files, project files, and `#pragma warning` blocks. +Upgrade the Aspire CLI, AppHost SDK, and hosting integration packages to Aspire 13.4 or later. After upgrading, remove any `ASPIREATS001` suppressions from _.editorconfig_ files, project files, `#:property NoWarn` directives in C# file-based AppHosts, and `#pragma warning` blocks. For more information, see [What's new in Aspire 13.4](/whats-new/aspire-13-4/) diff --git a/src/frontend/src/content/docs/diagnostics/aspireazure001.mdx b/src/frontend/src/content/docs/diagnostics/aspireazure001.mdx index dc4b027cf..3ecb4b6b7 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireazure001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireazure001.mdx @@ -15,11 +15,13 @@ import { Badge } from '@astrojs/starlight/components'; > Publishers are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. -The Aspire Azure hosting integration now ships with a publisher. If you're using any of the `Aspire.Hosting.AzurePublisherExtensions.AddAzurePublisher*` APIs, you might see a compiler error/warning indicating that the API is experimental. This behavior is expected, as the API is still in preview and the shape of this API is expected to change in the future. +Earlier versions of the Aspire Azure hosting integration exposed publisher registration through the `Aspire.Hosting.AzurePublisherExtensions.AddAzurePublisher*` APIs. In versions that expose these experimental APIs, using them can produce this compiler diagnostic. ## Example -The following code generates `ASPIREAZURE001`: +`AddAzurePublisher` is a legacy, C#-only publisher-registration API and isn't exported to the generated AppHost SDKs. AppHosts can configure Azure deployment through hosting integrations such as [Azure Container Apps](/integrations/cloud/azure/configure-container-apps/), but the generated SDKs do not expose `addAzurePublisher`. + +In versions that expose `AddAzurePublisher`, the following C# code generates `ASPIREAZURE001`: ```csharp title="AppHost.cs" builder.AddAzurePublisher(); @@ -27,6 +29,8 @@ builder.AddAzurePublisher(); ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the error with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireazure002.mdx b/src/frontend/src/content/docs/diagnostics/aspireazure002.mdx index b2bf5ccea..b7820b679 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireazure002.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireazure002.mdx @@ -1,10 +1,12 @@ --- title: Compiler Error ASPIREAZURE002 -seoTitle: "ASPIREAZURE002: Azure Container App Jobs are for evaluation" +seoTitle: 'ASPIREAZURE002: Azure Container App Jobs are for evaluation' description: Learn what causes the Aspire compiler error ASPIREAZURE002 and how to fix it so your AppHost builds cleanly. Resolve or suppress it to keep your Aspire build. --- -import { Badge, Aside } from '@astrojs/starlight/components'; +import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; > Azure Container App Jobs are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. -The Aspire Azure hosting integration now ships with support for Azure Container App Jobs. If you're using any of the `PublishAsAzureContainerAppJob` APIs, you might see a compiler error/warning indicating that the API is experimental. This behavior is expected, as the API is still in preview and the shape of this API is expected to change in the future. +The Aspire Azure hosting integration supports Azure Container App Jobs. In Aspire versions earlier than 13.4, using the `PublishAsAzureContainerAppJob` APIs in C# can produce this experimental API diagnostic. Current AppHosts can use the stable job APIs without suppressing it. ## Example -The following code generates `ASPIREAZURE002`: +Install the Azure Container Apps hosting integration: + + + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples publish a job scheduled to run daily at midnight with the dedicated scheduled-job API: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +await builder.addAzureContainerAppEnvironment('env'); +const processor = await builder.addProject( + 'data-processor', + '../DataProcessor/DataProcessor.csproj' +); +await processor.publishAsScheduledAzureContainerAppJob('0 0 * * *'); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("env"); +builder.AddProject("data-processor") + .PublishAsScheduledAzureContainerAppJob("0 0 * * *"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_azure_container_app_env("env") + processor = builder.add_project( + "data-processor", + "../DataProcessor/DataProcessor.csproj", + ) + processor.publish_as_scheduled_azure_container_app_job("0 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)) + } + + env := builder.AddAzureContainerAppEnvironment("env") + processor := builder. + AddProject("data-processor", "../DataProcessor/DataProcessor.csproj"). + PublishAsScheduledAzureContainerAppJob("0 0 * * *") + if err := env.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := processor.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzureContainerAppEnvironment("env"); + var processor = builder.addProject( + "data-processor", + "../DataProcessor/DataProcessor.csproj"); + processor.publishAsScheduledAzureContainerAppJob("0 0 * * *"); + + builder.build().run(); +} +``` + + + + +The generated Rust method requires a callback even when the job doesn't need +additional configuration, so pass a no-op callback: + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + builder.add_azure_container_app_environment("env")?; + let processor = builder.add_project( + "data-processor", + "../DataProcessor/DataProcessor.csproj", + None, + )?; + processor.publish_as_scheduled_azure_container_app_job( + "0 0 * * *", + |_| Value::Null, + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +For a manually triggered job, use the general job API without a schedule: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +await builder.addAzureContainerAppEnvironment('env'); +const processor = await builder.addProject( + 'data-processor', + '../DataProcessor/DataProcessor.csproj' +); +await processor.publishAsAzureContainerAppJob(); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("env"); +builder.AddProject("data-processor") + .PublishAsAzureContainerAppJob(); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_azure_container_app_env("env") + processor = builder.add_project( + "data-processor", + "../DataProcessor/DataProcessor.csproj", + ) + processor.publish_as_azure_container_app_job() + + 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)) + } + + env := builder.AddAzureContainerAppEnvironment("env") + processor := builder. + AddProject("data-processor", "../DataProcessor/DataProcessor.csproj"). + PublishAsAzureContainerAppJob() + if err := env.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := processor.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzureContainerAppEnvironment("env"); + var processor = builder.addProject( + "data-processor", + "../DataProcessor/DataProcessor.csproj"); + processor.publishAsAzureContainerAppJob(); + + builder.build().run(); +} +``` + + + + +The generated Rust method requires a callback even when the job doesn't need +additional configuration, so pass a no-op callback: + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + builder.add_azure_container_app_environment("env")?; + let processor = builder.add_project( + "data-processor", + "../DataProcessor/DataProcessor.csproj", + None, + )?; + processor.publish_as_azure_container_app_job(|_| Value::Null)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +In earlier Aspire versions, the following C# customization generated `ASPIREAZURE002`. Direct Azure provisioning object mutation in this form is C#-only; use the scheduled-job API shown above for the same scheduling behavior in generated AppHost SDKs. ```csharp title="AppHost.cs" builder.AddProject("data-processor") @@ -36,6 +332,10 @@ builder.AddProject("data-processor") ## To correct this error +On Aspire 13.4 or later, remove existing `ASPIREAZURE002` suppressions. The following suppression options apply only to earlier C# AppHosts. + +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the error with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireazure003.mdx b/src/frontend/src/content/docs/diagnostics/aspireazure003.mdx index e781e3b80..8db8d13e0 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireazure003.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireazure003.mdx @@ -1,10 +1,12 @@ --- title: Compiler Error ASPIREAZURE003 -seoTitle: "ASPIREAZURE003: Azure Virtual Network types and members are" +seoTitle: 'ASPIREAZURE003: Azure Virtual Network types and members are' description: Learn what causes the Aspire compiler error ASPIREAZURE003 and how to fix it so your AppHost builds cleanly. Resolve or suppress it to keep your Aspire build. --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples add a virtual network and subnet. The C# calls generate `ASPIREAZURE003`; generated AppHost SDKs expose the same experimental operations without emitting this C# compiler diagnostic. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const prefix = await builder.addParameter('subnet-prefix', '10.0.1.0/24'); +const vnet = await builder.addAzureVirtualNetwork('vnet'); +const subnet = await vnet.addSubnet('pe-subnet', prefix); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + var vnet = builder.AddAzureVirtualNetwork("vnet"); var subnet = vnet.AddSubnet("pe-subnet", "10.0.1.0/24"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + prefix = builder.add_parameter("subnet-prefix", value="10.0.1.0/24") + vnet = builder.add_azure_virtual_network("vnet") + subnet = vnet.add_subnet("pe-subnet", prefix) + + 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)) + } + + prefix := builder.AddParameter("subnet-prefix", &aspire.AddParameterOptions{ + Value: aspire.StringPtr("10.0.1.0/24"), + }) + vnet := builder.AddAzureVirtualNetwork("vnet") + subnet := vnet.AddSubnet("pe-subnet", prefix) + for _, err := range []error{ + prefix.Err(), + vnet.Err(), + subnet.Err(), + } { + if 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var prefix = builder.addParameter( + "subnet-prefix", + new AddParameterOptions().value("10.0.1.0/24")); + var vnet = builder.addAzureVirtualNetwork("vnet"); + var subnet = vnet.addSubnet("pe-subnet", prefix, null); + + 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 vnet = builder.add_azure_virtual_network("vnet", None)?; + let subnet = vnet.add_subnet( + "pe-subnet", + "10.0.1.0/24".into(), + None, + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} ``` + + + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the error with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireblazor001.mdx b/src/frontend/src/content/docs/diagnostics/aspireblazor001.mdx index 143c40728..5b131db4b 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireblazor001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireblazor001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIREBLAZOR001 and h --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The C# example generates `ASPIREBLAZOR001`. The generated SDK examples configure the same resources without a C# compiler diagnostic. The path-based C# `AddBlazorWasmApp` method is exported as `addBlazorWasmProject` in TypeScript; the generic `AddBlazorWasmProject` overload is C#-only. + +For the daily CLI, matching packages, and feature flags required by Python, Go, Java, and Rust, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const gateway = await builder.addBlazorGateway("gateway"); +const store = await builder.addBlazorWasmProject("store", "../Store/Store.csproj"); +await gateway.withBlazorClientApp(store); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -37,8 +65,107 @@ gateway.WithBlazorClientApp(store); builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + gateway = builder.add_blazor_gateway("gateway") + store = builder.add_blazor_wasm_project("store", "../Store/Store.csproj") + gateway.with_blazor_client_app(store) + + 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") + store := builder.AddBlazorWasmProject("store", "../Store/Store.csproj") + if err := store.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + gateway.WithBlazorClientApp(store) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var gateway = builder.addBlazorGateway("gateway"); + var store = builder.addBlazorWasmProject( + "store", + "../Store/Store.csproj"); + gateway.withBlazorClientApp(store); + + 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 store = + builder.add_blazor_wasm_project("store", "../Store/Store.csproj")?; + gateway.with_blazor_client_app(&store, None, None, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirebrowserlogs001.mdx b/src/frontend/src/content/docs/diagnostics/aspirebrowserlogs001.mdx index befc10bbd..c5f16dae5 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirebrowserlogs001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirebrowserlogs001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler error ASPIREBROWSERLOGS001 an --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + + +The C# example generates `ASPIREBROWSERLOGS001`. Generated AppHost SDKs expose the same browser logging operation without emitting this C# compiler diagnostic. + +For the daily CLI, matching packages, and feature flags required by Python, Go, Java, and Rust, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const web = await builder.addViteApp("web", "../web"); +await web.withBrowserLogs(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -36,9 +62,77 @@ builder.AddViteApp("web", "../web") 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") + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var web = builder.addViteApp("web", "../web"); + web.withBrowserLogs(); + + builder.build().run(); +} +``` + + + + ## To correct this error -Suppress the error with either of the following methods: +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirecertificates001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecertificates001.mdx index 95afddca1..5f3c8f6a5 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecertificates001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecertificates001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIRECERTIFICATES001 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + + +The C# examples generate `ASPIRECERTIFICATES001`. The generated AppHost SDKs can configure a developer certificate or disable certificate configuration without emitting this C# compiler diagnostic. These examples use Redis resources, whose integration supports TLS configuration. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const tlsCache = await builder.addRedis("tls-cache"); +await tlsCache.withHttpsDeveloperCertificate(); + +const cache = await builder.addRedis("cache"); +await cache.withoutHttpsCertificate(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); // Using developer certificate -builder.AddViteApp("frontend") +builder.AddRedis("tls-cache") .WithHttpsDeveloperCertificate(); +// Disabling HTTPS certificate configuration +builder.AddRedis("cache") + .WithoutHttpsCertificate(); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + tls_cache = builder.add_redis("tls-cache") + tls_cache.with_https_developer_certificate() + + cache = builder.add_redis("cache") + cache.without_https_certificate() + + 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)) + } + + tlsCache := builder. + AddRedis("tls-cache"). + WithHttpsDeveloperCertificate() + if err := tlsCache.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + cache := builder. + AddRedis("cache"). + WithoutHttpsCertificate() + if err := cache.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addRedis("tls-cache") + .withHttpsDeveloperCertificate(); + + builder.addRedis("cache") + .withoutHttpsCertificate(); + + 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 tls_cache = builder.add_redis("tls-cache", None, None)?; + tls_cache.with_https_developer_certificate(None)?; + + let cache = builder.add_redis("cache", None, None)?; + cache.without_https_certificate()?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + +### Custom certificates and .NET services + +The `WithHttpsCertificate(X509Certificate2)` overload and `IDeveloperCertificateService` aren't exported to the generated AppHost SDKs. These C#-only operations require .NET certificate objects or access to the .NET service provider: + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + // Using a custom certificate var certificate = new X509Certificate2("path/to/certificate.pfx", "password"); builder.AddYarp("gateway") .WithHttpsCertificate(certificate); -// Disabling HTTPS certificate configuration -builder.AddRedis("cache") - .WithoutHttpsCertificate(); - // Using IDeveloperCertificateService var developerCertService = builder.Services .BuildServiceProvider() .GetRequiredService(); ``` +The callback-based `withHttpsCertificateConfiguration` and execution-configuration `withHttpsCertificateConfig` APIs are exported to TypeScript. They configure certificate handling and aren't direct replacements for passing a .NET `X509Certificate2` instance. For their configuration model, see [Certificate configuration](/app-host/certificate-configuration/). + ## Understanding certificate configuration Aspire 13.1 introduced TLS termination support APIs that allow you to configure HTTPS certificates for resources that need to terminate TLS connections. Several containers have built-in TLS termination support: @@ -75,7 +217,9 @@ The `IDeveloperCertificateService` provides information about developer certific ## To suppress this warning -Suppress the warning with either of the following methods: +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirecommand001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecommand001.mdx index 17aa16399..e47330540 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecommand001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecommand001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIRECOMMAND001 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; ) -> Value` callbacks, without converting their arguments and results to `RequiredCommandValidationContext` and `RequiredCommandValidationResult`. This example requires the typed callback API.", +}}> + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const cache = await builder.addContainer("cache", { image: "redis" }); +await cache.withRequiredCommandValidation("redis-cli", async (context) => { + return await context.success(); +}); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -builder.AddContainer("db", "postgres") +builder.AddContainer("cache", "redis") .WithRequiredCommand( - "psql", + "redis-cli", context => Task.FromResult(context.Success())); builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import RequiredCommandValidationContext, create_builder + + +def validate_command(context: RequiredCommandValidationContext): + return context.success() + + +with create_builder() as builder: + cache = builder.add_container("cache", "redis") + cache.with_required_command_validation("redis-cli", validate_command) + + 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)) + } + + cache := builder.AddContainer("cache", "redis"). + WithRequiredCommandValidation( + "redis-cli", + func(context aspire.RequiredCommandValidationContext) aspire.RequiredCommandValidationResult { + result := context.Success() + if err := result.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + return result + }, + ) + if err := cache.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var cache = builder.addContainer("cache", "redis"); + cache.withRequiredCommandValidation( + "redis-cli", + RequiredCommandValidationContext::success); + + builder.build().run(); +} +``` + + + + The diagnostic is triggered because the `WithRequiredCommand` validation-callback overload and the `RequiredCommandValidationContext` type are marked with the `[Experimental("ASPIRECOMMAND001")]` attribute. ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. @@ -68,7 +174,7 @@ Suppress the warning with one of the following methods: ```csharp title="Suppressing the warning" #pragma warning disable ASPIRECOMMAND001 - builder.AddContainer("db", "postgres") - .WithRequiredCommand("psql", context => Task.FromResult(context.Success())); + builder.AddContainer("cache", "redis") + .WithRequiredCommand("redis-cli", context => Task.FromResult(context.Success())); #pragma warning restore ASPIRECOMMAND001 ``` diff --git a/src/frontend/src/content/docs/diagnostics/aspirecompute001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecompute001.mdx index 48d69b0f4..18dc9b4ca 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecompute001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecompute001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler error ASPIRECOMPUTE001 and ho --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); +await builder.addDockerComposeEnvironment("env"); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); builder.AddDockerComposeEnvironment("env"); -// or -builder.AddAzureContainerAppEnvironment("env") -// or -builder.AddKubernetesEnvironment("env") + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_docker_compose_env("env") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddDockerComposeEnvironment("env") + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addDockerComposeEnvironment("env"); + + 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)?; + + builder.add_docker_compose_environment("env")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +### Azure Container Apps + + + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); +await builder.addAzureContainerAppEnvironment("env"); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("env"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_azure_container_app_env("env") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddAzureContainerAppEnvironment("env") + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzureContainerAppEnvironment("env"); + + 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)?; + + builder.add_azure_container_app_environment("env")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +### Kubernetes + + + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); +await builder.addKubernetesEnvironment("env"); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddKubernetesEnvironment("env"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_kubernetes_env("env") + + builder.run() ``` + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddKubernetesEnvironment("env") + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addKubernetesEnvironment("env"); + + 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)?; + + builder.add_kubernetes_environment("env")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppress only diagnostics emitted by your installed Aspire version. + Suppress the error with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirecompute002.mdx b/src/frontend/src/content/docs/diagnostics/aspirecompute002.mdx index b3ea6cd9c..76de6cb81 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecompute002.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecompute002.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIRECOMPUTE002 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const computeEnv = await builder.addDockerComposeEnvironment("env"); +const api = await builder.addContainer("api", { image: "nginx" }); +await api.withHttpEndpoint({ targetPort: 80 }); +const endpoint = await api.getEndpoint("http"); +const hostAddress = await computeEnv.getHostAddressExpression(endpoint); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -var computeEnv = builder.AddAzureContainerAppEnvironment("env"); +var computeEnv = builder.AddDockerComposeEnvironment("env"); +var api = builder.AddContainer("api", "nginx") + .WithHttpEndpoint(targetPort: 80); +var endpoint = api.GetEndpoint("http"); +var hostAddress = computeEnv.Resource.GetHostAddressExpression(endpoint); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + compute_env = builder.add_docker_compose_env("env") + api = builder.add_container("api", "nginx") + api.with_http_endpoint(target_port=80) + endpoint = api.get_endpoint("http") + host_address = compute_env.get_host_address_expression(endpoint) + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + computeEnv := builder.AddDockerComposeEnvironment("env") + if err := computeEnv.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + api := builder.AddContainer("api", "nginx"). + WithHttpEndpoint(&aspire.WithHttpEndpointOptions{TargetPort: aspire.Float64Ptr(80)}) + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + endpoint := api.GetEndpoint("http") + if err := endpoint.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + hostAddress := computeEnv.GetHostAddressExpression(endpoint) + if err := computeEnv.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + _ = hostAddress + + 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.*; -// Accessing GetHostAddressExpression on a compute environment resource -var endpoint = computeEnv.Resource.GetEndpoint("http"); +void main(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var computeEnv = builder.addDockerComposeEnvironment("env"); + var api = builder.addContainer("api", "nginx"); + api.withHttpEndpoint(new WithHttpEndpointOptions().targetPort(80)); + var endpoint = api.getEndpoint("http"); + var hostAddress = computeEnv.getHostAddressExpression(endpoint); + + 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 compute_env = builder.add_docker_compose_environment("env")?; + let api = builder.add_container("api", serde_json::json!("nginx"))?; + api.with_http_endpoint(None, Some(80.0), None, None, None)?; + let endpoint = api.get_endpoint("http")?; + let _host_address = compute_env.get_host_address_expression(&endpoint)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +The C# call generates `ASPIRECOMPUTE002`; the operation remains experimental across AppHost languages but the generated language SDKs do not emit this C# compiler diagnostic. + +### Azure Container Apps + +The Azure Container Apps implementation of `GetHostAddressExpression` is not exported to the TypeScript, Python, Go, Java, or Rust SDKs. This specific implementation requires C#: + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var computeEnv = builder.AddAzureContainerAppEnvironment("env"); +var api = builder.AddContainer("api", "nginx") + .WithHttpEndpoint(targetPort: 80); +var endpoint = api.GetEndpoint("http"); var hostAddress = computeEnv.Resource.GetHostAddressExpression(endpoint); + +builder.Build().Run(); ``` ## Understanding GetHostAddressExpression @@ -49,6 +203,8 @@ This method is useful when you need to configure resources with just the hostnam ## To suppress this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. @@ -74,9 +230,11 @@ Suppress the warning with either of the following methods: var builder = DistributedApplication.CreateBuilder(args); var computeEnv = builder.AddAzureContainerAppEnvironment("env"); + var api = builder.AddContainer("api", "nginx") + .WithHttpEndpoint(targetPort: 80); #pragma warning disable ASPIRECOMPUTE002 - var endpoint = computeEnv.Resource.GetEndpoint("http"); + var endpoint = api.GetEndpoint("http"); var hostAddress = computeEnv.Resource.GetHostAddressExpression(endpoint); #pragma warning restore ASPIRECOMPUTE002 ``` diff --git a/src/frontend/src/content/docs/diagnostics/aspirecompute003.mdx b/src/frontend/src/content/docs/diagnostics/aspirecompute003.mdx index 710cd4742..260748bde 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecompute003.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecompute003.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIRECOMPUTE003 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const registry = await builder.addContainerRegistry( + "docker-hub", "docker.io", { repository: "myusername" }); +const api = await builder.addProject("api", "../Api/Api.csproj"); +await api.withContainerRegistry(registry); + +await builder.build().run(); +``` + + + ```csharp title="Using ContainerRegistryResource" var builder = DistributedApplication.CreateBuilder(args); @@ -29,8 +49,146 @@ var registry = builder.AddContainerRegistry("docker-hub", "docker.io", "myuserna var api = builder.AddProject("api") .WithContainerRegistry(registry); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + registry = builder.add_container_registry( + "docker-hub", + "docker.io", + repository="myusername", + ) + api = builder.add_project("api", "../Api/Api.csproj") + api.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( + "docker-hub", + "docker.io", + &aspire.AddContainerRegistryOptions{Repository: "myusername"}, + ) + if err := registry.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + api := builder. + AddProject("api", "../Api/Api.csproj"). + WithContainerRegistry(registry) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var registry = builder.addContainerRegistry( + "docker-hub", "docker.io", "myusername"); + builder.addProject("api", "../Api/Api.csproj") + .withContainerRegistry(registry); + + 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 registry = builder.add_container_registry( + "docker-hub", json!("docker.io"), Some(json!("myusername")), + )?; + let registry_resource = IResource::new( + registry.handle().clone(), registry.client().clone(), + ); + let api = builder.add_project("api", "../Api/Api.csproj", None)?; + api.with_container_registry(®istry_resource)?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + +The Rust SDK takes an `IResource` handle for the registry reference. Its public `handle()`, `client()`, and `IResource::new` APIs preserve the existing resource handle without creating another registry. + +Alternatively, use parameters for the registry endpoint and repository: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const endpointParameter = await builder.addParameter("registry-endpoint"); +const repositoryParameter = await builder.addParameter("registry-repo"); +const registry = await builder.addContainerRegistry( + "my-registry", endpointParameter, { repository: repositoryParameter }); + +const api = await builder.addProject("api", "../Api/Api.csproj"); +await api.withContainerRegistry(registry); + +await builder.build().run(); +``` + + + + ```csharp title="Using ContainerRegistryResource with parameters" var builder = DistributedApplication.CreateBuilder(args); @@ -42,8 +200,137 @@ var registry = builder.AddContainerRegistry("my-registry", endpointParameter, re var api = builder.AddProject("api") .WithContainerRegistry(registry); + +builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + endpoint_parameter = builder.add_parameter("registry-endpoint") + repository_parameter = builder.add_parameter("registry-repo") + registry = builder.add_container_registry( + "my-registry", + endpoint_parameter, + repository=repository_parameter, + ) + + api = builder.add_project("api", "../Api/Api.csproj") + api.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)) + } + + endpointParameter := builder.AddParameter("registry-endpoint") + if err := endpointParameter.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + repositoryParameter := builder.AddParameter("registry-repo") + if err := repositoryParameter.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + registry := builder.AddContainerRegistry( + "my-registry", + endpointParameter, + &aspire.AddContainerRegistryOptions{Repository: repositoryParameter}, + ) + if err := registry.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + api := builder. + AddProject("api", "../Api/Api.csproj"). + WithContainerRegistry(registry) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var endpointParameter = builder.addParameter("registry-endpoint"); + var repositoryParameter = builder.addParameter("registry-repo"); + var registry = builder.addContainerRegistry( + "my-registry", endpointParameter, repositoryParameter); + + builder.addProject("api", "../Api/Api.csproj") + .withContainerRegistry(registry); + + 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 endpoint = builder.add_parameter("registry-endpoint", None, None, None)?; + let repository = builder.add_parameter("registry-repo", None, None, None)?; + let registry = builder.add_container_registry( + "my-registry", + endpoint.handle().to_json(), + Some(repository.handle().to_json()), + )?; + let registry_resource = IResource::new( + registry.handle().clone(), registry.client().clone(), + ); + let api = builder.add_project("api", "../Api/Api.csproj", None)?; + api.with_container_registry(®istry_resource)?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + ## Understanding container registry resources The `ContainerRegistryResource` represents a general-purpose container registry that can be referenced in your Aspire application model. It enables integration with: @@ -75,6 +362,8 @@ The `ContainerRegistryResource` represents a general-purpose container registry ## To suppress this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirecontainerruntime001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecontainerruntime001.mdx index f1830f145..8d34e06dd 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecontainerruntime001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecontainerruntime001.mdx @@ -28,6 +28,8 @@ This API is experimental because the container runtime abstraction layer is bein ## Example +`IContainerRuntime` and its runtime detection, image management, and registry methods are .NET service APIs, not generated AppHost SDK exports. This C#-only example resolves that service directly; container resource configuration is a separate hosting API. + The following code generates `ASPIRECONTAINERRUNTIME001`: ```csharp title="AppHost.cs" @@ -53,7 +55,9 @@ var runtimeName = containerRuntime.Name; ## To correct this warning -Suppress the warning with either of the following methods: +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirecontainershellexecution001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecontainershellexecution001.mdx index 2f44f9129..6d741631e 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecontainershellexecution001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecontainershellexecution001.mdx @@ -19,18 +19,22 @@ The `ShellExecution` property on `ContainerResource` is an experimental feature ## Example +`ShellExecution` is a property of the underlying .NET `ContainerResource`, not its resource builder. The generated AppHost SDKs do not expose this property on the general container resource API. Directly setting it as shown in this C#-only example requires access to the underlying .NET resource. + The following code generates `ASPIRECONTAINERSHELLEXECUTION001`: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); var container = builder.AddContainer("mycontainer", "myimage"); -container.ShellExecution = true; +container.Resource.ShellExecution = true; ``` ## To correct this warning -Suppress the warning with either of the following methods: +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. @@ -53,6 +57,6 @@ Suppress the warning with either of the following methods: ```csharp title="Suppressing the warning" #pragma warning disable ASPIRECONTAINERSHELLEXECUTION001 - container.ShellExecution = true; + container.Resource.ShellExecution = true; #pragma warning restore ASPIRECONTAINERSHELLEXECUTION001 ``` \ No newline at end of file diff --git a/src/frontend/src/content/docs/diagnostics/aspirecosmosdb001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecosmosdb001.mdx index 777845f86..9434481cc 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecosmosdb001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecosmosdb001.mdx @@ -1,10 +1,12 @@ --- title: Compiler Error ASPIRECOSMOSDB001 -seoTitle: "ASPIRECOSMOSDB001: `RunAsPreviewEmulator` is for evaluation" +seoTitle: 'ASPIRECOSMOSDB001: `RunAsPreviewEmulator` is for evaluation' description: Learn what causes the Aspire compiler error ASPIRECOSMOSDB001 and how to fix it so your AppHost builds cleanly. --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples enable the preview emulator and its data explorer. The C# calls generate `ASPIRECOSMOSDB001`; generated AppHost SDKs expose the same experimental operations without emitting this C# compiler diagnostic. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const cosmos = await builder.addAzureCosmosDB('cosmos'); +await cosmos.runAsPreviewEmulator(async (emulator) => { + await emulator.withDataExplorer(); +}); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); var cosmos = builder.AddAzureCosmosDB("cosmos") - .RunAsPreviewEmulator(e => e.WithDataExplorer()); + .RunAsPreviewEmulator(e => e.WithDataExplorer()); + +builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + + +def configure_emulator(emulator) -> None: + emulator.with_data_explorer() + + +with create_builder() as builder: + cosmos = builder.add_azure_cosmos_db("cosmos") + cosmos.run_as_preview_emulator( + configure_container=configure_emulator + ) + + 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)) + } + + cosmos := builder. + AddAzureCosmosDB("cosmos"). + RunAsPreviewEmulator(&aspire.RunAsPreviewEmulatorOptions{ + ConfigureContainer: func(emulator aspire.AzureCosmosDBEmulatorResource) { + emulator.WithDataExplorer() + if err := emulator.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + }, + }) + if err := cosmos.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var cosmos = builder.addAzureCosmosDB("cosmos"); + cosmos.runAsPreviewEmulator( + emulator -> emulator.withDataExplorer()); + + builder.build().run(); +} +``` + + + + +The generated Rust callback receives a raw handle value. Reconstruct the public +`AzureCosmosDBEmulatorResource` wrapper before enabling the data explorer: + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let cosmos = builder.add_azure_cosmos_db("cosmos")?; + let client = cosmos.client().clone(); + cosmos.run_as_preview_emulator(move |args| { + let handle = serde_json::from_value(args[0].clone()) + .expect("Cosmos DB emulator callback argument"); + let emulator = AzureCosmosDBEmulatorResource::new( + handle, + client.clone(), + ); + emulator + .with_data_explorer(None) + .expect("enable data explorer"); + Value::Null + })?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this error -Suppress the Error with either of the following methods: +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + +Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirecsharpapps001.mdx b/src/frontend/src/content/docs/diagnostics/aspirecsharpapps001.mdx index 2289f091c..ab6afa43b 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirecsharpapps001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirecsharpapps001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler error ASPIRECSHARPAPPS001 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The C# example generates `ASPIRECSHARPAPPS001`. AppHosts written in TypeScript, Python, Go, Java, and Rust can also host a file-based C# app; the hosted app's language doesn't determine the AppHost's language. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const cache = await builder.addRedis("cache"); +const worker = await builder.addCSharpApp("worker", "../worker/Program.cs"); +await worker.withReference(cache); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -32,9 +56,108 @@ builder.AddCSharpApp("worker", "../worker/Program.cs") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + cache = builder.add_redis("cache") + worker = builder.add_c_sharp_app("worker", "../worker/Program.cs") + worker.with_reference(cache) + + 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)) + } + + cache := builder.AddRedis("cache") + if err := cache.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + worker := builder. + AddCSharpApp("worker", "../worker/Program.cs"). + WithReference(cache) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var cache = builder.addRedis("cache"); + builder.addCSharpApp("worker", "../worker/Program.cs") + .withReference(cache); + + 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 cache = builder.add_redis("cache", None, None)?; + let worker = builder.add_c_sharp_app("worker", "../worker/Program.cs", None)?; + worker.with_reference(cache.handle().to_json(), None, None, None)?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + +The Rust reference method accepts a JSON-encoded handle. Use the generated `handle().to_json()` helper to pass the existing Redis resource, rather than creating a connection-string literal. + ## To correct this error -Suppress the error with either of the following methods: +The following suppression options apply to C# AppHosts, not the generated SDK equivalents. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspiredockerfilebuilder001.mdx b/src/frontend/src/content/docs/diagnostics/aspiredockerfilebuilder001.mdx index 84f8a6ee1..f6e3316d7 100644 --- a/src/frontend/src/content/docs/diagnostics/aspiredockerfilebuilder001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspiredockerfilebuilder001.mdx @@ -4,7 +4,8 @@ seoTitle: "ASPIREDOCKERFILEBUILDER001: Dockerfile builder types and" description: Learn what causes the Aspire compiler warning ASPIREDOCKERFILEBUILDER001 and how to fix it so your AppHost builds cleanly. --- -import { Badge, Tabs, TabItem } from '@astrojs/starlight/components'; +import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; ) -> Value` callbacks, without converting their arguments to `DockerfileBuilderCallbackContext`. These examples require the typed callback-context API.", +}}> + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; +import type { DockerfileBuilderCallbackContext } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const configureDockerfile = async (context: DockerfileBuilderCallbackContext) => { + const dockerfile = await context.builder(); + const stage = await dockerfile.from("node:22-alpine", { stageName: "runtime" }); + await stage.workDir("/app"); + await stage.copy("package*.json", "./"); + await stage.run("npm ci --omit=dev"); + await stage.copy(".", "."); + await stage.cmd(["node", "index.js"]); +}; + +await builder.addDockerfileBuilder("my-service", "./service", configureDockerfile, { + stage: "runtime", +}); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -37,34 +71,134 @@ builder.AddDockerfileBuilder("my-service", "./service", context => return Task.CompletedTask; }, stage: "runtime"); + +builder.Build().Run(); ``` -## AppHost examples + + -TypeScript AppHosts can use the same Dockerfile builder callback surface. The warning suppression options below apply to C# compiler diagnostics; TypeScript code doesn't use `#pragma` or `.editorconfig` to suppress `ASPIREDOCKERFILEBUILDER001`. +```python title="apphost.py" +from aspire_app import DockerfileBuilderCallbackContext, create_builder - - -```csharp title="AppHost.cs" -var builder = DistributedApplication.CreateBuilder(args); -#pragma warning disable ASPIREDOCKERFILEBUILDER001 -builder.AddDockerfileBuilder("my-service", "./service", context => -{ - var stage = context.Builder.From("node:22-alpine", "runtime"); - stage.WorkDir("/app") - .Copy("package*.json", "./") - .Run("npm ci --omit=dev") - .Copy(".", ".") - .Cmd(["node", "index.js"]); +def configure_dockerfile(context: DockerfileBuilderCallbackContext) -> None: + stage = context.builder.from_("node:22-alpine", stage_name="runtime") + ( + stage.work_dir("/app") + .copy("package*.json", "./") + .run("npm ci --omit=dev") + .copy(".", ".") + .cmd(["node", "index.js"]) + ) - return Task.CompletedTask; -}, stage: "runtime"); -#pragma warning restore ASPIREDOCKERFILEBUILDER001 + +with create_builder() as builder: + builder.add_dockerfile_builder( + "my-service", + "./service", + configure_dockerfile, + stage="runtime", + ) + + 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)) + } + + service := builder.AddDockerfileBuilder( + "my-service", + "./service", + func(context aspire.DockerfileBuilderCallbackContext) { + dockerfile := context.Builder() + if err := dockerfile.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + stage := dockerfile. + From("node:22-alpine", &aspire.FromOptions{ + StageName: aspire.StringPtr("runtime"), + }). + WorkDir("/app"). + Copy("package*.json", "./"). + Run("npm ci --omit=dev"). + Copy(".", "."). + Cmd([]string{"node", "index.js"}) + if err := stage.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + }, + &aspire.AddDockerfileBuilderOptions{ + Stage: aspire.StringPtr("runtime"), + }, + ) + if err := service.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)) + } +} ``` - - -```typescript title="apphost.mts" + + + + +```java title="AppHost.java" +import aspire.*; + +void main(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addDockerfileBuilder( + "my-service", + "./service", + context -> { + var stage = context.builder().from("node:22-alpine", "runtime"); + stage.workDir("/app") + .copy("package*.json", "./") + .run("npm ci --omit=dev") + .copy(".", ".") + .cmd(new String[] { "node", "index.js" }); + }, + "runtime"); + + builder.build().run(); +} +``` + + + + +The callback can also be applied to an existing container resource with `WithDockerfileBuilder`: + +) -> Value` callbacks, without converting their arguments to `DockerfileBuilderCallbackContext`. These examples require the typed callback-context API.", +}}> + + +```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; import type { DockerfileBuilderCallbackContext } from './.aspire/modules/aspire.mjs'; @@ -72,30 +206,28 @@ const builder = await createBuilder(); const configureDockerfile = async (context: DockerfileBuilderCallbackContext) => { const dockerfile = await context.builder(); - await dockerfile - .from("node:22-alpine", { stageName: "runtime" }) - .workDir("/app") - .copy("package*.json", "./") - .run("npm ci --omit=dev") - .copy(".", ".") - .cmd(["node", "index.js"]); + const stage = await dockerfile.from("node:22-alpine", { stageName: "runtime" }); + await stage.workDir("/app"); + await stage.copy("package*.json", "./"); + await stage.run("npm ci --omit=dev"); + await stage.copy(".", "."); + await stage.cmd(["node", "index.js"]); }; -await builder.addDockerfileBuilder("my-service", "./service", configureDockerfile, { +const service = await builder.addContainer("my-service", { image: "node", tag: "22-alpine" }); +await service.withDockerfileBuilder("./service", configureDockerfile, { stage: "runtime", }); + +await builder.build().run(); ``` - - -The callback can also be applied to an existing container resource with `WithDockerfileBuilder`: + + - - ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -#pragma warning disable ASPIREDOCKERFILEBUILDER001 builder.AddContainer("my-service", "node:22-alpine") .WithDockerfileBuilder("./service", context => { @@ -108,35 +240,127 @@ builder.AddContainer("my-service", "node:22-alpine") return Task.CompletedTask; }, stage: "runtime"); -#pragma warning restore ASPIREDOCKERFILEBUILDER001 + +builder.Build().Run(); ``` - - -```typescript title="apphost.mts" -import { createBuilder } from './.aspire/modules/aspire.mjs'; -import type { DockerfileBuilderCallbackContext } from './.aspire/modules/aspire.mjs'; -const builder = await createBuilder(); + + + +```python title="apphost.py" +from aspire_app import AddContainerOptions, DockerfileBuilderCallbackContext, create_builder + + +def configure_dockerfile(context: DockerfileBuilderCallbackContext) -> None: + stage = context.builder.from_("node:22-alpine", stage_name="runtime") + ( + stage.work_dir("/app") + .copy("package*.json", "./") + .run("npm ci --omit=dev") + .copy(".", ".") + .cmd(["node", "index.js"]) + ) -const configureDockerfile = async (context: DockerfileBuilderCallbackContext) => { - const dockerfile = await context.builder(); - await dockerfile - .from("node:22-alpine", { stageName: "runtime" }) - .workDir("/app") - .copy("package*.json", "./") - .run("npm ci --omit=dev") - .copy(".", ".") - .cmd(["node", "index.js"]); -}; -await builder - .addContainer("my-service", "node:22-alpine") - .withDockerfileBuilder("./service", configureDockerfile, { - stage: "runtime", - }); +with create_builder() as builder: + image: AddContainerOptions = {"Image": "node", "Tag": "22-alpine"} + service = builder.add_container("my-service", image) + service.with_dockerfile_builder( + "./service", + configure_dockerfile, + stage="runtime", + ) + + 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)) + } + + service := builder. + AddContainer("my-service", "node:22-alpine"). + WithDockerfileBuilder( + "./service", + func(context aspire.DockerfileBuilderCallbackContext) { + dockerfile := context.Builder() + if err := dockerfile.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + stage := dockerfile. + From("node:22-alpine", &aspire.FromOptions{ + StageName: aspire.StringPtr("runtime"), + }). + WorkDir("/app"). + Copy("package*.json", "./"). + Run("npm ci --omit=dev"). + Copy(".", "."). + Cmd([]string{"node", "index.js"}) + if err := stage.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + }, + &aspire.WithDockerfileBuilderOptions{ + Stage: aspire.StringPtr("runtime"), + }, + ) + if err := service.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var service = builder.addContainer("my-service", "node:22-alpine"); + service.withDockerfileBuilder( + "./service", + context -> { + var stage = context.builder().from("node:22-alpine", "runtime"); + stage.workDir("/app") + .copy("package*.json", "./") + .run("npm ci --omit=dev") + .copy(".", ".") + .cmd(new String[] { "node", "index.js" }); + }, + "runtime"); + + builder.build().run(); +} +``` + + + ## Understanding Dockerfile builder callbacks @@ -168,11 +392,13 @@ The `DockerfileBuilderCallbackContext` provides context information for Dockerfi - Token to observe for cancellation requests - Enables cooperative cancellation of long-running operations -In TypeScript AppHosts, callback context values use camelCase names and asynchronous methods. For example, use `await context.builder()` to get the `DockerfileBuilder` instance. +Callback context and option names follow each SDK's conventions. Python exposes `context.builder` as a property and uses keyword-only options. Go uses option structs and reports fluent-handle errors through `Err`. Java uses typed callback contexts and overloads for optional values. ## To suppress this warning -Suppress the warning with either of the following methods: +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspiredotnetproject001.mdx b/src/frontend/src/content/docs/diagnostics/aspiredotnetproject001.mdx index 69adfd969..5c033cc62 100644 --- a/src/frontend/src/content/docs/diagnostics/aspiredotnetproject001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspiredotnetproject001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIREDOTNETPROJECT00 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The C# example generates `ASPIREDOTNETPROJECT001`. The generated SDK examples host the same file-based C# app without emitting this C# compiler diagnostic. + +For the daily CLI, matching packages, and feature flags required by Python, Go, Java, and Rust, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +await builder.addDotnetProject("inventoryservice", "../InventoryService.cs"); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -34,8 +58,105 @@ builder.AddDotnetProject("inventoryservice", @"..\InventoryService.cs"); 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" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + inventory := builder.AddDotnetProject( + "inventoryservice", + "../InventoryService.cs", + ) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addDotnetProject( + "inventoryservice", + "../InventoryService.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)?; + + builder.add_dotnet_project( + "inventoryservice", + "../InventoryService.cs", + None, + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspiredotnettool001.mdx b/src/frontend/src/content/docs/diagnostics/aspiredotnettool001.mdx index fb93ed2fd..a202eb7e9 100644 --- a/src/frontend/src/content/docs/diagnostics/aspiredotnettool001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspiredotnettool001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREDOTNETTOOL001 a --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const tool = await builder.addDotnetTool("my-tool", "dotnet-tool-package"); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -37,8 +56,94 @@ var tool = builder.AddDotnetTool("my-tool", "dotnet-tool-package"); builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_dotnet_tool("my-tool", "dotnet-tool-package") + + 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", "dotnet-tool-package") + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addDotnetTool("my-tool", "dotnet-tool-package"); + + 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)?; + + builder.add_dotnet_tool("my-tool", "dotnet-tool-package")?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + +Replace `dotnet-tool-package` with the NuGet package ID of the .NET tool you want to run. + ## To correct this warning +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspiredurabletask001.mdx b/src/frontend/src/content/docs/diagnostics/aspiredurabletask001.mdx index 119a017d5..5410cbf9a 100644 --- a/src/frontend/src/content/docs/diagnostics/aspiredurabletask001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspiredurabletask001.mdx @@ -1,10 +1,12 @@ --- title: Compiler Warning ASPIREDURABLETASK001 -seoTitle: "ASPIREDURABLETASK001: Durable Task scheduler and task hub" +seoTitle: 'ASPIREDURABLETASK001: Durable Task scheduler and task hub' description: Learn what causes the Aspire compiler warning ASPIREDURABLETASK001 and how to fix it so your AppHost builds cleanly. --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + + +For generated AppHost SDK setup, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + +The following examples run the scheduler and host storage as emulators and connect a Functions project to a task hub. The C# Durable Task calls generate `ASPIREDURABLETASK001`; generated AppHost SDKs expose the same experimental operations without emitting this C# compiler diagnostic. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const storage = await builder.addAzureStorage('storage'); +await storage.runAsEmulator(); + +const scheduler = await builder.addDurableTaskScheduler('scheduler'); +await scheduler.runAsEmulator(); +const hub = await scheduler.addTaskHub('hub'); + +const functions = await builder.addAzureFunctionsProject( + 'functions', + '../MyFunctions/MyFunctions.csproj' +); +await functions.withHostStorage(storage); +await functions.withReference(hub); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -49,8 +86,328 @@ builder.AddAzureFunctionsProject("functions") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + storage = builder.add_azure_storage("storage") + storage.run_as_emulator() + + scheduler = builder.add_durable_task_scheduler("scheduler") + scheduler.run_as_emulator() + hub = scheduler.add_task_hub("hub") + + functions = builder.add_azure_functions_project( + "functions", + "../MyFunctions/MyFunctions.csproj", + ) + functions.with_host_storage(storage) + functions.with_reference(hub) + + 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)) + } + + storage := builder.AddAzureStorage("storage").RunAsEmulator() + scheduler := builder. + AddDurableTaskScheduler("scheduler"). + RunAsEmulator() + hub := scheduler.AddTaskHub("hub") + functions := builder. + AddAzureFunctionsProject( + "functions", + "../MyFunctions/MyFunctions.csproj"). + WithHostStorage(storage). + WithReference(hub) + for _, err := range []error{ + storage.Err(), + scheduler.Err(), + hub.Err(), + functions.Err(), + } { + if 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var storage = builder.addAzureStorage("storage"); + storage.runAsEmulator(); + + var scheduler = builder.addDurableTaskScheduler("scheduler"); + scheduler.runAsEmulator(); + var hub = scheduler.addTaskHub("hub"); + + var functions = builder.addAzureFunctionsProject( + "functions", + "../MyFunctions/MyFunctions.csproj"); + functions.withHostStorage(storage); + functions.withReference(hub); + + builder.build().run(); +} +``` + + + + +The generated Rust emulator methods require callbacks even when no container +customization is needed, so pass no-op callbacks: + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let storage = builder.add_azure_storage("storage")?; + storage.run_as_emulator(|_| Value::Null)?; + + let scheduler = builder.add_durable_task_scheduler("scheduler")?; + scheduler.run_as_emulator(|_| Value::Null)?; + let hub = scheduler.add_task_hub("hub")?; + + let functions = builder.add_azure_functions_project( + "functions", + "../MyFunctions/MyFunctions.csproj", + )?; + functions.with_host_storage(&storage)?; + functions.with_reference( + hub.handle().to_json(), + None, + None, + None, + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +### Use an existing scheduler and a custom task hub name + +To connect to an existing scheduler during local runs, supply its connection string as a secret parameter. You can also configure the task hub name independently of its Aspire resource name: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const connection = await builder.addParameter('scheduler-connection', { + secret: true, +}); +const hubName = await builder.addParameter('task-hub-name'); + +const scheduler = await builder.addDurableTaskScheduler('scheduler'); +await scheduler.runAsExisting(connection); +const hub = await scheduler.addTaskHub('hub'); +await hub.withTaskHubName(hubName); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var connection = builder.AddParameter("scheduler-connection", secret: true); +var hubName = builder.AddParameter("task-hub-name"); + +var scheduler = builder.AddDurableTaskScheduler("scheduler") + .RunAsExisting(connection); +var hub = scheduler.AddTaskHub("hub") + .WithTaskHubName(hubName); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + connection = builder.add_parameter( + "scheduler-connection", + secret=True, + ) + hub_name = builder.add_parameter("task-hub-name") + + scheduler = builder.add_durable_task_scheduler("scheduler") + scheduler.run_as_existing(connection) + hub = scheduler.add_task_hub("hub") + hub.with_task_hub_name(hub_name) + + 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)) + } + + connection := builder.AddParameter( + "scheduler-connection", + &aspire.AddParameterOptions{Secret: aspire.BoolPtr(true)}) + hubName := builder.AddParameter("task-hub-name") + scheduler := builder. + AddDurableTaskScheduler("scheduler"). + RunAsExisting(connection) + hub := scheduler. + AddTaskHub("hub"). + WithTaskHubName(hubName) + for _, err := range []error{ + connection.Err(), + hubName.Err(), + scheduler.Err(), + hub.Err(), + } { + if 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var connection = builder.addParameter( + "scheduler-connection", + new AddParameterOptions().secret(true)); + var hubName = builder.addParameter("task-hub-name"); + + var scheduler = builder.addDurableTaskScheduler("scheduler"); + scheduler.runAsExisting(connection); + var hub = scheduler.addTaskHub("hub"); + hub.withTaskHubName(hubName); + + 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 connection = builder.add_parameter( + "scheduler-connection", + None, + None, + Some(true), + )?; + let hub_name = builder.add_parameter( + "task-hub-name", + None, + None, + None, + )?; + + let scheduler = builder.add_durable_task_scheduler("scheduler")?; + scheduler.run_as_existing(connection.handle().to_json())?; + let hub = scheduler.add_task_hub("hub")?; + hub.with_task_hub_name(hub_name.handle().to_json())?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the warning with any of the following methods: - Add a `#pragma warning disable` directive in the source file where the API is used: diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport001.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport001.mdx index 02ad6cac7..4dc54ab3d 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport001.mdx @@ -19,6 +19,8 @@ This diagnostic error is reported when a method decorated with the `[AspireExpor ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT001`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport002.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport002.mdx index 3162efb21..3501bd82e 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport002.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport002.mdx @@ -19,6 +19,8 @@ This diagnostic error is reported when the export ID specified in an `[AspireExp ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT002`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport003.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport003.mdx index 7e3eef91d..48ad1f4ee 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport003.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport003.mdx @@ -19,6 +19,8 @@ This diagnostic error is reported when a method decorated with `[AspireExport]` ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT003`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport004.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport004.mdx index ee4fdc8db..2c7beed20 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport004.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport004.mdx @@ -19,6 +19,8 @@ This diagnostic error is reported when a method decorated with `[AspireExport]` ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT004`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport005.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport005.mdx index 03222a71c..d67775614 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport005.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport005.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when an `[AspireUnion]` attribute is applied ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT005`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport006.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport006.mdx index 5db4815ed..2380c1496 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport006.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport006.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when one or more types specified in an `[Asp ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT006`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport007.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport007.mdx index 5b2e82bf6..e001ccd84 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport007.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport007.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when two or more `[AspireExport]` methods in ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT007`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport008.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport008.mdx index 5508af2ef..f5833e89c 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport008.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport008.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when a public static extension method extend ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT008`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport009.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport009.mdx index 0f02e3f63..d4b6b4fea 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport009.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport009.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when an `[AspireExport]` method targets the ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT009`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport010.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport010.mdx index 1df6f51b7..887749daf 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport010.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport010.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when an `[AspireExport]` method directly inv ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT010`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport013.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport013.mdx index ffcf1f8ca..a698691a2 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport013.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport013.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when two or more Aspire Type System (ATS) ex ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT013`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport015.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport015.mdx index cbaa12d27..ccdab8bf6 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport015.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport015.mdx @@ -19,6 +19,8 @@ This diagnostic error is reported when a new `[AspireExport]` attribute sets the ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT015`: ```csharp title="Integration.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireexport016.mdx b/src/frontend/src/content/docs/diagnostics/aspireexport016.mdx index e7f36577e..0e002bd62 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireexport016.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireexport016.mdx @@ -19,6 +19,8 @@ This diagnostic warning is reported when an `[AspireDto]` type exposes a mutable ## Example +These are C#-only integration-authoring examples. They define the .NET API surface used to generate AppHost SDKs, rather than configuring resources from a generated SDK. + The following code generates `ASPIREEXPORT016`: ```csharp title="MyDto.cs" diff --git a/src/frontend/src/content/docs/diagnostics/aspireextension001.mdx b/src/frontend/src/content/docs/diagnostics/aspireextension001.mdx index b34745ecf..988c28a4a 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireextension001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireextension001.mdx @@ -25,23 +25,29 @@ This diagnostic warning is reported when using experimental extension debugging These APIs are primarily used by integration authors to enable F5 debugging for custom resource types in Visual Studio and other IDEs. +:::note[Generated AppHost SDK applicability] +Generic debug launch configuration (`WithDebugSupport`), debug-support inspection, and `WithLaunchToolArgs` aren't exported to the generated AppHost SDKs. These C#-only integration-author APIs use .NET annotations and launch-configuration types; this warning and its suppression apply only to C#. +::: + `WithLaunchToolArgs` declares the tool-invocation prefix for the tool that hosts a program (for example `run ./cmd/api` for `go` launcher/CLI, or `-m flask` for `python` CLI), separating it from program-specific arguments a caller adds with `WithArgs`. This allows extensions and IDEs to vary host tool arguments as necessary to reach the desired execution mode of the program (e.g. running under debugger vs not, enabling or disabling profiling etc.) without affecting startup arguments the program sees itself. ## Example -The following code generates `ASPIREEXTENSION001`: +The following code generates `ASPIREEXTENSION001`. It assumes your integration defines `AddMyResource` and a `MyLaunchConfiguration` derived from `ExecutableLaunchConfiguration`. The callback receives the requested launch mode, not a process ID: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); builder.AddMyResource("resource") .WithDebugSupport( - processId => new MyLaunchConfiguration { ProcessId = processId }, + mode => new MyLaunchConfiguration { Mode = mode }, "myResourceType"); ``` ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirefilesystem001.mdx b/src/frontend/src/content/docs/diagnostics/aspirefilesystem001.mdx index 3b1ca7927..266c37370 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirefilesystem001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirefilesystem001.mdx @@ -24,6 +24,10 @@ This diagnostic warning is reported when using experimental file system service These APIs provide a centralized way to manage temporary directories and files used by Aspire, improving testability and consistency across the platform. +:::note[Generated AppHost SDK applicability] +`IFileSystemService`, `ITempFileSystemService`, and their disposable temporary-file and directory types aren't exported to the generated AppHost SDKs. This C#-only example resolves .NET services and calls `CreateTempSubdirectory` and `CreateTempFile`; it does not configure resource files through the generated SDK. This warning and its suppression apply only to C#. +::: + ## Example The following code generates `ASPIREFILESYSTEM001`: @@ -78,6 +82,8 @@ The `IFileSystemService` provides a standardized interface for managing temporar ## To suppress this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirehostingpython001.mdx b/src/frontend/src/content/docs/diagnostics/aspirehostingpython001.mdx index 672db1622..8dd5fcfe5 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirehostingpython001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirehostingpython001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler error ASPIREHOSTINGPYTHON001 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; `AddPythonApp` is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. -Aspire provides a way to add Python executables or applications to the Aspire AppHost with the `AddPythonApp` extension. Since the shape of this API is expected to change in the future, it's experimental. +Aspire provides a way to add Python executables or applications to the Aspire AppHost with the `AddPythonApp` extension. Earlier versions marked this API as experimental. In Aspire 13.5, the three-argument overload shown below is no longer marked experimental, so it doesn't require suppression. This page remains relevant for builds using older package versions. ## Example -The following code generates `ASPIREHOSTINGPYTHON001`: +Install the Python hosting integration: + + + +The C# example generates `ASPIREHOSTINGPYTHON001` in versions that mark `AddPythonApp` as experimental. The generated SDK examples host the same Python application and configure its HTTP endpoint and OpenTelemetry exporter without emitting a C# compiler diagnostic. + +For the daily CLI, matching packages, and feature flags required by Python, Go, Java, and Rust, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const pythonApp = await builder.addPythonApp("hello-python", "../hello-python", "main.py"); +await pythonApp.withHttpEndpoint({ env: "PORT" }); +await pythonApp.withExternalHttpEndpoints(); +await pythonApp.withOtlpExporter(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + var pythonApp = builder.AddPythonApp("hello-python", "../hello-python", "main.py") - .WithHttpEndpoint(env: "PORT") - .WithExternalHttpEndpoints() - .WithOtlpExporter(); + .WithHttpEndpoint(env: "PORT") + .WithExternalHttpEndpoints() + .WithOtlpExporter(); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + python_app = builder.add_python_app( + "hello-python", + "../hello-python", + "main.py", + ) + ( + python_app.with_http_endpoint(env="PORT") + .with_external_http_endpoints() + .with_otlp_exporter() + ) + + 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)) + } + + pythonApp := builder. + AddPythonApp("hello-python", "../hello-python", "main.py"). + WithHttpEndpoint(&aspire.WithHttpEndpointOptions{ + Env: aspire.StringPtr("PORT"), + }). + WithExternalHttpEndpoints(). + WithOtlpExporter() + if err := pythonApp.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var pythonApp = builder.addPythonApp( + "hello-python", + "../hello-python", + "main.py"); + pythonApp.withHttpEndpoint( + new WithHttpEndpointOptions().env("PORT")); + pythonApp.withExternalHttpEndpoints(); + pythonApp.withOtlpExporter(); + + builder.build().run(); +} +``` + + + + ## To correct this error -Suppress the error with either of the following methods: +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireinteraction001.mdx b/src/frontend/src/content/docs/diagnostics/aspireinteraction001.mdx index 642d63e12..d2b5bd9b7 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireinteraction001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireinteraction001.mdx @@ -5,28 +5,217 @@ description: Learn what causes the Aspire compiler warning ASPIREINTERACTION001 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; > Interaction service types and members are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. -This diagnostic warns when using the experimental `IInteractionService` interface and related interaction APIs. These APIs provide the ability to prompt users for input, request confirmation, and display messages in the Aspire dashboard or CLI during publish and deploy operations. +This diagnostic warns when using the experimental `IInteractionService` interface and related interaction APIs. These APIs provide the ability to prompt users for input in the Aspire dashboard or CLI during publish and deploy operations. Confirmation, message box, and notification interactions require the dashboard. ## Example generating diagnostic +The C# example generates `ASPIREINTERACTION001`. The TypeScript, Python, Go, and Java SDKs expose the interaction service through a service-provider handle without emitting this C# compiler diagnostic. The example registers a dashboard resource command so the service is resolved after the application has started. All APIs used here belong to the core `Aspire.Hosting` SDK; no additional integration package is required. Python, Go, and Java examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). + +) -> Value` callbacks, without converting their arguments to `ExecuteCommandContext`. This example requires that typed context to resolve the interaction service.", +}}> + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); +const cache = await builder.addContainer("cache", { image: "redis" }); + +await cache.withCommand("confirm", "Confirm action", async (context) => { + const services = await context.services(); + const interactionService = await services.getInteractionService(); + + if (!await interactionService.isAvailable()) { + return { success: false, errorMessage: "Interactive confirmation is unavailable." }; + } + + const result = await interactionService.promptConfirmation( + "Confirmation", + "Are you sure you want to proceed?" + ); + + return { success: !result.canceled && result.value === true }; +}); + +await builder.build().run(); +``` + + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -// Using IInteractionService from an IServiceProvider instance -var interactionService = serviceProvider.GetRequiredService(); -if (interactionService.IsAvailable) -{ - var result = await interactionService.PromptConfirmationAsync( - title: "Confirmation", - message: "Are you sure you want to proceed?"); +builder.AddContainer("cache", "redis") + .WithCommand("confirm", "Confirm action", async context => + { + var interactionService = context.Services + .GetRequiredService(); + + if (!interactionService.IsAvailable) + { + return new ExecuteCommandResult + { + Success = false, + ErrorMessage = "Interactive confirmation is unavailable." + }; + } + + var result = await interactionService.PromptConfirmationAsync( + title: "Confirmation", + message: "Are you sure you want to proceed?"); + + return new ExecuteCommandResult { Success = !result.Canceled && result.Data }; + }); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import ExecuteCommandContext, ExecuteCommandResult, create_builder + + +def confirm_action(context: ExecuteCommandContext) -> ExecuteCommandResult: + interaction_service = context.services.get_interaction_service() + if not interaction_service.is_available(): + return { + "Success": False, + "ErrorMessage": "Interactive confirmation is unavailable.", + } + + result = interaction_service.prompt_confirmation( + "Confirmation", + "Are you sure you want to proceed?", + ) + return { + "Success": not result["Canceled"] and result.get("Value") is True, + } + + +with create_builder() as builder: + cache = builder.add_container("cache", "redis") + cache.with_command("confirm", "Confirm action", confirm_action) + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + + "apphost/modules/aspire" +) + +func commandFailure(message string) *aspire.ExecuteCommandResult { + return &aspire.ExecuteCommandResult{ + Success: false, + ErrorMessage: aspire.StringPtr(message), + } +} + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + cache := builder.AddContainer("cache", "redis"). + WithCommand("confirm", "Confirm action", func(context aspire.ExecuteCommandContext) *aspire.ExecuteCommandResult { + services := context.Services() + if err := services.Err(); err != nil { + return commandFailure(err.Error()) + } + + interactionService := services.GetInteractionService() + if err := interactionService.Err(); err != nil { + return commandFailure(err.Error()) + } + + available, err := interactionService.IsAvailable() + if err != nil { + return commandFailure(err.Error()) + } + if !available { + return commandFailure("Interactive confirmation is unavailable.") + } + + result, err := interactionService.PromptConfirmation( + "Confirmation", + "Are you sure you want to proceed?", + ) + if err != nil { + return commandFailure(err.Error()) + } + + return &aspire.ExecuteCommandResult{ + Success: !result.Canceled && result.Value != nil && *result.Value, + } + }) + if err := cache.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var cache = builder.addContainer("cache", "redis"); + cache.withCommand("confirm", "Confirm action", context -> { + var interactionService = context.services().getInteractionService(); + var commandResult = new ExecuteCommandResult(); + + if (!interactionService.isAvailable()) { + commandResult.setSuccess(false); + commandResult.setErrorMessage( + "Interactive confirmation is unavailable."); + return commandResult; + } + + var result = interactionService.promptConfirmation( + "Confirmation", + "Are you sure you want to proceed?"); + commandResult.setSuccess( + !result.getCanceled() && Boolean.TRUE.equals(result.getValue())); + return commandResult; + }); + + builder.build().run(); +} +``` + + + + The diagnostic is triggered because `IInteractionService` is marked with the `[Experimental("ASPIREINTERACTION001")]` attribute. ## Understanding the diagnostic @@ -54,8 +243,12 @@ This API is experimental because the interaction patterns and API surface may ev - **`PromptInputAsync()`**: Prompts for a single input value - **`PromptInputsAsync()`**: Prompts for multiple input values +The TypeScript SDK exports `promptMessageBox`, `promptNotification`, `promptConfirmation`, `promptInput`, and `promptInputs` on the interaction-service handle. For input prompts, use its `createTextInput`, `createSecretInput`, `createChoiceInput`, `createBooleanInput`, or `createNumberInput` methods to create `InteractionInputBuilder` handles, rather than constructing C# `InteractionInput` objects. See [Interaction service](/extensibility/interaction-service/) for these workflows. + ## Suppressing the diagnostic +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + The diagnostic can be suppressed using one of several methods: ### Suppressing in .editorconfig @@ -77,10 +270,7 @@ dotnet_diagnostic.ASPIREINTERACTION001.severity = none ```csharp title="Suppressing the warning" #pragma warning disable ASPIREINTERACTION001 -var interactionService = serviceProvider.GetRequiredService(); -var result = await interactionService.PromptConfirmationAsync( - title: "Confirmation", - message: "Are you sure?"); +// Register the resource command from the example above. #pragma warning restore ASPIREINTERACTION001 ``` diff --git a/src/frontend/src/content/docs/diagnostics/aspirejavascript001.mdx b/src/frontend/src/content/docs/diagnostics/aspirejavascript001.mdx index ac3673fd4..bb6114328 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirejavascript001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirejavascript001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIREJAVASCRIPT001 a --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The C# examples generate `ASPIREJAVASCRIPT001`. The generated SDK examples configure the same publishing behavior without emitting this C# compiler diagnostic. + +For the daily CLI, matching packages, and feature flags required by Python, Go, Java, and Rust, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const frontend = await builder.addViteApp("frontend", "../MyViteApp"); +await frontend.publishAsStaticWebsite(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); var frontend = builder.AddViteApp("frontend", "../MyViteApp") .PublishAsStaticWebsite(); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + frontend = builder.add_vite_app("frontend", "../MyViteApp") + frontend.publish_as_static_website() + + 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. + AddViteApp("frontend", "../MyViteApp"). + PublishAsStaticWebsite() + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addViteApp("frontend", "../MyViteApp") + .publishAsStaticWebsite(); + + builder.build().run(); +} +``` + + + + +### Node.js and package script publishing + +For server applications, choose either a built Node.js entry point or a package script. These examples assume `../NodeServer` builds `dist/server.js` and `../ScriptServer` defines a `start` script. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const nodeServer = await builder.addJavaScriptApp("node-server", "../NodeServer"); +await nodeServer.publishAsNodeServer("dist/server.js", { outputPath: "dist" }); + +const scriptServer = await builder.addJavaScriptApp("script-server", "../ScriptServer"); +await scriptServer.publishAsPackageScript({ scriptName: "start" }); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddJavaScriptApp("node-server", "../NodeServer") + .PublishAsNodeServer("dist/server.js", outputPath: "dist"); + +builder.AddJavaScriptApp("script-server", "../ScriptServer") + .PublishAsPackageScript(scriptName: "start"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + node_server = builder.add_java_script_app( + "node-server", + "../NodeServer", + ) + node_server.publish_as_node_server( + "dist/server.js", + output_path="dist", + ) + + script_server = builder.add_java_script_app( + "script-server", + "../ScriptServer", + ) + script_server.publish_as_package_script(script_name="start") + + 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)) + } + + nodeServer := builder. + AddJavaScriptApp("node-server", "../NodeServer"). + PublishAsNodeServer( + "dist/server.js", + &aspire.PublishAsNodeServerOptions{ + OutputPath: aspire.StringPtr("dist"), + }, + ) + if err := nodeServer.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + scriptServer := builder. + AddJavaScriptApp("script-server", "../ScriptServer"). + PublishAsPackageScript(&aspire.PublishAsPackageScriptOptions{ + ScriptName: aspire.StringPtr("start"), + }) + if err := scriptServer.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addJavaScriptApp("node-server", "../NodeServer") + .publishAsNodeServer("dist/server.js", "dist"); + + builder.addJavaScriptApp("script-server", "../ScriptServer") + .publishAsPackageScript( + new PublishAsPackageScriptOptions().scriptName("start")); + + builder.build().run(); +} +``` + + + + +### Next.js build validation + +The Next.js integration checks its configuration before publishing, including whether `output: "standalone"` is set. Disable those checks only when the configuration is supplied dynamically or by an external mechanism that static file inspection can't detect: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const web = await builder.addNextJsApp("web", "../NextApp"); +await web.disableBuildValidation(); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddNextJsApp("web", "../NextApp") + .DisableBuildValidation(); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + web = builder.add_next_js_app("web", "../NextApp") + web.disable_build_validation() + + 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. + AddNextJsApp("web", "../NextApp"). + DisableBuildValidation() + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addNextJsApp("web", "../NextApp") + .disableBuildValidation(); + + builder.build().run(); +} +``` + + + + ## To correct this warning -Suppress the warning with either of the following methods: +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspiremcp001.mdx b/src/frontend/src/content/docs/diagnostics/aspiremcp001.mdx index de390167a..f4c9a132d 100644 --- a/src/frontend/src/content/docs/diagnostics/aspiremcp001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspiremcp001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREMCP001 and how --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const api = await builder.addProject("api", "../MyApi/MyApi.csproj"); +await api.withMcpServer(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -35,9 +55,113 @@ var api = builder.AddProject("api") 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") + api.with_mcp_server() + + 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"). + WithMcpServer() + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var api = builder.addProject("api", "../MyApi/MyApi.csproj"); + api.withMcpServer(); + + 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", "../MyApi/MyApi.csproj", None)?; + api.with_mcp_server(None, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + —or— -The following code uses a custom path and endpoint name: +The following code uses a custom path and endpoint name. The API project must expose an endpoint named `https` and serve MCP at `/sse`: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const api = await builder.addProject("api", "../MyApi/MyApi.csproj"); +await api.withMcpServer({ path: "/sse", endpointName: "https" }); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -48,8 +172,102 @@ var api = builder.AddProject("api") 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") + api.with_mcp_server(path="/sse", endpoint_name="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)) + } + + api := builder.AddProject("api", "../MyApi/MyApi.csproj"). + WithMcpServer(&aspire.WithMcpServerOptions{ + Path: aspire.StringPtr("/sse"), + EndpointName: aspire.StringPtr("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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var api = builder.addProject("api", "../MyApi/MyApi.csproj"); + api.withMcpServer(new WithMcpServerOptions() + .path("/sse") + .endpointName("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("api", "../MyApi/MyApi.csproj", None)?; + api.with_mcp_server(Some("/sse"), Some("https"))?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx b/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx index 5b5044e51..cc64a1295 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler error ASPIREPERSISTENCE001 an --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const worker = await builder.addExecutable("worker", "node", "../worker", ["server.js"]); +await worker.withPersistentLifetime(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -39,10 +61,215 @@ var worker = builder.AddExecutable("worker", "node", "../worker", "server.js") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + worker = builder.add_executable("worker", "node", "../worker", ["server.js"]) + worker.with_persistent_lifetime() + + 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. + AddExecutable("worker", "node", "../worker", []string{"server.js"}). + WithPersistentLifetime() + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addExecutable("worker", "node", "../worker", new String[] { "server.js" }) + .withPersistentLifetime(); + + builder.build().run(); +} +``` + + + + +The same shared lifetime APIs also support session, parent-process, and resource-scoped lifetimes. Choose the lifetime appropriate to each resource. Replace `12345` with the ID of the process that should own the parent-process resource. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const sessionWorker = await builder.addExecutable("session-worker", "node", "../worker", ["server.js"]); +await sessionWorker.withSessionLifetime(); + +const parentWorker = await builder.addExecutable("parent-worker", "node", "../worker", ["server.js"]); +await parentWorker.withParentProcessLifetime(12345); + +const helper = await builder.addExecutable("helper", "node", "../worker", ["helper.js"]); +await helper.withLifetimeOf(sessionWorker); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var sessionWorker = builder.AddExecutable("session-worker", "node", "../worker", "server.js") + .WithSessionLifetime(); + +var parentWorker = builder.AddExecutable("parent-worker", "node", "../worker", "server.js") + .WithParentProcessLifetime(12345); + +var helper = builder.AddExecutable("helper", "node", "../worker", "helper.js") + .WithLifetimeOf(sessionWorker); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + session_worker = builder.add_executable("session-worker", "node", "../worker", ["server.js"]) + session_worker.with_session_lifetime() + + parent_worker = builder.add_executable("parent-worker", "node", "../worker", ["server.js"]) + parent_worker.with_parent_process_lifetime(12345) + + helper = builder.add_executable("helper", "node", "../worker", ["helper.js"]) + helper.with_lifetime_of(session_worker) + + 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)) + } + + sessionWorker := builder. + AddExecutable("session-worker", "node", "../worker", []string{"server.js"}). + WithSessionLifetime() + if err := sessionWorker.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + parentWorker := builder. + AddExecutable("parent-worker", "node", "../worker", []string{"server.js"}). + WithParentProcessLifetime(12345) + if err := parentWorker.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + helper := builder. + AddExecutable("helper", "node", "../worker", []string{"helper.js"}). + WithLifetimeOf(sessionWorker) + if err := helper.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var sessionWorker = builder.addExecutable( + "session-worker", "node", "../worker", new String[] { "server.js" }); + sessionWorker.withSessionLifetime(); + + builder.addExecutable("parent-worker", "node", "../worker", new String[] { "server.js" }) + .withParentProcessLifetime(12345); + + builder.addExecutable("helper", "node", "../worker", new String[] { "helper.js" }) + .withLifetimeOf(sessionWorker); + + builder.build().run(); +} +``` + + + + ## To correct this error Suppress the diagnostic when you intentionally opt in to the experimental shared lifetime APIs. For container resources, you can instead use the supported container-specific `WithLifetime(ContainerLifetime.Persistent)` and `WithLifetime(ContainerLifetime.Session)` APIs. +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx b/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx index eccee9703..7ee2087e3 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepipelines001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler error ASPIREPIPELINES001 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; ) -> Value` callbacks, without converting their arguments to `PipelineStepContext`. This example requires that typed context to obtain the reporting-step and reporting-task handles.", +}}> + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const pipeline = await builder.pipeline(); +await pipeline.addStep("report-status", async context => { + const step = await context.reportingStep(); + const task = await step.createTask("Checking deployment prerequisites"); + await task.completeTask("Prerequisites checked"); +}); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.Pipeline.AddStep("report-status", async context => +{ + await using var task = await context.ReportingStep.CreateTaskAsync( + "Checking deployment prerequisites"); + await task.CompleteAsync("Prerequisites checked"); +}); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import PipelineStepContext, create_builder + + +def report_status(context: PipelineStepContext) -> None: + task = context.reporting_step.create_task( + "Checking deployment prerequisites" + ) + task.complete_task(completion_message="Prerequisites checked") + + +with create_builder() as builder: + builder.pipeline.add_step("report-status", report_status) + + 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)) + } + + pipeline := builder.Pipeline() + if err := pipeline.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + err = pipeline.AddStep("report-status", func(context aspire.PipelineStepContext) { + step := context.ReportingStep() + if err := step.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + task := step.CreateTask("Checking deployment prerequisites") + if err := task.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + if err := task.CompleteTask(&aspire.CompleteTaskOptions{ + CompletionMessage: aspire.StringPtr("Prerequisites checked"), + }); err != nil { + log.Fatal(aspire.FormatError(err)) + } + }) + if 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.pipeline().addStep("report-status", context -> { + var task = context.reportingStep().createTask( + "Checking deployment prerequisites"); + task.completeTask(new CompleteTaskOptions() + .completionMessage("Prerequisites checked")); + }); + + builder.build().run(); +} +``` + + + + +Run the registered step with `aspire do report-status`. Implementing .NET interfaces such as `IPipelineActivityReporter`, or constructing `PublishingContext` and `PublishingCallbackAnnotation` directly, remains a C#-only extensibility operation; the generated SDKs do not expose those construction APIs. + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx b/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx index 9e61a754b..219e4bb29 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepipelines002.mdx @@ -32,8 +32,12 @@ This diagnostic applies to the following deployment state manager APIs: - Azure provisioning context providers - Related extension methods and implementations +The deployment-state service, `PublishingOptions` properties, and callback annotations listed here are C#-only .NET infrastructure APIs, not exported AppHost operations. AppHosts using generated SDKs can participate in deployment pipelines, but do not directly resolve `IDeploymentStateManager` or construct these annotations, and do not emit this C# compiler diagnostic. + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepipelines003.mdx b/src/frontend/src/content/docs/diagnostics/aspirepipelines003.mdx index 4c3e3ac86..014fe72dd 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepipelines003.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepipelines003.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler error ASPIREPIPELINES003 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; ) -> Value` callbacks, without converting their arguments to `ContainerBuildOptionsCallbackContext`. These examples require the typed callback-context API.", +}}> + + +```typescript title="apphost.mts" twoslash +import { + ContainerImageFormat, + ContainerTargetPlatform, + createBuilder, +} from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const api = await builder.addDockerfile("api", "../Api"); +await api.withContainerBuildOptions(async context => { + await context.setTargetPlatform(ContainerTargetPlatform.LinuxAmd64); + await context.setImageFormat(ContainerImageFormat.Oci); +}); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +using Aspire.Hosting.Publishing; + +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddDockerfile("api", "../Api") + .WithContainerBuildOptions(context => + { + context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64; + context.ImageFormat = ContainerImageFormat.Oci; + return Task.CompletedTask; + }); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import ContainerBuildOptionsCallbackContext, create_builder + + +def configure_build(context: ContainerBuildOptionsCallbackContext) -> None: + context.target_platform = "LinuxAmd64" + context.image_format = "Oci" + + +with create_builder() as builder: + api = builder.add_dockerfile("api", "../Api") + api.with_container_build_options(configure_build) + + 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.AddDockerfile("api", "../Api"). + WithContainerBuildOptions(func(context aspire.ContainerBuildOptionsCallbackContext) { + platform := aspire.ContainerTargetPlatformLinuxAmd64 + format := aspire.ContainerImageFormatOci + context. + SetTargetPlatform(&platform). + SetImageFormat(&format) + if err := context.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var api = builder.addDockerfile("api", "../Api"); + api.withContainerBuildOptions(context -> { + context.setTargetPlatform(ContainerTargetPlatform.LINUX_AMD64); + context.setImageFormat(ContainerImageFormat.OCI); + }); + + builder.build().run(); +} +``` + + + + +The C# build configuration APIs generate `ASPIREPIPELINES003`; the TypeScript, Python, Go, and Java SDKs expose the same experimental AppHost configuration without emitting this C# compiler diagnostic. Implementing `IResourceContainerImageBuilder` or a container runtime remains .NET extensibility work, not an AppHost operation in these SDKs. + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepipelines004.mdx b/src/frontend/src/content/docs/diagnostics/aspirepipelines004.mdx index a6e157929..3e275cf68 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepipelines004.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepipelines004.mdx @@ -27,6 +27,8 @@ This API is experimental because the pipeline infrastructure is under active dev ## Example +`IPipelineOutputService`, its implementation, and its directory methods are .NET infrastructure APIs and are not exported to the generated AppHost SDKs. Resolving this service from the dependency-injection container is therefore a C#-only operation. + The following code generates `ASPIREPIPELINES004`: ```csharp title="AppHost.cs" @@ -49,6 +51,8 @@ var tempDir = outputService.GetTempDirectory(); ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepostgres001.mdx b/src/frontend/src/content/docs/diagnostics/aspirepostgres001.mdx index 2d9486bd5..12011eb9d 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepostgres001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepostgres001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIREPOSTGRES001 — --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The following examples add an MCP server to a PostgreSQL database. Both `Aspire.Hosting.PostgreSQL` and `Aspire.Hosting.Azure.PostgreSQL` export the MCP operation to generated AppHost SDKs, even though only the C# compiler reports `ASPIREPOSTGRES001`. + +For the daily CLI, matching packages, and feature flags required by Python, Go, Java, and Rust, see [Experimental AppHost examples](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const postgres = await builder.addPostgres("postgres"); +const db = await postgres.addDatabase("mydb"); +await db.withPostgresMcp(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs (PostgreSQL)" var builder = DistributedApplication.CreateBuilder(args); @@ -36,9 +62,125 @@ var db = builder.AddPostgres("postgres") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + postgres = builder.add_postgres("postgres") + db = postgres.add_database("mydb") + db.with_postgres_mcp() + + 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)) + } + + db := builder. + AddPostgres("postgres"). + AddDatabase("mydb"). + WithPostgresMcp() + if err := db.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addPostgres("postgres") + .addDatabase("mydb") + .withPostgresMcp(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let postgres = builder.add_postgres("postgres", None, None, None)?; + let db = postgres.add_database("mydb", None)?; + db.with_postgres_mcp(|_| Value::Null, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + —or— -The following code generates `ASPIREPOSTGRES001` when using an Azure PostgreSQL Flexible Server database resource: +Install the Azure PostgreSQL hosting integration to use an Azure PostgreSQL Flexible Server database: + + + +Run the server as a local container before enabling its MCP server. The C# `WithPostgresMcp` call also generates `ASPIREPOSTGRES001` in this example. The generated SDKs expose the same operation on `AzurePostgresFlexibleServerDatabaseResource`. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const postgres = await builder.addAzurePostgresFlexibleServer("postgres"); +await postgres.runAsContainer(); +const db = await postgres.addDatabase("mydb"); +await db.withPostgresMcp(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs (Azure PostgreSQL)" var builder = DistributedApplication.CreateBuilder(args); @@ -51,8 +193,107 @@ var db = builder.AddAzurePostgresFlexibleServer("postgres") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + postgres = builder.add_azure_postgres_flexible_server("postgres") + postgres.run_as_container() + db = postgres.add_database("mydb") + db.with_postgres_mcp() + + 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)) + } + + db := builder. + AddAzurePostgresFlexibleServer("postgres"). + RunAsContainer(). + AddDatabase("mydb"). + WithPostgresMcp() + if err := db.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addAzurePostgresFlexibleServer("postgres") + .runAsContainer() + .addDatabase("mydb") + .withPostgresMcp(); + + builder.build().run(); +} +``` + + + + +```rust title="apphost.rs" +#[path = ".aspire/modules/mod.rs"] +mod aspire; + +use aspire::*; +use serde_json::Value; + +fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + let postgres = builder.add_azure_postgres_flexible_server("postgres")?; + postgres.run_as_container(|_| Value::Null)?; + let db = postgres.add_database("mydb", None)?; + db.with_postgres_mcp(|_| Value::Null, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#; generated AppHost SDKs use the same API without the compiler warning. + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireprobes001.mdx b/src/frontend/src/content/docs/diagnostics/aspireprobes001.mdx index ed8c0fbf2..cb080e93e 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireprobes001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireprobes001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREPROBES001 and h --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder, ProbeType } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const api = await builder.addProject("api", "../Api/Api.csproj"); +await api.withHttpProbe(ProbeType.Readiness, { path: "/health/ready" }); + +const worker = await builder.addProject("worker", "../Worker/Worker.csproj"); +await worker.withHttpProbe(ProbeType.Liveness, { path: "/health/live" }); + +const service = await builder.addProject("service", "../Service/Service.csproj"); +await service.withHttpProbe(ProbeType.Startup, { + path: "/health/startup", + initialDelaySeconds: 15, + failureThreshold: 10, +}); +await service.withHttpProbe(ProbeType.Readiness, { + path: "/health/ready", + periodSeconds: 5, + timeoutSeconds: 3, +}); +await service.withHttpProbe(ProbeType.Liveness, { + path: "/health/live", + periodSeconds: 30, + failureThreshold: 3, +}); + +await builder.build().run(); +``` + + + ```csharp title="Using HTTP probes" var builder = DistributedApplication.CreateBuilder(args); @@ -52,9 +92,216 @@ var service = builder.AddProject("service") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_project("api", "../Api/Api.csproj") + api.with_http_probe("Readiness", path="/health/ready") + + worker = builder.add_project("worker", "../Worker/Worker.csproj") + worker.with_http_probe("Liveness", path="/health/live") + + service = builder.add_project("service", "../Service/Service.csproj") + ( + service.with_http_probe( + "Startup", + path="/health/startup", + initial_delay_seconds=15, + failure_threshold=10, + ) + .with_http_probe( + "Readiness", + path="/health/ready", + period_seconds=5, + timeout_seconds=3, + ) + .with_http_probe( + "Liveness", + path="/health/live", + period_seconds=30, + failure_threshold=3, + ) + ) + + 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", "../Api/Api.csproj"). + WithHttpProbe(aspire.ProbeTypeReadiness, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/health/ready"), + }) + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + worker := builder.AddProject("worker", "../Worker/Worker.csproj"). + WithHttpProbe(aspire.ProbeTypeLiveness, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/health/live"), + }) + if err := worker.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + service := builder.AddProject("service", "../Service/Service.csproj"). + WithHttpProbe(aspire.ProbeTypeStartup, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/health/startup"), + InitialDelaySeconds: aspire.Float64Ptr(15), + FailureThreshold: aspire.Float64Ptr(10), + }). + WithHttpProbe(aspire.ProbeTypeReadiness, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/health/ready"), + PeriodSeconds: aspire.Float64Ptr(5), + TimeoutSeconds: aspire.Float64Ptr(3), + }). + WithHttpProbe(aspire.ProbeTypeLiveness, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/health/live"), + PeriodSeconds: aspire.Float64Ptr(30), + FailureThreshold: aspire.Float64Ptr(3), + }) + if err := service.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var api = builder.addProject("api", "../Api/Api.csproj"); + api.withHttpProbe( + ProbeType.READINESS, + new WithHttpProbeOptions().path("/health/ready")); + + var worker = builder.addProject("worker", "../Worker/Worker.csproj"); + worker.withHttpProbe( + ProbeType.LIVENESS, + new WithHttpProbeOptions().path("/health/live")); + + var service = builder.addProject( + "service", + "../Service/Service.csproj"); + service.withHttpProbe( + ProbeType.STARTUP, + new WithHttpProbeOptions() + .path("/health/startup") + .initialDelaySeconds(15) + .failureThreshold(10)); + service.withHttpProbe( + ProbeType.READINESS, + new WithHttpProbeOptions() + .path("/health/ready") + .periodSeconds(5) + .timeoutSeconds(3)); + service.withHttpProbe( + ProbeType.LIVENESS, + new WithHttpProbeOptions() + .path("/health/live") + .periodSeconds(30) + .failureThreshold(3)); + + 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", "../Api/Api.csproj", None)?; + api.with_http_probe( + ProbeType::Readiness, + Some("/health/ready"), + None, None, None, None, None, None, + )?; + + let worker = builder.add_project( + "worker", + "../Worker/Worker.csproj", + None, + )?; + worker.with_http_probe( + ProbeType::Liveness, + Some("/health/live"), + None, None, None, None, None, None, + )?; + + let service = builder.add_project( + "service", + "../Service/Service.csproj", + None, + )?; + service.with_http_probe( + ProbeType::Startup, + Some("/health/startup"), + Some(15.0), None, None, Some(10.0), None, None, + )?; + service.with_http_probe( + ProbeType::Readiness, + Some("/health/ready"), + None, Some(5.0), Some(3.0), None, None, None, + )?; + service.with_http_probe( + ProbeType::Liveness, + Some("/health/live"), + None, Some(30.0), None, Some(3.0), None, None, + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## Understanding health probes -Health probes allow you to configure monitoring for your resources' health state. Aspire supports three types of probes that align with Kubernetes health check concepts: +Health probes allow you to configure monitoring for your resources' health state. `WithHttpProbe`/`withHttpProbe` adds both a deployment probe annotation and an HTTP health check used during local runs. Aspire supports three probe types that align with Kubernetes health check concepts. Startup ordering, traffic routing, and automatic restarts described below are deployment-platform behaviors, not promises that the local AppHost will restart or stop routing to an unhealthy resource: ### Probe types @@ -97,6 +344,29 @@ Health probes allow you to configure monitoring for your resources' health state ### Custom endpoint targeting +Select an endpoint by name in any supported AppHost language: + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder, ProbeType } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const api = await builder.addProject("api", "../Api/Api.csproj"); +await api.withHttpEndpoint({ port: 8080, name: "management" }); +await api.withHttpProbe(ProbeType.Readiness, { + path: "/actuator/health", + endpointName: "management", +}); + +await builder.build().run(); +``` + + + + ```csharp title="Probe specific endpoint" var builder = DistributedApplication.CreateBuilder(args); @@ -106,40 +376,302 @@ var api = builder.AddProject("api") .WithHttpProbe(ProbeType.Readiness, "/actuator/health", endpointName: "management"); -// Probe with endpoint selector function -var service = builder.AddProject("service") - .WithHttpProbe(ProbeType.Liveness, "/status", - endpointSelector: () => service.GetEndpoint("https")); +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_project("api", "../Api/Api.csproj") + api.with_http_endpoint(port=8080, name="management") + api.with_http_probe( + "Readiness", + path="/actuator/health", + endpoint_name="management", + ) + + 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", "../Api/Api.csproj"). + WithHttpEndpoint(&aspire.WithHttpEndpointOptions{ + Port: aspire.Float64Ptr(8080), + Name: aspire.StringPtr("management"), + }). + WithHttpProbe(aspire.ProbeTypeReadiness, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/actuator/health"), + EndpointName: aspire.StringPtr("management"), + }) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var api = builder.addProject("api", "../Api/Api.csproj"); + api.withHttpEndpoint(new WithHttpEndpointOptions() + .port(8080) + .name("management")); + api.withHttpProbe( + ProbeType.READINESS, + new WithHttpProbeOptions() + .path("/actuator/health") + .endpointName("management")); + + 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", "../Api/Api.csproj", None)?; + api.with_http_endpoint( + Some(8080.0), + None, + Some("management"), + None, + None, + )?; + api.with_http_probe( + ProbeType::Readiness, + Some("/actuator/health"), + None, None, None, None, None, + Some("management"), + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +The overload accepting an endpoint-selector callback is C#-only; the generated SDKs use the endpoint-name option instead. When using a C# callback, create the resource before capturing it: + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var service = builder.AddProject("service"); +service.WithHttpProbe( + ProbeType.Liveness, + endpointSelector: () => service.GetEndpoint("https"), + path: "/status"); builder.Build().Run(); ``` ### Integration with resource dependencies -Probes work seamlessly with resource wait dependencies: +The HTTP health checks registered by probes participate in local resource wait dependencies. In this example, the frontend waits until the API's readiness probe succeeds. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder, ProbeType } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const api = await builder.addProject("api", "../Api/Api.csproj"); +await api.withHttpProbe(ProbeType.Readiness, { path: "/health/ready" }); + +const frontend = await builder.addProject("frontend", "../Frontend/Frontend.csproj"); +await frontend.waitFor(api); + +await builder.build().run(); +``` + + + ```csharp title="Probes with dependencies" var builder = DistributedApplication.CreateBuilder(args); -var database = builder.AddPostgres("postgres"); -var cache = builder.AddRedis("redis"); - -// API with probes that check dependencies var api = builder.AddProject("api") - .WithHttpProbe(ProbeType.Readiness, "/health/ready") - .WaitFor(database) - .WaitFor(cache) - .WithReference(database) - .WithReference(cache); + .WithHttpProbe(ProbeType.Readiness, "/health/ready"); -// Frontend waits for API to be ready (not just started) var frontend = builder.AddProject("frontend") - .WaitFor(api) // Waits for API readiness probe to pass - .WithReference(api); + .WaitFor(api); builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + api = builder.add_project("api", "../Api/Api.csproj") + api.with_http_probe("Readiness", path="/health/ready") + + frontend = builder.add_project( + "frontend", + "../Frontend/Frontend.csproj", + ) + frontend.wait_for(api) + + 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", "../Api/Api.csproj"). + WithHttpProbe(aspire.ProbeTypeReadiness, &aspire.WithHttpProbeOptions{ + Path: aspire.StringPtr("/health/ready"), + }) + if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + frontend := builder.AddProject( + "frontend", + "../Frontend/Frontend.csproj", + ).WaitFor(api) + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var api = builder.addProject("api", "../Api/Api.csproj"); + api.withHttpProbe( + ProbeType.READINESS, + new WithHttpProbeOptions().path("/health/ready")); + + var frontend = builder.addProject( + "frontend", + "../Frontend/Frontend.csproj"); + frontend.waitFor(api); + + 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", "../Api/Api.csproj", None)?; + api.with_http_probe( + ProbeType::Readiness, + Some("/health/ready"), + None, None, None, None, None, None, + )?; + + let frontend = builder.add_project( + "frontend", + "../Frontend/Frontend.csproj", + None, + )?; + let api_resource = IResource::new( + api.handle().clone(), + api.client().clone(), + ); + frontend.wait_for(&api_resource, None)?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## Deployment integration Probes are automatically configured when deploying to supported compute environments: @@ -150,6 +682,8 @@ Probes are automatically configured when deploying to supported compute environm ## To suppress this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireprocesscommand001.mdx b/src/frontend/src/content/docs/diagnostics/aspireprocesscommand001.mdx index ca1b2ac3c..770df30d5 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireprocesscommand001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireprocesscommand001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREPROCESSCOMMAND0 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const cache = await builder.addContainer("cache", { image: "redis" }); +await cache.withProcessCommand("dotnet-version", "Show .NET version", { + executablePath: "dotnet", + arguments: ["--version"], +}); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -builder.AddRedis("cache") +builder.AddContainer("cache", "redis") .WithProcessCommand( commandName: "dotnet-version", displayName: "Show .NET version", @@ -40,10 +61,127 @@ builder.AddRedis("cache") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import ProcessCommandExportOptions, create_builder + +with create_builder() as builder: + cache = builder.add_container("cache", "redis") + options: ProcessCommandExportOptions = { + "ExecutablePath": "dotnet", + "Arguments": ["--version"], + } + cache.with_process_command("dotnet-version", "Show .NET version", options) + + 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)) + } + + cache := builder.AddContainer("cache", "redis"). + WithProcessCommand( + "dotnet-version", + "Show .NET version", + &aspire.ProcessCommandExportOptions{ + ExecutablePath: aspire.StringPtr("dotnet"), + Arguments: []string{"--version"}, + }, + ) + if err := cache.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var options = new ProcessCommandExportOptions(); + options.setExecutablePath("dotnet"); + options.setArguments(new String[] { "--version" }); + + var cache = builder.addContainer("cache", "redis"); + cache.withProcessCommand( + "dotnet-version", + "Show .NET version", + options); + + 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 cache = builder.add_container("cache", serde_json::json!("redis"))?; + cache.with_process_command( + "dotnet-version", + "Show .NET version", + ProcessCommandExportOptions { + executable_path: Some("dotnet".to_string()), + arguments: vec!["--version".to_string()], + ..Default::default() + }, + )?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +TypeScript passes process settings in `ProcessCommandExportOptions`, rather than the positional C# overload. Its `createProcessSpec` option supports dynamically created process specifications; the older `withProcessCommandFactory` export is obsolete. + The diagnostic is triggered because `WithProcessCommand`, `ProcessCommandSpec`, and `ProcessCommandOptions` are all marked with the `[Experimental("ASPIREPROCESSCOMMAND001")]` attribute. ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. @@ -67,7 +205,7 @@ Suppress the warning with one of the following methods: ```csharp title="Suppressing the warning" #pragma warning disable ASPIREPROCESSCOMMAND001 - builder.AddRedis("cache") + builder.AddContainer("cache", "redis") .WithProcessCommand("dotnet-version", "Show .NET version", "dotnet", ["--version"]); #pragma warning restore ASPIREPROCESSCOMMAND001 ``` diff --git a/src/frontend/src/content/docs/diagnostics/aspireprojects001.mdx b/src/frontend/src/content/docs/diagnostics/aspireprojects001.mdx index c0552d53c..a1026ec13 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireprojects001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireprojects001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREPROJECTS001 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const web = await builder.addProject("web", "../Web/Web.csproj"); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var web = builder.AddProject("web"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_project("web", "../Web/Web.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)) + } + + web := builder.AddProject("web", "../Web/Web.csproj") + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addProject("web", "../Web/Web.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)?; + + builder.add_project("web", "../Web/Web.csproj", None)?; + + builder.build()?.run(None)?; + Ok(()) +} +``` + + + + +`WithProjectDefaults` and its annotations aren't exported to the generated AppHost SDKs. They are C#-only APIs for custom .NET integration authors implementing resources that carry `IProjectMetadata`. The following operation generates `ASPIREPROJECTS001` and isn't equivalent to adding a normal project resource: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -37,6 +146,8 @@ builder.AddResource(new MyDotnetResource("resource")) ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireproxyendpoints001.mdx b/src/frontend/src/content/docs/diagnostics/aspireproxyendpoints001.mdx index 8d82e36ab..189da75ab 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireproxyendpoints001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireproxyendpoints001.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler error ASPIREPROXYENDPOINTS001 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The C# example generates `ASPIREPROXYENDPOINTS001`. The generated SDK examples configure the same Redis endpoint proxy behavior without emitting this C# compiler diagnostic. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from './.aspire/modules/aspire.mjs'; + +const builder = await createBuilder(); + +const redis = await builder.addRedis("example-redis", { port: 1234 }); +await redis.withEndpointProxySupport(false); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -var redis = builder.AddRedis($"example-redis", 1234) - .WithEndpointProxySupport(false); +var redis = builder.AddRedis("example-redis", 1234) + .WithEndpointProxySupport(false); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + redis = builder.add_redis("example-redis", port=1234) + redis.with_endpoint_proxy_support(False) + + 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)) + } + + redis := builder. + AddRedis("example-redis", &aspire.AddRedisOptions{ + Port: aspire.Float64Ptr(1234), + }). + WithEndpointProxySupport(false) + if err := redis.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addRedis("example-redis", new AddRedisOptions().port(1234)) + .withEndpointProxySupport(false); + + 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 redis = builder.add_redis("example-redis", Some(1234.0), None)?; + redis.with_endpoint_proxy_support(false)?; + + builder.build()?.run(None)?; + Ok(()) +} ``` + + + ## To correct this error -Suppress the error with either of the following methods: +The following suppression options apply to C# AppHosts. For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + +Suppress the error with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspirepublishers001.mdx b/src/frontend/src/content/docs/diagnostics/aspirepublishers001.mdx index 01df2c475..477bd7795 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirepublishers001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirepublishers001.mdx @@ -15,12 +15,16 @@ import { Badge } from '@astrojs/starlight/components'; > Publishers are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. -Aspire introduced the concept of _Publishers_ starting in version 9.2. Publishers play a pivotal role in the deployment process, enabling the transformation of your distributed app into deployable assets. This alleviates the intermediate step of producing the publishing [manifest](/architecture/resource-publishing/) for tools to act on, instead empowering the developer to express their intent directly in C#. +Aspire introduced the concept of _Publishers_ starting in version 9.2. Publishers transform your distributed app into deployable assets. This avoids the intermediate step of producing a publishing [manifest](/architecture/resource-publishing/) for tools to act on, letting you express deployment intent in your AppHost. Publishers are considered experimental and are expected to change in the future. +This diagnostic concerns C#-only publisher infrastructure and registration APIs. Implementing a custom .NET publisher is not an exported AppHost operation. AppHosts across languages can configure supported deployment targets through [compute environment integrations](/diagnostics/aspirecompute001/); generated language SDKs do not emit or suppress this C# compiler diagnostic. + ## To correct this error +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the error with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireradius003.mdx b/src/frontend/src/content/docs/diagnostics/aspireradius003.mdx index cb0d5e7d5..809858a71 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireradius003.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireradius003.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIRERADIUS003 and h --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +Creating a Radius environment is supported across AppHost languages. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). The cloud-provider callbacks are a separate, C#-only operation shown after these setup examples. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +await builder.addRadiusEnvironment("radius"); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddRadiusEnvironment("radius"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_radius_env("radius") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddRadiusEnvironment("radius") + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addRadiusEnvironment("radius"); + + 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)?; + + builder.add_radius_environment("radius")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +### Configure cloud providers in C# + +`WithAzureProvider`, `WithAwsProvider`, and their credential-builder callbacks are not exported to the TypeScript SDK catalog for `Aspire.Hosting.Radius` or to the daily Python, Go, Java, and Rust SDKs used for this documentation. Configuring providers through these APIs requires a C# AppHost: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -44,6 +160,8 @@ builder.Build().Run(); ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). The warning is only emitted by C#; the generated language SDKs do not export the provider callbacks that trigger it. + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireradius004.mdx b/src/frontend/src/content/docs/diagnostics/aspireradius004.mdx index 5341a5af7..c992959f6 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireradius004.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireradius004.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIRERADIUS004 and h --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +Creating a Radius environment is supported across AppHost languages. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). Infrastructure customization is a separate, C#-only operation shown after these setup examples. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +await builder.addRadiusEnvironment("radius"); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddRadiusEnvironment("radius"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_radius_env("radius") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddRadiusEnvironment("radius") + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addRadiusEnvironment("radius"); + + 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)?; + + builder.add_radius_environment("radius")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +### Customize infrastructure in C# + +The `ConfigureRadiusInfrastructure` callback and its construct types are not exported to the TypeScript SDK catalog for `Aspire.Hosting.Radius` or to the daily Python, Go, Java, and Rust SDKs used for this documentation. This customization requires a C# AppHost: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -40,6 +156,8 @@ builder.Build().Run(); ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). The warning is only emitted by C#; the generated language SDKs do not export the callback or its construct types. + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireradius006.mdx b/src/frontend/src/content/docs/diagnostics/aspireradius006.mdx index 6dbf23c43..1d3286765 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireradius006.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireradius006.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIRERADIUS006 and h --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +Creating a Radius environment is supported across AppHost languages. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). Secret-store configuration is a separate, C#-only operation shown after these setup examples. + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +await builder.addRadiusEnvironment("radius"); + +await builder.build().run(); +``` + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddRadiusEnvironment("radius"); + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_radius_env("radius") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddRadiusEnvironment("radius") + if err := env.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addRadiusEnvironment("radius"); + + 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)?; + + builder.add_radius_environment("radius")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +### Configure secret stores in C# + +`AddRadiusSecretStore`, `WithSecretStore`, their callbacks, and `RadiusSecretStoreType` are not exported to the TypeScript SDK catalog for `Aspire.Hosting.Radius` or to the daily Python, Go, Java, and Rust SDKs used for this documentation. Secret-store configuration requires the C# APIs in the daily build: ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -45,6 +161,8 @@ builder.Build().Run(); ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). The warning is only emitted by C#; the generated language SDKs do not export the secret-store APIs. + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireradius057.mdx b/src/frontend/src/content/docs/diagnostics/aspireradius057.mdx index fa1377355..91a069d50 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireradius057.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireradius057.mdx @@ -5,6 +5,8 @@ description: Learn what causes the Aspire compiler warning ASPIRERADIUS057 and h --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; +import InstallPackage from '@components/InstallPackage.astro'; + +The following examples associate a pre-built image with a project. The C# call generates `ASPIRERADIUS057`; the generated language SDKs expose the same experimental operation without emitting this C# compiler diagnostic. Python, Go, Java, and Rust examples require the [experimental AppHost setup](/diagnostics/overview/#experimental-apphost-examples). + + + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +await builder.addRadiusEnvironment("radius"); +const api = await builder.addProject("apiservice", "../ApiService/ApiService.csproj"); +await api.withContainerImage("localhost:5001/apiservice:latest"); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -32,8 +56,103 @@ builder.AddProject("apiservice") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + builder.add_radius_env("radius") + api = builder.add_project("apiservice", "../ApiService/ApiService.csproj") + api.with_container_image("localhost:5001/apiservice:latest") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "apphost/modules/aspire" + "log" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + env := builder.AddRadiusEnvironment("radius") + if err := env.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + api := builder.AddProject("apiservice", "../ApiService/ApiService.csproj"). + WithContainerImage("localhost:5001/apiservice:latest") + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addRadiusEnvironment("radius"); + var api = builder.addProject("apiservice", "../ApiService/ApiService.csproj"); + api.withContainerImage("localhost:5001/apiservice:latest"); + + 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)?; + + builder.add_radius_environment("radius")?; + let api = builder.add_project("apiservice", "../ApiService/ApiService.csproj", None)?; + api.with_container_image("localhost:5001/apiservice:latest")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). Suppression is only needed for C#. + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireterminal001.mdx b/src/frontend/src/content/docs/diagnostics/aspireterminal001.mdx index 07652de44..71243407c 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireterminal001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireterminal001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIRETERMINAL001 and --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const cache = await builder.addContainer("cache", { image: "redis" }); +await cache.withTerminal(); + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -35,8 +53,98 @@ builder.AddContainer("cache", "redis") builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + cache = builder.add_container("cache", "redis") + cache.with_terminal() + + 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)) + } + + cache := builder.AddContainer("cache", "redis").WithTerminal() + if err := cache.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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + builder.addContainer("cache", "redis").withTerminal(); + + 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 cache = builder.add_container("cache", json!("redis"))?; + cache.with_terminal()?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +The `TerminalOptions` configuration callback isn't exported to the polyglot SDKs; their parameterless terminal method uses the default terminal options. + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with one of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/aspireusersecrets001.mdx b/src/frontend/src/content/docs/diagnostics/aspireusersecrets001.mdx index e9cc39d8e..d7ac39046 100644 --- a/src/frontend/src/content/docs/diagnostics/aspireusersecrets001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspireusersecrets001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREUSERSECRETS001 --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); + +const configuration = await builder.getConfiguration(); +const secretValue = await configuration.getConfigValue("MY_SECRET"); +if (!secretValue) { + throw new Error("Set MY_SECRET before running the AppHost."); +} + +const userSecretsManager = await builder.userSecretsManager(); +const filePath = await userSecretsManager.filePath(); +const success = await userSecretsManager.trySetSecret("MySecret", secretValue); +if (!success) { + throw new Error("Could not save the development secret."); +} + +await builder.build().run(); +``` + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); -// Using IUserSecretsManager +var secretValue = builder.Configuration["MY_SECRET"]; +if (string.IsNullOrEmpty(secretValue)) +{ + throw new InvalidOperationException("Set MY_SECRET before running the AppHost."); +} + var userSecretsManager = builder.UserSecretsManager; var filePath = userSecretsManager.FilePath; -bool success = userSecretsManager.TrySetSecret("MySecret", "SecretValue"); +bool success = userSecretsManager.TrySetSecret("MySecret", secretValue); +if (!success) +{ + throw new InvalidOperationException("Could not save the development secret."); +} + +builder.Build().Run(); +``` + + + + +```python title="apphost.py" +import os + +from aspire_app import create_builder + +with create_builder() as builder: + secret_value = os.environ.get("MY_SECRET") + if not secret_value: + raise RuntimeError("Set MY_SECRET before running the AppHost.") + + user_secrets_manager = builder.user_secrets_manager + file_path = user_secrets_manager.file_path + success = user_secrets_manager.try_set_secret("MySecret", secret_value) + if not success: + raise RuntimeError("Could not save the development secret.") + + builder.run() +``` + + + + +```go title="apphost.go" +package main + +import ( + "log" + "os" + + "apphost/modules/aspire" +) + +func main() { + builder, err := aspire.CreateBuilder() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + secretValue := os.Getenv("MY_SECRET") + if secretValue == "" { + log.Fatal("Set MY_SECRET before running the AppHost.") + } + + userSecretsManager := builder.UserSecretsManager() + if err := userSecretsManager.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + filePath, err := userSecretsManager.FilePath() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + _ = filePath + + success, err := userSecretsManager.TrySetSecret("MySecret", secretValue) + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if !success { + log.Fatal("Could not save the development secret.") + } + + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + + var secretValue = System.getenv("MY_SECRET"); + if (secretValue == null || secretValue.isEmpty()) { + throw new IllegalStateException( + "Set MY_SECRET before running the AppHost."); + } + + var userSecretsManager = builder.userSecretsManager(); + var filePath = userSecretsManager.filePath(); + var success = userSecretsManager.trySetSecret( + "MySecret", + secretValue); + if (!success) { + throw new IllegalStateException( + "Could not save the development secret."); + } + + 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 secret_value = std::env::var("MY_SECRET") + .map_err(|_| "Set MY_SECRET before running the AppHost.")?; + if secret_value.is_empty() { + return Err("Set MY_SECRET before running the AppHost.".into()); + } + + let user_secrets_manager = builder.user_secrets_manager()?; + let _file_path = user_secrets_manager.file_path()?; + if !user_secrets_manager.try_set_secret("MySecret", &secret_value)? { + return Err("Could not save the development secret.".into()); + } + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + ### Related types - **`IUserSecretsManager`**: Interface for managing user secrets @@ -49,8 +226,12 @@ bool success = userSecretsManager.TrySetSecret("MySecret", "SecretValue"); - **`GetOrSetSecret()`**: Gets a secret from configuration or generates and saves a new value - **`SaveStateAsync()`**: Saves deployment state to user secrets asynchronously +The generated SDKs expose the manager through the builder instead of as a public constructor. Python uses `user_secrets_manager` and `file_path` properties. TypeScript uses asynchronous `userSecretsManager()` and `filePath()` accessors. Go and Rust return explicit errors from manager operations. Java methods throw when the underlying capability call fails. + ## To correct this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. @@ -75,6 +256,6 @@ Suppress the warning with either of the following methods: ```csharp title="Suppressing the warning" #pragma warning disable ASPIREUSERSECRETS001 var userSecretsManager = builder.UserSecretsManager; - bool success = userSecretsManager.TrySetSecret("MySecret", "SecretValue"); + bool success = userSecretsManager.TrySetSecret("MySecret", secretValue); #pragma warning restore ASPIREUSERSECRETS001 ``` diff --git a/src/frontend/src/content/docs/diagnostics/aspirewatch001.mdx b/src/frontend/src/content/docs/diagnostics/aspirewatch001.mdx index b7c2c7bc1..911511139 100644 --- a/src/frontend/src/content/docs/diagnostics/aspirewatch001.mdx +++ b/src/frontend/src/content/docs/diagnostics/aspirewatch001.mdx @@ -5,6 +5,7 @@ description: Learn what causes the Aspire compiler warning ASPIREWATCH001 and ho --- import { Badge } from '@astrojs/starlight/components'; +import AppHostTabs from '@components/AppHostTabs.astro'; + + +```typescript title="apphost.mts" twoslash +import { createBuilder } from "./.aspire/modules/aspire.mjs"; + +const builder = await createBuilder(); +const executionContext = await builder.executionContext(); + +if (await executionContext.isRunMode()) { + const runConfiguration = await executionContext.runConfiguration(); + + if (runConfiguration.watchEnabled) { + // Launch resources so source changes are hot-reloaded. + } +} + +await builder.build().run(); +``` + + + ```csharp title="Reading run mode configuration" var builder = DistributedApplication.CreateBuilder(args); @@ -43,6 +67,127 @@ if (builder.ExecutionContext.IsRunMode) builder.Build().Run(); ``` + + + +```python title="apphost.py" +from aspire_app import create_builder + +with create_builder() as builder: + execution_context = builder.execution_context + + if execution_context.is_run_mode: + run_configuration = execution_context.run_config + + if run_configuration.get("WatchEnabled", False): + # Launch resources so source changes are hot-reloaded. + pass + + 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)) + } + + executionContext := builder.ExecutionContext() + if err := executionContext.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) + } + + isRunMode, err := executionContext.IsRunMode() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + if isRunMode { + runConfiguration, err := executionContext.RunConfiguration() + if err != nil { + log.Fatal(aspire.FormatError(err)) + } + + if runConfiguration.WatchEnabled != nil && *runConfiguration.WatchEnabled { + // Launch resources so source changes are hot-reloaded. + } + } + + 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(String[] args) throws Exception { + var builder = DistributedApplication.CreateBuilder(args); + var executionContext = builder.executionContext(); + + if (executionContext.isRunMode()) { + var runConfiguration = executionContext.runConfiguration(); + + if (Boolean.TRUE.equals(runConfiguration.getWatchEnabled())) { + // Launch resources so source changes are hot-reloaded. + } + } + + 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 execution_context = builder.execution_context()?; + + if execution_context.is_run_mode()? { + let run_configuration = execution_context.run_configuration()?; + + if run_configuration.watch_enabled.unwrap_or(false) { + // Launch resources so source changes are hot-reloaded. + } + } + + let app = builder.build()?; + app.run(None)?; + Ok(()) +} +``` + + + + +Execution-context access follows each SDK's conventions. Python returns a `RunConfiguration` dictionary with wire-format field names, Go uses explicit errors and pointer-valued optional DTO fields, Java exposes bean accessors, and Rust returns `Result` values with optional DTO fields. + ## Understanding run mode configuration `RunConfiguration` holds settings that only apply when the AppHost is running in [run mode](/app-host/resource-lifetimes/) (as opposed to publish mode). Integrations use it to vary how their resources are launched without changing the core hosting behavior. @@ -53,6 +198,8 @@ Indicates that resources should start in watch mode if able. Integrations that s ## To suppress this warning +For a C# file-based AppHost, see [Suppress in a file-based AppHost](/diagnostics/overview/#suppress-in-a-file-based-apphost). + Suppress the warning with either of the following methods: - Set the severity of the rule in the _.editorconfig_ file. diff --git a/src/frontend/src/content/docs/diagnostics/overview.mdx b/src/frontend/src/content/docs/diagnostics/overview.mdx index 5ac984956..06509afb5 100644 --- a/src/frontend/src/content/docs/diagnostics/overview.mdx +++ b/src/frontend/src/content/docs/diagnostics/overview.mdx @@ -4,7 +4,11 @@ seoTitle: 'Aspire diagnostics overview: ASPIRE compiler diagnostics' description: Learn about the diagnostics tools and features available in Aspire — analyzers, compiler errors, the dashboard, structured telemetry, and troubleshooting workflows. --- -The following table lists the possible MSBuild and analyzer warnings and errors you might encounter with Aspire: +The following table lists the possible MSBuild and analyzer warnings and errors you might encounter with Aspire. + +These diagnostic IDs describe checks performed by the .NET build tools and C# analyzers. AppHosts written in TypeScript, Python, Go, Java, and Rust can use many of the same hosting APIs without emitting the corresponding C# compiler diagnostic. Diagnostic pages include a language variant only where its generated SDK supports the operation. The examples share your `aspire-lang` selection and default to TypeScript when you haven't selected a language. + +MSBuild configuration, C# integration-authoring examples, and C# diagnostic suppressions remain language-specific. An API's experimental status still applies across AppHost languages, even when no compiler suppression is required. Python, Go, Java, and Rust AppHost authoring is also experimental; see [Experimental AppHost examples](#experimental-apphost-examples). | Diagnostic ID | Type | Description | | ---------------------------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -74,16 +78,29 @@ The following table lists the possible MSBuild and analyzer warnings and errors | [ASPIREUSERSECRETS001](/diagnostics/aspireusersecrets001/) | (Experimental) Warning | Type is for evaluation purposes only and is subject to change or removal in future updates. | | [ASPIREWATCH001](/diagnostics/aspirewatch001/) | (Experimental) Warning | Run mode configuration types and members are for evaluation purposes only and are subject to change or removal in future updates. | +## Experimental AppHost examples + +The Python, Go, Java, and Rust examples target the dev-channel Aspire CLI and matching prerelease hosting packages, not the Aspire 13.5.3 release. Their signatures match the daily generated SDK used for this documentation. Use the [Aspire CLI installer](/get-started/install-cli/) with the dev channel and regenerate your AppHost SDK after adding the integration packages named on the diagnostic page. + +Enable the feature flag for your AppHost language: + +```bash title="Enable an experimental AppHost language" +aspire config set features:experimentalPolyglot:python true --global +``` + +Replace `python` with `go`, `java`, or `rust` for those AppHosts. Experimental language availability is separate from an individual API's experimental status: neither flag removes C# compiler diagnostics or makes unexported C# callbacks available in a generated SDK. When an operation isn't available, the example explains the limitation instead of showing a substitute implementation. + ## Suppress diagnostic -You can suppress any diagnostic in this document using one of the following methods: +For a suppressible C# diagnostic, use a method supported by that diagnostic: - Add the `System.Diagnostics.CodeAnalysis.SuppressMessageAttribute` at the assembly, class, method, line, etc. - Include the diagnostic ID in the `NoWarn` property of your project file. +- For a C# file-based AppHost, set `NoWarn` with a `#:property` directive. - Use preprocessor directives in your code. - Configure diagnostic severity in an _.editorconfig_ file. -There are some common patterns for suppressing diagnostics in .NET projects. The best method depends on your context and the specific diagnostic. Here's a quick guide to help you choose: +These methods apply to C# code and .NET builds, not to `apphost.mts`. Read the diagnostic's guidance before suppressing it: configuration errors and invalid resource names should be corrected, and not every diagnostic supports every suppression method. :::note[Important] The following sections provide examples for suppressing the `ASPIREE000` diagnostic, which is a placeholder for any diagnostic ID you might encounter. Replace `ASPIREE000` with the actual diagnostic ID you want to suppress. @@ -91,6 +108,8 @@ The following sections provide examples for suppressing the `ASPIREE000` diagnos ### Suppress with the suppress message attribute +This attribute is for analyzer diagnostics that support it. It doesn't suppress C# compiler diagnostics, including diagnostics emitted by `[Experimental]`; use `NoWarn` or `#pragma warning disable` for those instead. + The `System.Diagnostics.CodeAnalysis.SuppressMessageAttribute` is ideal when you need targeted, documented suppression tied directly to a specific code element like a class or method. It shines when you're making a deliberate exception to a rule that is valid in most other places. This attribute keeps the suppression close to the code it affects, which helps reviewers and future maintainers understand the rationale. While it's a clean solution for isolated cases, it can clutter the code if overused, and it's not the best choice for widespread or bulk suppressions. **When to use:** @@ -124,6 +143,20 @@ Adding the diagnostic ID to the `NoWarn` property in your _.csproj_ file is a st ``` +### Suppress in a file-based AppHost + +A C# [file-based AppHost](/get-started/aspire-sdk/) doesn't have a _.csproj_ file to edit. Add a `#:property` directive at the top of _AppHost.cs_, alongside its existing `#:sdk` and `#:package` directives: + +```csharp title="AppHost.cs" +#:property NoWarn=$(NoWarn);ASPIREE000 +``` + +This sets the `NoWarn` MSBuild property on the project generated for the file-based app. Keep `$(NoWarn)` to preserve existing suppressions, and separate additional diagnostic IDs with semicolons. Replace `ASPIREE000` with the actual suppressible diagnostic ID. + +The directive applies to the whole file-based app. To suppress a diagnostic only around a particular API call, use the [preprocessor directives](#suppress-with-preprocessor-directives) below instead; they work in both file-based and project-based C# AppHosts. You don't need to create a project file or convert the app. + +For more information, see [File-based app directives](https://learn.microsoft.com/dotnet/core/sdk/file-based-apps#supported-directives). + ### Suppress with preprocessor directives Preprocessor directives like `#pragma warning disable` provide pinpoint control over which specific lines of code are affected by the suppression. This makes them the most precise option when you want to temporarily mute a diagnostic for a small, well-defined section of code, such as a tricky workaround or legacy code. While their precision is a strength, it can also become a burden if the suppressions are scattered throughout a file. Additionally, they make the code slightly less readable due to the added directives, so they're best used sparingly for short-lived or highly specific issues. diff --git a/src/frontend/tests/e2e/diagnostics-language.spec.ts b/src/frontend/tests/e2e/diagnostics-language.spec.ts new file mode 100644 index 000000000..027e9e8a7 --- /dev/null +++ b/src/frontend/tests/e2e/diagnostics-language.spec.ts @@ -0,0 +1,161 @@ +import { expect, test, type Page } from '@playwright/test'; +import { dismissCookieConsentIfVisible } from '@tests/e2e/helpers'; +import languageConfig from '../../src/data/apphost-languages.json' with { type: 'json' }; + +const enabledLanguages = languageConfig.languages.filter(({ enabled }) => enabled); +type Language = (typeof enabledLanguages)[number]; + +function languageAccessibleName(language: Language) { + const label = language.label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`^${label}${language.experimental ? '(?:\\s*Experimental)?' : ''}$`, 'i'); +} + +async function expectDiagnosticsLanguage(page: Page, language: Language) { + const groups = page.locator('[data-apphost-tabs]'); + await expect(groups.first()).toBeVisible(); + for (const group of await groups.all()) { + const supportedIds = await group.evaluate((element) => + element + .getAttributeNames() + .filter((name) => name.startsWith('data-supports-')) + .map((name) => name.slice('data-supports-'.length)) + ); + const supported = enabledLanguages.filter(({ id }) => supportedIds.includes(id)); + await expect(group.getByRole('tab', { includeHidden: true })).toHaveText( + supported.map(({ label }) => label) + ); + if (!supportedIds.includes(language.id)) { + await expect(group.locator('.apphost-tabs__tabs')).toBeHidden(); + await expect(group.locator(`[data-apphost-limitation="${language.id}"]`)).toBeVisible(); + await expect(group.getByRole('tabpanel')).toHaveCount(0); + continue; + } + for (const candidate of supported) { + await expect( + group.getByRole('tab', { name: languageAccessibleName(candidate) }) + ).toHaveAttribute('aria-selected', String(candidate.id === language.id)); + } + await expect(group.getByRole('tabpanel')).toHaveCount(1); + await expect( + group.getByRole('tabpanel', { name: languageAccessibleName(language) }) + ).toBeVisible(); + } +} + +const typescript = enabledLanguages.find(({ id }) => id === 'typescript')!; +const csharp = enabledLanguages.find(({ id }) => id === 'csharp')!; + +test('diagnostics render typed AppHost examples and file-based suppression guidance', async ({ + page, +}) => { + await page.goto('/diagnostics/aspire006/?aspire-lang=typescript'); + await dismissCookieConsentIfVisible(page); + await expectDiagnosticsLanguage(page, typescript); + const panel = page + .locator('starlight-tabs[data-sync-key="aspire-lang"]') + .getByRole('tabpanel', { name: typescript.label, exact: true }); + const hover = panel.locator('.twoslash-hover').first(); + await hover.hover(); + await expect(panel.getByRole('tooltip')).toBeVisible(); + await expect(panel.getByRole('tooltip')).toContainText('createBuilder'); + + await page.goto('/diagnostics/overview/#suppress-in-a-file-based-apphost'); + await expect( + page.getByRole('heading', { name: 'Suppress in a file-based AppHost', exact: true }) + ).toBeVisible(); + await expect( + page.locator('pre').filter({ hasText: '#:property NoWarn=$(NoWarn);ASPIREE000' }) + ).toBeVisible(); +}); + +test('diagnostics ignore an invalid aspire-lang query and restore the saved language', async ({ + page, +}) => { + await page.addInitScript(() => { + localStorage.setItem('aspire-lang', 'csharp'); + localStorage.setItem('starlight-synced-tabs__aspire-lang', 'C#'); + }); + await page.goto('/diagnostics/aspireprobes001/?aspire-lang=unknown'); + await dismissCookieConsentIfVisible(page); + await expectDiagnosticsLanguage(page, csharp); +}); + +test('diagnostics AppHost tabs default to TypeScript and synchronize keyboard selection', async ({ + page, +}) => { + await page.goto('/diagnostics/aspireprobes001/'); + await dismissCookieConsentIfVisible(page); + const groups = page.locator('starlight-tabs[data-sync-key="aspire-lang"]'); + expect(await groups.count()).toBeGreaterThan(1); + await expectDiagnosticsLanguage(page, typescript); + + await groups.first().getByRole('tab', { name: typescript.label, exact: true }).focus(); + await page.keyboard.press('ArrowRight'); + await expectDiagnosticsLanguage(page, csharp); + await expect(page).toHaveURL(/aspire-lang=csharp/); +}); + +for (const language of languageConfig.languages.filter(({ enabled }) => !enabled)) { + test(`diagnostics do not expose disabled ${language.label} examples`, async ({ page }) => { + await page.addInitScript(() => { + localStorage.setItem('aspire-lang', 'csharp'); + localStorage.setItem('starlight-synced-tabs__aspire-lang', 'C#'); + }); + await page.goto(`/diagnostics/aspireprobes001/?aspire-lang=${language.id}`); + await dismissCookieConsentIfVisible(page); + await expectDiagnosticsLanguage(page, typescript); + await expect(page).toHaveURL(/aspire-lang=typescript/); + await expect(page.getByRole('tab', { name: language.label, exact: true })).toHaveCount(0); + }); +} + +for (const language of enabledLanguages) { + const { id: value, label } = language; + const opposite = enabledLanguages.find(({ id }) => id !== value)!; + test(`diagnostics URL selects ${label} over the saved preference`, async ({ page }) => { + await page.addInitScript(({ id, label }) => { + localStorage.setItem('aspire-lang', id); + localStorage.setItem('starlight-synced-tabs__aspire-lang', label); + }, opposite); + + await page.goto(`/diagnostics/aspireprobes001/?aspire-lang=${value}`); + await dismissCookieConsentIfVisible(page); + await expectDiagnosticsLanguage(page, language); + await expect.poll(() => page.evaluate(() => localStorage.getItem('aspire-lang'))).toBe(value); + await page.reload(); + await expectDiagnosticsLanguage(page, language); + }); + + test(`diagnostics persist ${label} across pages and reloads`, async ({ page }) => { + await page.goto(`/diagnostics/aspirepostgres001/?aspire-lang=${opposite.id}`); + await dismissCookieConsentIfVisible(page); + const tab = page + .locator('starlight-tabs[data-sync-key="aspire-lang"]') + .first() + .getByRole('tab', { name: languageAccessibleName(language) }); + if (await tab.count()) { + await tab.click(); + } else { + await page.goto(`/diagnostics/aspirepostgres001/?aspire-lang=${value}`); + } + await expectDiagnosticsLanguage(page, language); + await expect(page).toHaveURL(new RegExp(`aspire-lang=${value}`)); + await expect + .poll(() => page.evaluate(() => localStorage.getItem('starlight-synced-tabs__aspire-lang'))) + .toBe(label); + + await page + .locator('a[href="/diagnostics/overview/#suppress-in-a-file-based-apphost"]') + .first() + .click(); + await expect( + page.getByRole('heading', { name: 'Suppress in a file-based AppHost', exact: true }) + ).toBeVisible(); + // Navigate without a query to exercise persistence independently of link rewriting. + await page.goto('/diagnostics/aspireprobes001/'); + await expect(page).toHaveURL(/\/diagnostics\/aspireprobes001\//); + await expectDiagnosticsLanguage(page, language); + await page.reload(); + await expectDiagnosticsLanguage(page, language); + }); +} diff --git a/src/frontend/tests/unit/diagnostics-language.vitest.test.ts b/src/frontend/tests/unit/diagnostics-language.vitest.test.ts new file mode 100644 index 000000000..34d3ffb7f --- /dev/null +++ b/src/frontend/tests/unit/diagnostics-language.vitest.test.ts @@ -0,0 +1,256 @@ +import { readdirSync, readFileSync } from 'node:fs'; +import { createProcessor } from '@mdx-js/mdx'; +import ts from 'typescript'; +import { describe, expect, test } from 'vitest'; +import { getAppHostLanguages, getEnabledAppHostLanguages } from '../../src/utils/apphost-languages'; +import { renderAppHostTabsInMarkdown } from '../../config/apphost-language-markdown.mjs'; + +const diagnosticsRoot = new URL('../../src/content/docs/diagnostics/', import.meta.url); +const parser = createProcessor(); +const languages = getAppHostLanguages(); +const enabledLanguages = getEnabledAppHostLanguages(); +const pages = readdirSync(diagnosticsRoot) + .filter((name) => name.endsWith('.mdx')) + .map((name) => ({ + name, + source: readFileSync(new URL(name, diagnosticsRoot), 'utf8'), + })); +const modulesRoot = new URL('../../src/data/ts-modules/', import.meta.url); +const apiPackages = new Map>(); +for (const file of readdirSync(modulesRoot).filter((file) => file.endsWith('.json'))) { + const module: { package: { name: string }; functions: Array<{ name: string }> } = JSON.parse( + readFileSync(new URL(file, modulesRoot), 'utf8') + ); + for (const api of module.functions) { + const packages = apiPackages.get(api.name) ?? new Set(); + packages.add(module.package.name); + apiPackages.set(api.name, packages); + } +} + +describe('diagnostics AppHost language examples', () => { + test.each(pages)( + '$name uses canonical, TypeScript-first language tabs and twoslash', + ({ source }) => { + expect(() => parser.parse(source.replace(/^---\r?\n[\s\S]*?\r?\n---/, ''))).not.toThrow(); + expect(source).not.toMatch(/]*syncKey=['"]aspire-lang['"]/); + const documentedPackages = new Set( + [ + 'Aspire.Hosting', + ...source.matchAll( + /\b(?:CommunityToolkit\.)?Aspire\.Hosting(?:\.[A-Za-z][A-Za-z0-9]*)+/g + ), + ].map((match) => (typeof match === 'string' ? match : match[0])) + ); + const languageGroups = [ + ...source.matchAll( + /"'`]|"[^"]*"|'[^']*'|`[^`]*`)*>([\s\S]*?)<\/AppHostTabs>/g + ), + ]; + + if (languageGroups.length > 0) { + expect(source).toContain('/diagnostics/overview/#experimental-apphost-examples'); + } + + for (const [wrapper, group] of languageGroups) { + expect(source).toContain("import AppHostTabs from '@components/AppHostTabs.astro';"); + const slots = [ + ...group.matchAll(/([\s\S]*?)<\/Fragment>/g), + ]; + const ids = slots.map(([, id]) => id); + expect(ids).toEqual(languages.filter(({ id }) => ids.includes(id)).map(({ id }) => id)); + expect(new Set(ids).size).toBe(ids.length); + const allLanguageMarkdown = renderAppHostTabsInMarkdown( + wrapper, + languages.map((language) => ({ ...language, enabled: true })) + ); + for (const language of languages) { + expect( + allLanguageMarkdown, + `Provide a ${language.id} example or a specific SDK limitation before activation.` + ).toContain(`### ${language.label}`); + } + const markdown = renderAppHostTabsInMarkdown(wrapper, languages); + for (const language of enabledLanguages) { + // A missing slot must have an explicit, readable limitation, just as in the renderer. + expect(markdown).toContain(`### ${language.label}`); + } + for (const language of languages.filter(({ enabled }) => !enabled)) { + expect(markdown).not.toContain(`### ${language.label}`); + } + for (const [, id, content] of slots) { + const language = languages.find((language) => language.id === id)!; + expect(content).toContain(`\`\`\`${language.codeFence}`); + if (language.experimental) { + expect(content).toContain(`title="${language.appHostFile}"`); + expect(content).not.toMatch(/```[^\n]*\btwoslash\b/); + expect(content).not.toMatch(/\bTODO\b|not yet (?:implemented|validated)/i); + } + } + } + + for (const block of source.matchAll(/```([\w+-]+)\b[^\n]*\n([\s\S]*?)```/g)) { + const title = block[0] + .slice(0, block[0].indexOf('\n')) + .match(/\btitle\s*=\s*["']([^"']+)["']/)?.[1]; + const appHostFileTitle = /\bapphost\.(?:cs|mts|py|go|java|rs)\b/i.test(title ?? ''); + if ( + !appHostFileTitle && + !/\b(?:createBuilder|CreateBuilder|create_builder)\s*\(|\bbuilder\s*\.\s*(?:[Aa]dd|[Bb]uild|[Ww]ith)/.test( + block[2] + ) + ) { + continue; + } + if ( + block[1] === 'csharp' && + block[2] + .trim() + .split(/\r?\n/) + .every((line) => !line.trim() || line.trim().startsWith('#:package ')) + ) { + continue; + } + if ( + languageGroups.some( + (group) => + block.index >= group.index && + block.index + block[0].length <= group.index + group[0].length + ) + ) { + continue; + } + + expect(block[1], 'AppHost builder examples belong in AppHostTabs.').toBe('csharp'); + if ( + /\bthis\s+(?:IResourceBuilder<|IDistributedApplicationBuilder\b)/.test(block[2]) && + !/\bCreateBuilder\s*\(/.test(block[2]) + ) { + expect(source).toContain('C#-only integration-authoring examples'); + continue; + } + const precedingProse = source + .slice(0, block.index) + .replace(/```[\s\S]*?```/g, '') + .slice(-1800); + if ( + /#pragma warning disable\b|#:property\s+NoWarn\b|\bSuppressMessage\s*\(/.test(block[2]) + ) { + expect(precedingProse).toMatch(/suppress(?:ion|ing| the| in)/i); + continue; + } + expect( + precedingProse, + 'A standalone C# AppHost example needs a nearby, specific C#-only explanation.' + ).toMatch(/C#-only|requires? (?:a )?C#|apply only to C#/); + } + + for (const block of source.matchAll(/```(?:typescript|ts|tsx)\b([^\n]*)\n([\s\S]*?)```/g)) { + expect(block[1]).toMatch(/\btwoslash\b/); + expect(block[1]).toMatch(/title=['"]apphost\.mts['"]/); + expect(block[2]).not.toMatch(/@ts-(?:ignore|expect-error)|\/\/\s*@errors:|\bas any\b/); + expect( + languageGroups.some(([, group]) => + [...group.matchAll(/([\s\S]*?)<\/Fragment>/g)].some( + ([, content]) => content.includes(block[0]) + ) + ) + ).toBe(true); + const syntax = ts.createSourceFile('apphost.mts', block[2], ts.ScriptTarget.Latest, true); + function checkAwaitedCalls(node: ts.Node): void { + if (ts.isCallExpression(node)) { + const expression = node.expression; + const name = ts.isIdentifier(expression) + ? expression.text + : ts.isPropertyAccessExpression(expression) + ? expression.name.text + : ''; + if (name === 'createBuilder' || name === 'run' || apiPackages.has(name)) { + let call = node; + // SDK thenable wrappers also support awaiting the complete fluent chain. + while ( + ts.isPropertyAccessExpression(call.parent) && + ts.isCallExpression(call.parent.parent) && + call.parent.parent.expression === call.parent + ) { + call = call.parent.parent; + } + expect( + ts.isAwaitExpression(call.parent), + `Await SDK call: ${node.getText(syntax)}` + ).toBe(true); + } + } + ts.forEachChild(node, checkAwaitedCalls); + } + checkAwaitedCalls(syntax); + for (const [, method] of block[2].matchAll(/\.(\w+)\s*\(/g)) { + const packages = apiPackages.get(method); + if (packages) { + expect( + [...packages].some((name) => documentedPackages.has(name)), + `Document the package for ${method}: ${[...packages].join(', ')}` + ).toBe(true); + } + } + } + } + ); + + test('shared APIs retain language examples even on compiler diagnostic pages', () => { + for (const name of [ + 'aspire006.mdx', + 'aspireacadomains001.mdx', + 'aspireacanaming001.mdx', + 'aspireacanaming002.mdx', + 'aspireazure002.mdx', + 'aspireazure003.mdx', + 'aspireblazor001.mdx', + 'aspirebrowserlogs001.mdx', + 'aspirecertificates001.mdx', + 'aspirecommand001.mdx', + 'aspirecompute001.mdx', + 'aspirecompute002.mdx', + 'aspirecompute003.mdx', + 'aspirecosmosdb001.mdx', + 'aspirecsharpapps001.mdx', + 'aspiredockerfilebuilder001.mdx', + 'aspiredotnetproject001.mdx', + 'aspiredotnettool001.mdx', + 'aspiredurabletask001.mdx', + 'aspirehostingpython001.mdx', + 'aspireinteraction001.mdx', + 'aspirejavascript001.mdx', + 'aspiremcp001.mdx', + 'aspirepersistence001.mdx', + 'aspirepipelines001.mdx', + 'aspirepipelines003.mdx', + 'aspirepostgres001.mdx', + 'aspireprobes001.mdx', + 'aspireprocesscommand001.mdx', + 'aspireprojects001.mdx', + 'aspireproxyendpoints001.mdx', + 'aspireradius003.mdx', + 'aspireradius004.mdx', + 'aspireradius006.mdx', + 'aspireradius057.mdx', + 'aspireterminal001.mdx', + 'aspireusersecrets001.mdx', + 'aspirewatch001.mdx', + ]) { + const source = readFileSync(new URL(name, diagnosticsRoot), 'utf8'); + expect(source, name).toContain(' { + const overview = readFileSync(new URL('overview.mdx', diagnosticsRoot), 'utf8'); + expect(overview).toContain('### Suppress in a file-based AppHost'); + expect(overview).toContain('#:property NoWarn=$(NoWarn);ASPIREE000'); + expect(overview).toContain('#pragma warning restore ASPIREE000'); + expect(overview).toContain('not to `apphost.mts`'); + expect(overview).toContain('## Experimental AppHost examples'); + expect(overview).toContain('features:experimentalPolyglot:python'); + expect(overview).toContain('daily generated SDK'); + }); +}); From 563bdd592d74bd8b4a2faf1fad1b1a592d2c533c Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:34:45 -0500 Subject: [PATCH 2/6] Expand core AppHost docs for polyglot SDKs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../app-host/certificate-configuration.mdx | 550 ++++++-- .../content/docs/app-host/container-files.mdx | 152 ++- .../docs/app-host/container-registry.mdx | 370 +++++- .../src/content/docs/app-host/eventing.mdx | 263 +++- .../docs/app-host/executable-resources.mdx | 352 ++++- .../docs/app-host/hot-reload-and-watch.mdx | 52 +- .../app-host/migrate-from-docker-compose.mdx | 186 ++- .../docs/app-host/resource-lifetimes.mdx | 353 ++++- .../docs/app-host/typescript-apphost.mdx | 2 +- .../content/docs/app-host/with-terminal.mdx | 147 +- .../content/docs/app-host/withdockerfile.mdx | 287 +++- .../architecture/resource-api-patterns.mdx | 288 +++- .../architecture/resource-hierarchies.mdx | 522 +++++++- .../docs/architecture/resource-model.mdx | 31 +- .../blazor-webassembly.mdx | 25 +- .../dashboard/telemetry-after-deployment.mdx | 38 + .../content/docs/deployment/app-lifecycle.mdx | 15 +- .../docs/deployment/azure/app-service.mdx | 56 +- .../deployment/azure/azure-developer-cli.mdx | 7 +- .../azure/azure-security-best-practices.mdx | 28 +- .../docs/deployment/azure/container-apps.mdx | 88 +- .../docs/deployment/custom-deployments.mdx | 7 +- .../docs/deployment/deploy-with-aspire.mdx | 14 +- .../docs/deployment/docker-compose.mdx | 154 ++- .../content/docs/deployment/environments.mdx | 363 ++++- .../docs/deployment/javascript-apps.mdx | 422 +++++- .../deployment/kubernetes-gateway-aks.mdx | 21 +- .../deployment/kubernetes-ingress-aks.mdx | 21 +- .../docs/deployment/kubernetes-ingress.mdx | 14 +- .../docs/deployment/kubernetes/aks.mdx | 70 +- .../docs/deployment/kubernetes/clusters.mdx | 35 +- .../deployment/kubernetes/helm-charts.mdx | 42 +- .../kubernetes/persistent-volumes.mdx | 42 +- .../src/content/docs/deployment/pipelines.mdx | 479 ++++++- .../extensibility/interaction-service.mdx | 947 ++++++++++++- .../fundamentals/annotations-overview.mdx | 171 ++- .../fundamentals/container-networking.mdx | 108 +- .../fundamentals/custom-resource-commands.mdx | 1184 ++++++++++++++++- .../fundamentals/custom-resource-urls.mdx | 233 +++- .../fundamentals/environment-variables.mdx | 147 ++ .../docs/fundamentals/external-parameters.mdx | 819 +++++++++++- .../docs/fundamentals/health-checks.mdx | 187 ++- .../docs/fundamentals/http-commands.mdx | 127 ++ .../docs/fundamentals/networking-overview.mdx | 623 ++++++++- .../fundamentals/persist-data-volumes.mdx | 118 +- .../docs/fundamentals/service-discovery.mdx | 405 ++++++ .../get-started/add-aspire-existing-app.mdx | 40 +- .../docs/get-started/aspire-mcp-server.mdx | 279 +++- .../content/docs/get-started/aspire-sdk.mdx | 35 +- .../docs/get-started/resource-mcp-servers.mdx | 147 +- .../docs/testing/advanced-scenarios.mdx | 156 +++ .../content/docs/testing/manage-app-host.mdx | 36 +- .../docs/testing/write-your-first-test.mdx | 2 +- 53 files changed, 10869 insertions(+), 391 deletions(-) diff --git a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx index 796d61f47..dc034faa6 100644 --- a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx +++ b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx @@ -41,10 +41,10 @@ Many of the certificate features in Aspire rely on a development certificate. Be The preferred way to manage the development certificate is to use the [Aspire CLI](/get-started/install-cli/). When you run `aspire run` in an interactive session, the CLI automatically ensures the development certificate is created and trusted. No additional manual steps are required. -For non-C# AppHosts (such as [TypeScript](/app-host/typescript-apphost/) or Python AppHosts), the `dotnet` first-run experience that normally creates the HTTPS development certificate never runs, because these AppHosts launch a prebuilt native binary instead of invoking `dotnet`. The Aspire CLI fills this gap when `aspire run` starts and no development certificate exists: +For generated TypeScript, Python, Go, Java, and Rust AppHosts, the `dotnet` first-run experience that normally creates the HTTPS development certificate never runs, because these AppHosts launch a prebuilt native binary instead of invoking `dotnet`. The Aspire CLI fills this gap when `aspire run` starts and no development certificate exists: -- In an interactive session—and on Linux, where establishing trust doesn't require a prompt—the CLI creates *and* trusts the certificate, just as it does for C# AppHosts. -- In a non-interactive session on macOS or Windows (for example, in CI), the CLI can't show the macOS Keychain password prompt or the Windows trust dialog, so it *generates* the certificate without trusting it. This lets servers such as Kestrel load the certificate from the personal store, even though it isn't trusted. If the certificate can't be generated, a warning is displayed and the run continues. +- In an interactive session—and on Linux, where establishing trust doesn't require a prompt—the CLI creates _and_ trusts the certificate, just as it does for C# AppHosts. +- In a non-interactive session on macOS or Windows (for example, in CI), the CLI can't show the macOS Keychain password prompt or the Windows trust dialog, so it _generates_ the certificate without trusting it. This lets servers such as Kestrel load the certificate from the personal store, even though it isn't trusted. If the certificate can't be generated, a warning is displayed and the run continues. To opt out of automatic certificate generation, set the `ASPIRE_CLI_GENERATE_HTTPS_CERTIFICATE` environment variable to `false`. This mirrors the .NET SDK's `DOTNET_GENERATE_ASPNET_CERTIFICATE` opt-out: @@ -85,19 +85,20 @@ aspire certs trust ### Developer certificate for DCP communication @@ -141,8 +142,16 @@ You can control this behavior using the HTTPS endpoint APIs described below. To explicitly configure a resource to use the development certificate for its HTTPS endpoints: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -157,24 +166,31 @@ var pythonApp = builder.AddUvicornApp("api", "../api", "app:main") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); // Explicitly use the developer certificate -const nodeApp = await builder.addViteApp("frontend", "../frontend") - .withHttpsDeveloperCertificate(); +const nodeApp = await builder + .addViteApp('frontend', '../frontend') + .withHttpsDeveloperCertificate(); // Use developer certificate with an encrypted private key -const certPassword = await builder.addParameter("cert-password", { secret: true }); -const pythonApp = await builder.addUvicornApp("api", "../api", "app:main") - .withHttpsDeveloperCertificate({ password: certPassword }); +const certPassword = await builder.addParameter('cert-password', { + secret: true, +}); +const pythonApp = await builder + .addUvicornApp('api', '../api', 'app:main') + .withHttpsDeveloperCertificate({ password: certPassword }); await builder.build().run(); ``` + @@ -189,8 +205,16 @@ The `WithHttpsDeveloperCertificate` method: To configure a resource to use a specific X.509 certificate for HTTPS endpoints: - + + ```csharp title="AppHost.cs" using System.Security.Cryptography.X509Certificates; @@ -210,19 +234,22 @@ builder.AddNpmApp("frontend", "../frontend") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder, refExpr } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -const api = await builder.addContainer("api", { - image: "my-api", - tag: "latest", +const api = await builder.addContainer('api', { + image: 'my-api', + tag: 'latest', }); -api.createExecutionConfiguration() +api + .createExecutionConfiguration() .withArgumentsConfig() .withEnvironmentVariablesConfig() .withHttpsCertificateConfig(async () => ({ @@ -233,6 +260,7 @@ api.createExecutionConfiguration() await builder.build().run(); ``` + @@ -246,8 +274,16 @@ The certificate must: To prevent Aspire from configuring any HTTPS certificate for a resource: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -257,19 +293,21 @@ var redis = builder.AddRedis("cache") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); // Disable automatic HTTPS certificate configuration -const redis = await builder.addRedis("cache") - .withoutHttpsCertificate(); +const redis = await builder.addRedis('cache').withoutHttpsCertificate(); await builder.build().run(); ``` + @@ -283,8 +321,14 @@ Use `WithoutHttpsCertificate` when: For resources that need custom certificate configuration logic, use `WithHttpsCertificateConfiguration` to specify how certificate files should be passed to the resource: - +) -> Value, so its typed certificate context cannot be used safely.', + }} +> + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -315,30 +359,76 @@ builder.AddContainer("api", "my-api:latest") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder, refExpr } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -const api = await builder.addContainer("api", { - image: "myimage", - tag: "latest", +const api = await builder.addContainer('api', { + image: 'myimage', + tag: 'latest', }); -api.createExecutionConfiguration() +api + .createExecutionConfiguration() .withArgumentsConfig() .withEnvironmentVariablesConfig() .withCertificateTrustConfig(async () => ({ certificateBundlePath: refExpr`/certs/ca-bundle.crt`, certificateDirectoriesPath: refExpr`/certs`, - rootCertificatesPath: "/etc/ssl/certs", + rootCertificatesPath: '/etc/ssl/certs', isContainer: true, })); await builder.build().run(); ``` + + + + +```go title="apphost.go" +api := builder. + AddContainer("api", "my-api:latest"). + WithHttpsCertificateConfiguration( + func(ctx aspire.HttpsCertificateConfigurationCallbackAnnotationContext) { + arguments := ctx.Arguments() + _ = arguments.Add("--tls-cert") + _ = arguments.Add(ctx.CertificatePath()) + _ = arguments.Add("--tls-key") + _ = arguments.Add(ctx.KeyPath()) + + environment := ctx.Environment() + _ = environment.Set("TLS_CERT_FILE", ctx.CertificatePath()) + _ = environment.Set("TLS_KEY_FILE", ctx.KeyPath()) + _ = environment.Set("TLS_PFX_FILE", ctx.PfxPath()) + }, + ) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + +```java title="AppHost.java" +var api = builder.addContainer("api", "my-api:latest"); +api.withHttpsCertificateConfiguration(ctx -> { + ctx.arguments().add("--tls-cert"); + ctx.arguments().add(ctx.certificatePath()); + ctx.arguments().add("--tls-key"); + ctx.arguments().add(ctx.keyPath()); + + ctx.environment().set("TLS_CERT_FILE", ctx.certificatePath()); + ctx.environment().set("TLS_KEY_FILE", ctx.keyPath()); + ctx.environment().set("TLS_PFX_FILE", ctx.pfxPath()); +}); +``` + @@ -377,8 +467,16 @@ You can control this behavior per resource using the `WithDeveloperCertificateTr To explicitly enable or disable development certificate trust for a specific resource: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -392,23 +490,28 @@ var pythonApp = builder.AddPythonApp("api", "../api", "main.py") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); // Explicitly enable development certificate trust -const nodeApp = await builder.addNodeApp("frontend", "../frontend", "index.js") - .withDeveloperCertificateTrust(true); +const nodeApp = await builder + .addNodeApp('frontend', '../frontend', 'index.js') + .withDeveloperCertificateTrust(true); // Disable development certificate trust -const pythonApp = await builder.addPythonApp("api", "../api", "main.py") - .withDeveloperCertificateTrust(false); +const pythonApp = await builder + .addPythonApp('api', '../api', 'main.py') + .withDeveloperCertificateTrust(false); await builder.build().run(); ``` + @@ -418,6 +521,17 @@ Certificate authority collections allow you to bundle custom certificates and ma #### Create and use a certificate authority collection + + + ```csharp title="AppHost.cs" using System.Security.Cryptography.X509Certificates; @@ -438,9 +552,8 @@ builder.AddNpmApp("my-project", "../myapp") builder.Build().Run(); ``` - + + In the preceding example, the certificate bundle is created with custom certificates and then applied to a Node.js application, enabling it to trust those certificates. @@ -465,8 +578,16 @@ Attempts to append the configured certificates to the default trusted certificat This is the default scope for most resources. For Python resources, only OTEL trust configuration will be applied in this mode. - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -475,18 +596,25 @@ builder.AddNodeApp("api", "../api") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash -import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs'; +import { + createBuilder, + CertificateTrustScope, +} from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -await builder.addNodeApp("api", "../api", "index.js") - .withCertificateTrustScope(CertificateTrustScope.Append); +await builder + .addNodeApp('api', '../api', 'index.js') + .withCertificateTrustScope(CertificateTrustScope.Append); await builder.build().run(); ``` + @@ -496,25 +624,36 @@ await builder.build().run(); #### Override mode Attempts to override a resource to only trust the configured certificates, replacing the default trusted certificates entirely. This mode is useful when you need strict control over which certificates are trusted. + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -528,9 +667,8 @@ builder.AddPythonModule("api", "./api", "uvicorn") builder.Build().Run(); ``` - + + #### System mode @@ -538,8 +676,16 @@ Attempts to combine the configured certificates with the default system root cer This is the default scope for Python projects because Python only has mechanisms to fully override certificate trust. - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -548,18 +694,25 @@ builder.AddPythonApp("worker", "../worker", "main.py") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash -import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs'; +import { + createBuilder, + CertificateTrustScope, +} from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -await builder.addPythonApp("worker", "../worker", "main.py") - .withCertificateTrustScope(CertificateTrustScope.System); +await builder + .addPythonApp('worker', '../worker', 'main.py') + .withCertificateTrustScope(CertificateTrustScope.System); await builder.build().run(); ``` + @@ -571,6 +724,7 @@ This is the default scope for .NET projects on Windows, as there's no way to aut + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -579,18 +733,61 @@ builder.AddContainer("service", "myimage") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash -import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs'; +import { + createBuilder, + CertificateTrustScope, +} from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -await builder.addContainer("service", { image: "myimage", tag: "latest" }) - .withCertificateTrustScope(CertificateTrustScope.None); +await builder + .addContainer('service', { image: 'myimage', tag: 'latest' }) + .withCertificateTrustScope(CertificateTrustScope.None); await builder.build().run(); ``` + + + + +```python title="apphost.py" +service = builder.add_container("service", "myimage") +service.with_certificate_trust_scope("None") +``` + + + + +```go title="apphost.go" +service := builder. + AddContainer("service", "myimage"). + WithCertificateTrustScope(aspire.CertificateTrustScopeNone) +``` + + + + +```java title="AppHost.java" +var service = builder.addContainer("service", "myimage"); +service.withCertificateTrustScope(CertificateTrustScope.NONE); +``` + + + + +```rust title="apphost.rs" +let service = builder.add_container( + "service", + serde_json::json!("myimage"), +)?; +service.with_certificate_trust_scope(CertificateTrustScope::None)?; +``` + @@ -602,8 +799,16 @@ For advanced scenarios, you can specify custom certificate trust behavior using Use `WithCertificateTrustConfiguration` to customize how certificate trust is configured for a resource: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -625,6 +830,7 @@ builder.AddContainer("api", "myimage") builder.Build().Run(); ``` +