diff --git a/.agents/skills/update-samples/SKILL.md b/.agents/skills/update-samples/SKILL.md index 78f58715e..34ade6663 100644 --- a/.agents/skills/update-samples/SKILL.md +++ b/.agents/skills/update-samples/SKILL.md @@ -71,6 +71,7 @@ Tags are automatically inferred from the README content and sample name. The scr | `javascript` | JavaScript references | | `node` | Node.js references | | `go` | Go/Golang references | +| `java` | Java, Spring Boot, Quarkus, Maven, Gradle references | #### Services & Technologies | Tag | Matched by | diff --git a/src/frontend/scripts/update-samples.ts b/src/frontend/scripts/update-samples.ts index 8e3c224f7..0f6ea47cb 100644 --- a/src/frontend/scripts/update-samples.ts +++ b/src/frontend/scripts/update-samples.ts @@ -147,6 +147,10 @@ const TAG_RULES: TagRule[] = [ patterns: [/\bTypeScript\b/i, /\bts-node\b/i, /\bapphost\.m?ts\b/i, /\.m?tsx?\b/], }, { tag: 'node', patterns: [/\bNode\.?js\b/i, /\bnpm\b/i] }, + { + tag: 'java', + patterns: [/\bJava\b/i, /\bSpring\s+Boot\b/i, /\bQuarkus\b/i, /\bMaven\b/i, /\bGradle\b/i, /\bmvnw\b/i], + }, { tag: 'go', patterns: [ diff --git a/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-dark.png b/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-dark.png new file mode 100644 index 000000000..73495e3b8 Binary files /dev/null and b/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-dark.png differ diff --git a/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-light.png b/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-light.png index 5dc22bad7..94a7eab47 100644 Binary files a/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-light.png and b/src/frontend/src/assets/samples/aspire-with-javascript/aspire-dashboard-light.png differ diff --git a/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-dark.png b/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-dark.png new file mode 100644 index 000000000..83f3701b8 Binary files /dev/null and b/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-dark.png differ diff --git a/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-light.png b/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-light.png index eab952f28..63683b9ed 100644 Binary files a/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-light.png and b/src/frontend/src/assets/samples/container-build/aspire-dashboard-container-build-light.png differ diff --git a/src/frontend/src/assets/samples/spring-petclinic/spring-petclinic.png b/src/frontend/src/assets/samples/spring-petclinic/spring-petclinic.png new file mode 100644 index 000000000..efd7051e1 Binary files /dev/null and b/src/frontend/src/assets/samples/spring-petclinic/spring-petclinic.png differ diff --git a/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-dark.png b/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-dark.png new file mode 100644 index 000000000..9d863323d Binary files /dev/null and b/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-dark.png differ diff --git a/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png b/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png index ba4bf5e8c..bad6c855e 100644 Binary files a/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png and b/src/frontend/src/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png differ diff --git a/src/frontend/src/data/samples.json b/src/frontend/src/data/samples.json index 25010bbee..41b155cf4 100644 --- a/src/frontend/src/data/samples.json +++ b/src/frontend/src/data/samples.json @@ -45,8 +45,8 @@ "title": "Integrating Angular, React, and Vue with Aspire", "description": "This sample demonstrates using the Aspire JavaScript hosting integration to configure and run client-side applications.\n\nThe app consists of five services:\n\n- **AspireJavaScript.MinimalApi**: This is an HTTP API that returns randomly generated weather forecast data.\n- **AspireJavaScript.Angular**: An Angular app that consumes the weather forecast API and displays it with a featured-day hero and supporting day cards.\n- **AspireJavaScript.React**: A React app (Webpack) that consumes the weather forecast API and displays the forecast.\n- **AspireJavaScript.Vue**: A Vue app that consumes the weather forecast API and presents the forecast as a swipeable, keyboard-navigable day-by-day carousel.\n- **AspireJavaScript.Vite**: A React + Vite + TypeScript app that consumes the weather forecast API and displays the forecast.\n\nThe four front ends all render the **same** weather data, but each one wears a **completely different design identity** — the point of the sample is to compare the frameworks side by side, so we lean into that contrast:\n\n| Front end | Design identity | CSS approach | Icon set |\n| --- | --- | --- | --- |\n| **Angular** | Material 3 \"expressive\" — dynamic tonal color, elevated surfaces | Angular Material + SCSS | Material Symbols |\n| **React** | Neo-brutalism — thick borders, hard offset shadows, chunky type | CSS Modules | Phosphor |\n| **Vue** | Forecast carousel — soft cards, Vue-green gradients, day-by-day navigation | Scoped CSS + custom properties | Lucide |\n| **Vite** | Retro synthwave — neon sun, 80s grid horizon | Tailwind CSS | Tabler |\n\nEvery front end is keyboard operable, ships a skip link, announces async state with `aria-live`, honors `prefers-reduced-motion` and `prefers-color-scheme`, and passes an automated `axe-core` accessibility scan in both light and dark themes.", "href": "https://github.com/microsoft/aspire-samples/tree/main/samples/aspire-with-javascript", - "readme": "# Integrating Angular, React, and Vue with Aspire\n\nThis sample demonstrates using the Aspire JavaScript hosting integration to configure and run client-side applications.\n\nThe app consists of five services:\n\n- **AspireJavaScript.MinimalApi**: This is an HTTP API that returns randomly generated weather forecast data.\n- **AspireJavaScript.Angular**: An Angular app that consumes the weather forecast API and displays it with a featured-day hero and supporting day cards.\n- **AspireJavaScript.React**: A React app (Webpack) that consumes the weather forecast API and displays the forecast.\n- **AspireJavaScript.Vue**: A Vue app that consumes the weather forecast API and presents the forecast as a swipeable, keyboard-navigable day-by-day carousel.\n- **AspireJavaScript.Vite**: A React + Vite + TypeScript app that consumes the weather forecast API and displays the forecast.\n\nThe four front ends all render the **same** weather data, but each one wears a **completely different design identity** — the point of the sample is to compare the frameworks side by side, so we lean into that contrast:\n\n| Front end | Design identity | CSS approach | Icon set |\n| --- | --- | --- | --- |\n| **Angular** | Material 3 \"expressive\" — dynamic tonal color, elevated surfaces | Angular Material + SCSS | Material Symbols |\n| **React** | Neo-brutalism — thick borders, hard offset shadows, chunky type | CSS Modules | Phosphor |\n| **Vue** | Forecast carousel — soft cards, Vue-green gradients, day-by-day navigation | Scoped CSS + custom properties | Lucide |\n| **Vite** | Retro synthwave — neon sun, 80s grid horizon | Tailwind CSS | Tabler |\n\nEvery front end is keyboard operable, ships a skip link, announces async state with `aria-live`, honors `prefers-reduced-motion` and `prefers-color-scheme`, and passes an automated `axe-core` accessibility scan in both light and dark themes.\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n- [Node.js](https://nodejs.org) - at least version 24.x\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `AspireJavaScript.AppHost` project using either the Aspire or C# debuggers.\n\nIf using Visual Studio, open the solution file `AspireJavaScript.slnx` and launch/debug the `AspireJavaScript.AppHost` project.\n\nIf using the .NET CLI, run `dotnet run` from the `AspireShop.AppHost` directory.\n\n### Experiencing the app\n\nOnce the app is running, the Aspire dashboard will launch in your browser:\n\n![Aspire dashboard](~/assets/samples/aspire-with-javascript/aspire-dashboard-light.png#gh-light-mode-only)\n![Aspire dashboard](~/assets/samples/aspire-with-javascript/aspire-dashboard.png#gh-dark-mode-only)\n\nFrom the dashboard, you can navigate to the Angular, React, Vue, and Vite apps.\n\n**Angular** — Material 3 expressive\n\n![Angular app (light)](~/assets/samples/aspire-with-javascript/angular-app-light.png#gh-light-mode-only)\n![Angular app (dark)](~/assets/samples/aspire-with-javascript/angular-app-dark.png#gh-dark-mode-only)\n\n**React** — Neo-brutalism\n\n![React app (light)](~/assets/samples/aspire-with-javascript/react-app-light.png#gh-light-mode-only)\n![React app (dark)](~/assets/samples/aspire-with-javascript/react-app-dark.png#gh-dark-mode-only)\n\n**Vue** — Forecast carousel\n\n![Vue app (light)](~/assets/samples/aspire-with-javascript/vue-app-light.png#gh-light-mode-only)\n![Vue app (dark)](~/assets/samples/aspire-with-javascript/vue-app-dark.png#gh-dark-mode-only)\n\n**Vite** — Retro synthwave\n\n![Vite app (light)](~/assets/samples/aspire-with-javascript/reactvite-app-light.png#gh-light-mode-only)\n![Vite app (dark)](~/assets/samples/aspire-with-javascript/reactvite-app-dark.png#gh-dark-mode-only)\n", - "readmeRaw": "# Integrating Angular, React, and Vue with Aspire\n\nThis sample demonstrates using the Aspire JavaScript hosting integration to configure and run client-side applications.\n\nThe app consists of five services:\n\n- **AspireJavaScript.MinimalApi**: This is an HTTP API that returns randomly generated weather forecast data.\n- **AspireJavaScript.Angular**: An Angular app that consumes the weather forecast API and displays it with a featured-day hero and supporting day cards.\n- **AspireJavaScript.React**: A React app (Webpack) that consumes the weather forecast API and displays the forecast.\n- **AspireJavaScript.Vue**: A Vue app that consumes the weather forecast API and presents the forecast as a swipeable, keyboard-navigable day-by-day carousel.\n- **AspireJavaScript.Vite**: A React + Vite + TypeScript app that consumes the weather forecast API and displays the forecast.\n\nThe four front ends all render the **same** weather data, but each one wears a **completely different design identity** — the point of the sample is to compare the frameworks side by side, so we lean into that contrast:\n\n| Front end | Design identity | CSS approach | Icon set |\n| --- | --- | --- | --- |\n| **Angular** | Material 3 \"expressive\" — dynamic tonal color, elevated surfaces | Angular Material + SCSS | Material Symbols |\n| **React** | Neo-brutalism — thick borders, hard offset shadows, chunky type | CSS Modules | Phosphor |\n| **Vue** | Forecast carousel — soft cards, Vue-green gradients, day-by-day navigation | Scoped CSS + custom properties | Lucide |\n| **Vite** | Retro synthwave — neon sun, 80s grid horizon | Tailwind CSS | Tabler |\n\nEvery front end is keyboard operable, ships a skip link, announces async state with `aria-live`, honors `prefers-reduced-motion` and `prefers-color-scheme`, and passes an automated `axe-core` accessibility scan in both light and dark themes.\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n- [Node.js](https://nodejs.org) - at least version 24.x\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `AspireJavaScript.AppHost` project using either the Aspire or C# debuggers.\n\nIf using Visual Studio, open the solution file `AspireJavaScript.slnx` and launch/debug the `AspireJavaScript.AppHost` project.\n\nIf using the .NET CLI, run `dotnet run` from the `AspireShop.AppHost` directory.\n\n### Experiencing the app\n\nOnce the app is running, the Aspire dashboard will launch in your browser:\n\n![Aspire dashboard](./images/aspire-dashboard.png)\n\nFrom the dashboard, you can navigate to the Angular, React, Vue, and Vite apps.\n\n**Angular** — Material 3 expressive\n\n![Angular app (light)](./images/angular-app-light.png#gh-light-mode-only)\n![Angular app (dark)](./images/angular-app-dark.png#gh-dark-mode-only)\n\n**React** — Neo-brutalism\n\n![React app (light)](./images/react-app-light.png#gh-light-mode-only)\n![React app (dark)](./images/react-app-dark.png#gh-dark-mode-only)\n\n**Vue** — Forecast carousel\n\n![Vue app (light)](./images/vue-app-light.png#gh-light-mode-only)\n![Vue app (dark)](./images/vue-app-dark.png#gh-dark-mode-only)\n\n**Vite** — Retro synthwave\n\n![Vite app (light)](./images/reactvite-app-light.png#gh-light-mode-only)\n![Vite app (dark)](./images/reactvite-app-dark.png#gh-dark-mode-only)\n", + "readme": "# Integrating Angular, React, and Vue with Aspire\n\nThis sample demonstrates using the Aspire JavaScript hosting integration to configure and run client-side applications.\n\nThe app consists of five services:\n\n- **AspireJavaScript.MinimalApi**: This is an HTTP API that returns randomly generated weather forecast data.\n- **AspireJavaScript.Angular**: An Angular app that consumes the weather forecast API and displays it with a featured-day hero and supporting day cards.\n- **AspireJavaScript.React**: A React app (Webpack) that consumes the weather forecast API and displays the forecast.\n- **AspireJavaScript.Vue**: A Vue app that consumes the weather forecast API and presents the forecast as a swipeable, keyboard-navigable day-by-day carousel.\n- **AspireJavaScript.Vite**: A React + Vite + TypeScript app that consumes the weather forecast API and displays the forecast.\n\nThe four front ends all render the **same** weather data, but each one wears a **completely different design identity** — the point of the sample is to compare the frameworks side by side, so we lean into that contrast:\n\n| Front end | Design identity | CSS approach | Icon set |\n| --- | --- | --- | --- |\n| **Angular** | Material 3 \"expressive\" — dynamic tonal color, elevated surfaces | Angular Material + SCSS | Material Symbols |\n| **React** | Neo-brutalism — thick borders, hard offset shadows, chunky type | CSS Modules | Phosphor |\n| **Vue** | Forecast carousel — soft cards, Vue-green gradients, day-by-day navigation | Scoped CSS + custom properties | Lucide |\n| **Vite** | Retro synthwave — neon sun, 80s grid horizon | Tailwind CSS | Tabler |\n\nEvery front end is keyboard operable, ships a skip link, announces async state with `aria-live`, honors `prefers-reduced-motion` and `prefers-color-scheme`, and passes an automated `axe-core` accessibility scan in both light and dark themes.\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n- [Node.js](https://nodejs.org) - at least version 24.x\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `AspireJavaScript.AppHost` project using either the Aspire or C# debuggers.\n\nIf using Visual Studio, open the solution file `AspireJavaScript.slnx` and launch/debug the `AspireJavaScript.AppHost` project.\n\nIf using the .NET CLI, run `dotnet run` from the `AspireShop.AppHost` directory.\n\n### Experiencing the app\n\nOnce the app is running, the Aspire dashboard will launch in your browser:\n\n![Aspire dashboard showing the JavaScript sample resources (light theme)](~/assets/samples/aspire-with-javascript/aspire-dashboard-light.png#gh-light-mode-only)\n![Aspire dashboard showing the JavaScript sample resources (dark theme)](~/assets/samples/aspire-with-javascript/aspire-dashboard-dark.png#gh-dark-mode-only)\n\nFrom the dashboard, you can navigate to the Angular, React, Vue, and Vite apps.\n\n**Angular** — Material 3 expressive\n\n![Angular app (light)](~/assets/samples/aspire-with-javascript/angular-app-light.png#gh-light-mode-only)\n![Angular app (dark)](~/assets/samples/aspire-with-javascript/angular-app-dark.png#gh-dark-mode-only)\n\n**React** — Neo-brutalism\n\n![React app (light)](~/assets/samples/aspire-with-javascript/react-app-light.png#gh-light-mode-only)\n![React app (dark)](~/assets/samples/aspire-with-javascript/react-app-dark.png#gh-dark-mode-only)\n\n**Vue** — Forecast carousel\n\n![Vue app (light)](~/assets/samples/aspire-with-javascript/vue-app-light.png#gh-light-mode-only)\n![Vue app (dark)](~/assets/samples/aspire-with-javascript/vue-app-dark.png#gh-dark-mode-only)\n\n**Vite** — Retro synthwave\n\n![Vite app (light)](~/assets/samples/aspire-with-javascript/reactvite-app-light.png#gh-light-mode-only)\n![Vite app (dark)](~/assets/samples/aspire-with-javascript/reactvite-app-dark.png#gh-dark-mode-only)\n", + "readmeRaw": "# Integrating Angular, React, and Vue with Aspire\n\nThis sample demonstrates using the Aspire JavaScript hosting integration to configure and run client-side applications.\n\nThe app consists of five services:\n\n- **AspireJavaScript.MinimalApi**: This is an HTTP API that returns randomly generated weather forecast data.\n- **AspireJavaScript.Angular**: An Angular app that consumes the weather forecast API and displays it with a featured-day hero and supporting day cards.\n- **AspireJavaScript.React**: A React app (Webpack) that consumes the weather forecast API and displays the forecast.\n- **AspireJavaScript.Vue**: A Vue app that consumes the weather forecast API and presents the forecast as a swipeable, keyboard-navigable day-by-day carousel.\n- **AspireJavaScript.Vite**: A React + Vite + TypeScript app that consumes the weather forecast API and displays the forecast.\n\nThe four front ends all render the **same** weather data, but each one wears a **completely different design identity** — the point of the sample is to compare the frameworks side by side, so we lean into that contrast:\n\n| Front end | Design identity | CSS approach | Icon set |\n| --- | --- | --- | --- |\n| **Angular** | Material 3 \"expressive\" — dynamic tonal color, elevated surfaces | Angular Material + SCSS | Material Symbols |\n| **React** | Neo-brutalism — thick borders, hard offset shadows, chunky type | CSS Modules | Phosphor |\n| **Vue** | Forecast carousel — soft cards, Vue-green gradients, day-by-day navigation | Scoped CSS + custom properties | Lucide |\n| **Vite** | Retro synthwave — neon sun, 80s grid horizon | Tailwind CSS | Tabler |\n\nEvery front end is keyboard operable, ships a skip link, announces async state with `aria-live`, honors `prefers-reduced-motion` and `prefers-color-scheme`, and passes an automated `axe-core` accessibility scan in both light and dark themes.\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n- [Node.js](https://nodejs.org) - at least version 24.x\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `AspireJavaScript.AppHost` project using either the Aspire or C# debuggers.\n\nIf using Visual Studio, open the solution file `AspireJavaScript.slnx` and launch/debug the `AspireJavaScript.AppHost` project.\n\nIf using the .NET CLI, run `dotnet run` from the `AspireShop.AppHost` directory.\n\n### Experiencing the app\n\nOnce the app is running, the Aspire dashboard will launch in your browser:\n\n![Aspire dashboard showing the JavaScript sample resources (light theme)](./images/aspire-dashboard-light.png#gh-light-mode-only)\n![Aspire dashboard showing the JavaScript sample resources (dark theme)](./images/aspire-dashboard-dark.png#gh-dark-mode-only)\n\nFrom the dashboard, you can navigate to the Angular, React, Vue, and Vite apps.\n\n**Angular** — Material 3 expressive\n\n![Angular app (light)](./images/angular-app-light.png#gh-light-mode-only)\n![Angular app (dark)](./images/angular-app-dark.png#gh-dark-mode-only)\n\n**React** — Neo-brutalism\n\n![React app (light)](./images/react-app-light.png#gh-light-mode-only)\n![React app (dark)](./images/react-app-dark.png#gh-dark-mode-only)\n\n**Vue** — Forecast carousel\n\n![Vue app (light)](./images/vue-app-light.png#gh-light-mode-only)\n![Vue app (dark)](./images/vue-app-dark.png#gh-dark-mode-only)\n\n**Vite** — Retro synthwave\n\n![Vite app (light)](./images/reactvite-app-light.png#gh-light-mode-only)\n![Vite app (dark)](./images/reactvite-app-dark.png#gh-dark-mode-only)\n", "tags": [ "csharp", "dashboard", @@ -56,7 +56,7 @@ ], "thumbnail": { "light": "~/assets/samples/aspire-with-javascript/aspire-dashboard-light.png#gh-light-mode-only", - "dark": "~/assets/samples/aspire-with-javascript/aspire-dashboard.png#gh-dark-mode-only" + "dark": "~/assets/samples/aspire-with-javascript/aspire-dashboard-dark.png#gh-dark-mode-only" }, "appHost": "csproj", "appHostPath": "AspireJavaScript.AppHost/AppHost.cs", @@ -105,7 +105,7 @@ }, "appHost": "file-based", "appHostPath": "apphost.cs", - "appHostCode": "#:sdk Aspire.AppHost.Sdk@13.5.0\n#:package Aspire.Hosting.JavaScript@13.5.0\n#:package Aspire.Hosting.Python@13.5.0\n#:package Aspire.Hosting.Redis@13.5.0\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nvar cache = builder.AddRedis(\"cache\");\n\nvar app = builder.AddUvicornApp(\"app\", \"./app\", \"main:app\")\n .WithUv()\n .WithExternalHttpEndpoints()\n .WithReference(cache)\n .WaitFor(cache)\n .WithHttpHealthCheck(\"/health\");\n\nvar frontend = builder.AddViteApp(\"frontend\", \"./frontend\")\n .WithReference(app)\n .WaitFor(app);\n\napp.PublishWithContainerFiles(frontend, \"./static\");\n\nbuilder.Build().Run();" + "appHostCode": "#:sdk Aspire.AppHost.Sdk@13.6.0\n#:package Aspire.Hosting.JavaScript@13.6.0\n#:package Aspire.Hosting.Python@13.6.0\n#:package Aspire.Hosting.Redis@13.6.0\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nvar cache = builder.AddRedis(\"cache\");\n\nvar app = builder.AddUvicornApp(\"app\", \"./app\", \"main:app\")\n .WithUv()\n .WithExternalHttpEndpoints()\n .WithReference(cache)\n .WaitFor(cache)\n .WithHttpHealthCheck(\"/health\");\n\nvar frontend = builder.AddViteApp(\"frontend\", \"./frontend\")\n .WithReference(app)\n .WaitFor(app);\n\napp.PublishWithContainerFiles(frontend, \"./static\");\n\nbuilder.Build().Run();" }, { "name": "client-apps-integration", @@ -129,8 +129,8 @@ "title": "Working with container-built resources in an Aspire application", "description": "This sample demonstrates integrating applications into an Aspire app via Dockerfiles and container-based builds. This is especially helpful to integrate applications written in languages that Aspire does not have a native integration for, or to reduce the prerequisites required to run the application.\n\n\nThe sample integrates a simple app written using [Go](https://go.dev/) and the [Gin Web Framework](https://gin-gonic.com/) by using a [Dockerfile](./ginapp/Dockerfile):\n\n- **ginapp**: This is a simple \"Hello, World\" HTTP API that returns a JSON object like `{ \"message\": \"Hello, World!\" }` from `/` and sends OpenTelemetry instrumentation to the Aspire dashboard.", "href": "https://github.com/microsoft/aspire-samples/tree/main/samples/container-build", - "readme": "# Working with container-built resources in an Aspire application\n\nThis sample demonstrates integrating applications into an Aspire app via Dockerfiles and container-based builds. This is especially helpful to integrate applications written in languages that Aspire does not have a native integration for, or to reduce the prerequisites required to run the application.\n\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile](~/assets/samples/container-build/aspire-dashboard-container-build-light.png#gh-light-mode-only)\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile](~/assets/samples/container-build/aspire-dashboard-container-build.png#gh-dark-mode-only)\n\nThe sample integrates a simple app written using [Go](https://go.dev/) and the [Gin Web Framework](https://gin-gonic.com/) by using a [Dockerfile](./ginapp/Dockerfile):\n\n- **ginapp**: This is a simple \"Hello, World\" HTTP API that returns a JSON object like `{ \"message\": \"Hello, World!\" }` from `/` and sends OpenTelemetry instrumentation to the Aspire dashboard.\n\n## Development Features\n\nThis sample includes **hot reload** for local development! The Go application uses:\n\n- **Bind mounts** - Your local source code is mounted directly into the container at `/app`\n- **[Air](https://github.com/air-verse/air)** - A live reload tool for Go that watches for file changes and rebuilds automatically\n- **Polling-based file watching** - Configured to work reliably with Docker bind mounts on Windows\n\nWhen you edit any `.go` file in the `ginapp` directory, Air automatically detects the change, rebuilds the Go binary, and restarts the app in just a few seconds—without rebuilding the entire container. This provides a much faster development feedback loop compared to full container rebuilds.\n\n### How it works\n\n- **Development mode** (default): Uses `Dockerfile.dev` with Air for hot reload\n - Source files are bind-mounted from your local machine\n - Changes to `.go` files trigger automatic rebuilds\n - Dependencies are resolved on-demand via `go get`\n\n- **Production mode** (`aspire publish`): Uses the standard `Dockerfile`\n - Multi-stage build for optimized images\n - No bind mounts or development tools\n - Minimal runtime container based on distroless images\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `apphost.cs` file using either the Aspire or C# debuggers.\n\nIf using the .NET CLI, run `dotnet run apphost.cs` from this directory.\n\nFrom the Aspire dashboard, click on the endpoint URL for the `ginapp` resource to see the response in the browser.\n", - "readmeRaw": "# Working with container-built resources in an Aspire application\n\nThis sample demonstrates integrating applications into an Aspire app via Dockerfiles and container-based builds. This is especially helpful to integrate applications written in languages that Aspire does not have a native integration for, or to reduce the prerequisites required to run the application.\n\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile](./images/aspire-dashboard-container-build.png)\n\nThe sample integrates a simple app written using [Go](https://go.dev/) and the [Gin Web Framework](https://gin-gonic.com/) by using a [Dockerfile](./ginapp/Dockerfile):\n\n- **ginapp**: This is a simple \"Hello, World\" HTTP API that returns a JSON object like `{ \"message\": \"Hello, World!\" }` from `/` and sends OpenTelemetry instrumentation to the Aspire dashboard.\n\n## Development Features\n\nThis sample includes **hot reload** for local development! The Go application uses:\n\n- **Bind mounts** - Your local source code is mounted directly into the container at `/app`\n- **[Air](https://github.com/air-verse/air)** - A live reload tool for Go that watches for file changes and rebuilds automatically\n- **Polling-based file watching** - Configured to work reliably with Docker bind mounts on Windows\n\nWhen you edit any `.go` file in the `ginapp` directory, Air automatically detects the change, rebuilds the Go binary, and restarts the app in just a few seconds—without rebuilding the entire container. This provides a much faster development feedback loop compared to full container rebuilds.\n\n### How it works\n\n- **Development mode** (default): Uses `Dockerfile.dev` with Air for hot reload\n - Source files are bind-mounted from your local machine\n - Changes to `.go` files trigger automatic rebuilds\n - Dependencies are resolved on-demand via `go get`\n\n- **Production mode** (`aspire publish`): Uses the standard `Dockerfile`\n - Multi-stage build for optimized images\n - No bind mounts or development tools\n - Minimal runtime container based on distroless images\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `apphost.cs` file using either the Aspire or C# debuggers.\n\nIf using the .NET CLI, run `dotnet run apphost.cs` from this directory.\n\nFrom the Aspire dashboard, click on the endpoint URL for the `ginapp` resource to see the response in the browser.\n", + "readme": "# Working with container-built resources in an Aspire application\n\nThis sample demonstrates integrating applications into an Aspire app via Dockerfiles and container-based builds. This is especially helpful to integrate applications written in languages that Aspire does not have a native integration for, or to reduce the prerequisites required to run the application.\n\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile (light theme)](~/assets/samples/container-build/aspire-dashboard-container-build-light.png#gh-light-mode-only)\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile (dark theme)](~/assets/samples/container-build/aspire-dashboard-container-build-dark.png#gh-dark-mode-only)\n\nThe sample integrates a simple app written using [Go](https://go.dev/) and the [Gin Web Framework](https://gin-gonic.com/) by using a [Dockerfile](./ginapp/Dockerfile):\n\n- **ginapp**: This is a simple \"Hello, World\" HTTP API that returns a JSON object like `{ \"message\": \"Hello, World!\" }` from `/` and sends OpenTelemetry instrumentation to the Aspire dashboard.\n\n## Development Features\n\nThis sample includes **hot reload** for local development! The Go application uses:\n\n- **Bind mounts** - Your local source code is mounted directly into the container at `/app`\n- **[Air](https://github.com/air-verse/air)** - A live reload tool for Go that watches for file changes and rebuilds automatically\n- **Polling-based file watching** - Configured to work reliably with Docker bind mounts on Windows\n\nWhen you edit any `.go` file in the `ginapp` directory, Air automatically detects the change, rebuilds the Go binary, and restarts the app in just a few seconds—without rebuilding the entire container. This provides a much faster development feedback loop compared to full container rebuilds.\n\n### How it works\n\n- **Development mode** (default): Uses `Dockerfile.dev` with Air for hot reload\n - Source files are bind-mounted from your local machine\n - Changes to `.go` files trigger automatic rebuilds\n - Dependencies are resolved on-demand via `go get`\n\n- **Production mode** (`aspire publish`): Uses the standard `Dockerfile`\n - Multi-stage build for optimized images\n - No bind mounts or development tools\n - Minimal runtime container based on distroless images\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `apphost.cs` file using either the Aspire or C# debuggers.\n\nIf using the .NET CLI, run `dotnet run apphost.cs` from this directory.\n\nFrom the Aspire dashboard, click on the endpoint URL for the `ginapp` resource to see the response in the browser.\n", + "readmeRaw": "# Working with container-built resources in an Aspire application\n\nThis sample demonstrates integrating applications into an Aspire app via Dockerfiles and container-based builds. This is especially helpful to integrate applications written in languages that Aspire does not have a native integration for, or to reduce the prerequisites required to run the application.\n\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile (light theme)](./images/aspire-dashboard-container-build-light.png#gh-light-mode-only)\n![Screenshot of the Aspire dashboard showing the ginapp container resource built from a Dockerfile (dark theme)](./images/aspire-dashboard-container-build-dark.png#gh-dark-mode-only)\n\nThe sample integrates a simple app written using [Go](https://go.dev/) and the [Gin Web Framework](https://gin-gonic.com/) by using a [Dockerfile](./ginapp/Dockerfile):\n\n- **ginapp**: This is a simple \"Hello, World\" HTTP API that returns a JSON object like `{ \"message\": \"Hello, World!\" }` from `/` and sends OpenTelemetry instrumentation to the Aspire dashboard.\n\n## Development Features\n\nThis sample includes **hot reload** for local development! The Go application uses:\n\n- **Bind mounts** - Your local source code is mounted directly into the container at `/app`\n- **[Air](https://github.com/air-verse/air)** - A live reload tool for Go that watches for file changes and rebuilds automatically\n- **Polling-based file watching** - Configured to work reliably with Docker bind mounts on Windows\n\nWhen you edit any `.go` file in the `ginapp` directory, Air automatically detects the change, rebuilds the Go binary, and restarts the app in just a few seconds—without rebuilding the entire container. This provides a much faster development feedback loop compared to full container rebuilds.\n\n### How it works\n\n- **Development mode** (default): Uses `Dockerfile.dev` with Air for hot reload\n - Source files are bind-mounted from your local machine\n - Changes to `.go` files trigger automatic rebuilds\n - Dependencies are resolved on-demand via `go get`\n\n- **Production mode** (`aspire publish`): Uses the standard `Dockerfile`\n - Multi-stage build for optimized images\n - No bind mounts or development tools\n - Minimal runtime container based on distroless images\n\n## Pre-requisites\n\n- [Aspire development environment](https://aspire.dev/get-started/prerequisites/)\n- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)\n\n## Running the app\n\nIf using the Aspire CLI, run `aspire run` from this directory.\n\nIf using VS Code, open this directory as a workspace and launch the `apphost.cs` file using either the Aspire or C# debuggers.\n\nIf using the .NET CLI, run `dotnet run apphost.cs` from this directory.\n\nFrom the Aspire dashboard, click on the endpoint URL for the `ginapp` resource to see the response in the browser.\n", "tags": [ "csharp", "dashboard", @@ -140,11 +140,11 @@ ], "thumbnail": { "light": "~/assets/samples/container-build/aspire-dashboard-container-build-light.png#gh-light-mode-only", - "dark": "~/assets/samples/container-build/aspire-dashboard-container-build.png#gh-dark-mode-only" + "dark": "~/assets/samples/container-build/aspire-dashboard-container-build-dark.png#gh-dark-mode-only" }, "appHost": "file-based", "appHostPath": "apphost.cs", - "appHostCode": "#:sdk Aspire.AppHost.Sdk@13.5.0\n\nusing Microsoft.Extensions.Hosting;\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nvar goVersion = builder.AddParameter(\"goversion\", \"1.25.4\", publishValueAsDefault: true);\n\nIResourceBuilder ginapp;\n\nif (builder.ExecutionContext.IsPublishMode)\n{\n // Production build: multi-stage Dockerfile for optimized image\n ginapp = builder.AddDockerfile(\"ginapp\", \"./ginapp\")\n .WithBuildArg(\"GO_VERSION\", goVersion);\n}\nelse\n{\n // Development build: use Air for hot reload with bind mount\n ginapp = builder.AddDockerfile(\"ginapp\", \"./ginapp\", \"Dockerfile.dev\")\n .WithBuildArg(\"GO_VERSION\", goVersion)\n .WithBindMount(\"./ginapp\", \"/app\");\n}\n\nginapp\n .WithHttpEndpoint(targetPort: 5555, env: \"PORT\")\n .WithHttpHealthCheck(\"/\")\n .WithExternalHttpEndpoints()\n .WithOtlpExporter()\n .WithDeveloperCertificateTrust(trust: true);\n\nif (builder.ExecutionContext.IsPublishMode)\n{\n ginapp\n .WithEnvironment(\"GIN_MODE\", \"release\")\n // Trust all proxies when running behind a reverse proxy. If deploying to an environment\n // without a reverse proxy that ensures X-Forwarded-* headers are not forwarded from clients,\n // this should be removed.\n .WithEnvironment(\"TRUSTED_PROXIES\", \"all\");\n}\n\nbuilder.Build().Run();" + "appHostCode": "#:sdk Aspire.AppHost.Sdk@13.6.0\n\nusing Microsoft.Extensions.Hosting;\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nvar goVersion = builder.AddParameter(\"goversion\", \"1.25.4\", publishValueAsDefault: true);\n\nIResourceBuilder ginapp;\n\nif (builder.ExecutionContext.IsPublishMode)\n{\n // Production build: multi-stage Dockerfile for optimized image\n ginapp = builder.AddDockerfile(\"ginapp\", \"./ginapp\")\n .WithBuildArg(\"GO_VERSION\", goVersion);\n}\nelse\n{\n // Development build: use Air for hot reload with bind mount\n ginapp = builder.AddDockerfile(\"ginapp\", \"./ginapp\", \"Dockerfile.dev\")\n .WithBuildArg(\"GO_VERSION\", goVersion)\n .WithBindMount(\"./ginapp\", \"/app\");\n}\n\nginapp\n .WithHttpEndpoint(targetPort: 5555, env: \"PORT\")\n .WithHttpHealthCheck(\"/\")\n .WithExternalHttpEndpoints()\n .WithOtlpExporter()\n .WithDeveloperCertificateTrust(trust: true);\n\nif (builder.ExecutionContext.IsPublishMode)\n{\n ginapp\n .WithEnvironment(\"GIN_MODE\", \"release\")\n // Trust all proxies when running behind a reverse proxy. If deploying to an environment\n // without a reverse proxy that ensures X-Forwarded-* headers are not forwarded from clients,\n // this should be removed.\n .WithEnvironment(\"TRUSTED_PROXIES\", \"all\");\n}\n\nbuilder.Build().Run();" }, { "name": "custom-resources", @@ -266,15 +266,15 @@ }, "appHost": "file-based", "appHostPath": "apphost.cs", - "appHostCode": "#pragma warning disable ASPIRECSHARPAPPS001\n#pragma warning disable ASPIREAZURE002\n\n#:sdk Aspire.AppHost.Sdk@13.5.0\n#:package Aspire.Hosting.Azure.Storage@13.5.0\n#:package Aspire.Hosting.Azure.Sql@13.5.0\n#:package Aspire.Hosting.JavaScript@13.5.0\n#:package Aspire.Hosting.Azure.AppContainers@13.5.0\n\nusing Aspire.Hosting.Azure;\nusing Azure.Provisioning.AppContainers;\nusing Azure.Provisioning.Expressions;\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nbuilder.AddAzureContainerAppEnvironment(\"env\");\n\n// Storage: Use Azurite emulator in run mode, real Azure in publish mode\nvar storage = builder.AddAzureStorage(\"storage\")\n .RunAsEmulator();\n\nvar blobs = storage.AddBlobContainer(\"images\");\nvar queues = storage.AddQueues(\"queues\");\n\n// Azure SQL Database\nvar sql = builder.AddAzureSqlServer(\"sql\")\n .RunAsContainer(c => c.WithLifetime(ContainerLifetime.Persistent))\n .AddDatabase(\"imagedb\");\n\n// API: Upload images, queue thumbnail jobs, serve metadata\nvar api = builder.AddCSharpApp(\"api\", \"./api\")\n .WithHttpHealthCheck(\"/health\")\n .WithExternalHttpEndpoints()\n .WaitFor(sql)\n .WithReference(blobs)\n .WithReference(queues)\n .WithReference(sql)\n .WithUrls(context =>\n {\n foreach (var u in context.Urls)\n {\n u.DisplayLocation = UrlDisplayLocation.DetailsOnly;\n }\n\n context.Urls.Add(new()\n {\n Url = \"/scalar\",\n DisplayText = \"API Reference\",\n Endpoint = context.GetEndpoint(\"https\")\n });\n })\n .PublishAsAzureContainerApp((infra, app) =>\n {\n // Scale to zero when idle\n app.Template.Scale.MinReplicas = 0;\n });\n\n// Worker: Container Apps Job for queue-triggered thumbnail generation\n// Event-driven: starts when messages arrive, exits within ~5 seconds when queue is empty\nvar worker = builder.AddCSharpApp(\"worker\", \"./worker\")\n .WithReference(blobs)\n .WithReference(queues)\n .WithReference(sql)\n .WaitFor(sql)\n .WaitFor(queues);\n\nif (builder.ExecutionContext.IsRunMode)\n{\n // In run mode, keep worker running continuously for fast local development\n worker = worker.WithEnvironment(\"WORKER_RUN_CONTINUOUSLY\", \"true\");\n}\nelse\n{\n // In publish mode, use event-driven scaling based on queue depth\n worker.PublishAsAzureContainerAppJob((infra, job) =>\n {\n var accountNameParameter = queues.Resource.Parent.NameOutputReference.AsProvisioningParameter(infra);\n\n // Resolve the identity annotation added to the worker app\n if (!worker.Resource.TryGetLastAnnotation(out var identityAnnotation))\n {\n throw new InvalidOperationException(\"Identity annotation not found.\");\n }\n\n job.Configuration.TriggerType = ContainerAppJobTriggerType.Event;\n job.Configuration.EventTriggerConfig.Scale.PollingIntervalInSeconds = 1;\n job.Configuration.EventTriggerConfig.Scale.Rules.Add(new ContainerAppJobScaleRule\n {\n Name = \"queue-rule\",\n JobScaleRuleType = \"azure-queue\",\n Metadata = new ObjectExpression(\n new PropertyExpression(\"accountName\", new IdentifierExpression(accountNameParameter.BicepIdentifier)),\n new PropertyExpression(\"queueName\", new StringLiteralExpression(\"thumbnails\")),\n new PropertyExpression(\"queueLength\", new IntLiteralExpression(1))\n ),\n Identity = identityAnnotation.IdentityResource.Id.AsProvisioningParameter(infra)\n });\n });\n}\n\n// Frontend: Vite+React for upload and gallery UI\nvar frontend = builder.AddViteApp(\"frontend\", \"./frontend\")\n .WithEndpoint(\"http\", e => e.Port = 9080)\n .WithReference(api)\n .WithUrl(\"\", \"Image Gallery\");\n\n// Publish: Embed frontend build output in API container\napi.PublishWithContainerFiles(frontend, \"wwwroot\");\n\nbuilder.Build().Run();" + "appHostCode": "#pragma warning disable ASPIRECSHARPAPPS001\n#pragma warning disable ASPIREAZURE002\n\n#:sdk Aspire.AppHost.Sdk@13.6.0\n#:package Aspire.Hosting.Azure.Storage@13.6.0\n#:package Aspire.Hosting.Azure.Sql@13.6.0\n#:package Aspire.Hosting.JavaScript@13.6.0\n#:package Aspire.Hosting.Azure.AppContainers@13.6.0\n\nusing Aspire.Hosting.Azure;\nusing Azure.Provisioning.AppContainers;\nusing Azure.Provisioning.Expressions;\n\nvar builder = DistributedApplication.CreateBuilder(args);\n\nbuilder.AddAzureContainerAppEnvironment(\"env\");\n\n// Storage: Use Azurite emulator in run mode, real Azure in publish mode\nvar storage = builder.AddAzureStorage(\"storage\")\n .RunAsEmulator();\n\nvar blobs = storage.AddBlobContainer(\"images\");\nvar queues = storage.AddQueues(\"queues\");\n\n// Azure SQL Database\nvar sql = builder.AddAzureSqlServer(\"sql\")\n .RunAsContainer(c => c.WithLifetime(ContainerLifetime.Persistent))\n .AddDatabase(\"imagedb\");\n\n// API: Upload images, queue thumbnail jobs, serve metadata\nvar api = builder.AddCSharpApp(\"api\", \"./api\")\n .WithHttpHealthCheck(\"/health\")\n .WithExternalHttpEndpoints()\n .WaitFor(sql)\n .WithReference(blobs)\n .WithReference(queues)\n .WithReference(sql)\n .WithUrls(context =>\n {\n foreach (var u in context.Urls)\n {\n u.DisplayLocation = UrlDisplayLocation.DetailsOnly;\n }\n\n context.Urls.Add(new()\n {\n Url = \"/scalar\",\n DisplayText = \"API Reference\",\n Endpoint = context.GetEndpoint(\"https\")\n });\n })\n .PublishAsAzureContainerApp((infra, app) =>\n {\n // Scale to zero when idle\n app.Template.Scale.MinReplicas = 0;\n });\n\n// Worker: Container Apps Job for queue-triggered thumbnail generation\n// Event-driven: starts when messages arrive, exits within ~5 seconds when queue is empty\nvar worker = builder.AddCSharpApp(\"worker\", \"./worker\")\n .WithReference(blobs)\n .WithReference(queues)\n .WithReference(sql)\n .WaitFor(sql)\n .WaitFor(queues);\n\nif (builder.ExecutionContext.IsRunMode)\n{\n // In run mode, keep worker running continuously for fast local development\n worker = worker.WithEnvironment(\"WORKER_RUN_CONTINUOUSLY\", \"true\");\n}\nelse\n{\n // In publish mode, use event-driven scaling based on queue depth\n worker.PublishAsAzureContainerAppJob((infra, job) =>\n {\n var accountNameParameter = queues.Resource.Parent.NameOutputReference.AsProvisioningParameter(infra);\n\n // Resolve the identity annotation added to the worker app\n if (!worker.Resource.TryGetLastAnnotation(out var identityAnnotation))\n {\n throw new InvalidOperationException(\"Identity annotation not found.\");\n }\n\n job.Configuration.TriggerType = ContainerAppJobTriggerType.Event;\n job.Configuration.EventTriggerConfig.Scale.PollingIntervalInSeconds = 1;\n job.Configuration.EventTriggerConfig.Scale.Rules.Add(new ContainerAppJobScaleRule\n {\n Name = \"queue-rule\",\n JobScaleRuleType = \"azure-queue\",\n Metadata = new ObjectExpression(\n new PropertyExpression(\"accountName\", new IdentifierExpression(accountNameParameter.BicepIdentifier)),\n new PropertyExpression(\"queueName\", new StringLiteralExpression(\"thumbnails\")),\n new PropertyExpression(\"queueLength\", new IntLiteralExpression(1))\n ),\n Identity = identityAnnotation.IdentityResource.Id.AsProvisioningParameter(infra)\n });\n });\n}\n\n// Frontend: Vite+React for upload and gallery UI\nvar frontend = builder.AddViteApp(\"frontend\", \"./frontend\")\n .WithEndpoint(\"http\", e => e.Port = 9080)\n .WithReference(api)\n .WithUrl(\"\", \"Image Gallery\");\n\n// Publish: Embed frontend build output in API container\napi.PublishWithContainerFiles(frontend, \"wwwroot\");\n\nbuilder.Build().Run();" }, { "name": "maildev-mailkit", "title": "MailDev and MailKit custom integrations", - "description": "This sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.5. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).", + "description": "This sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.6. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).", "href": "https://github.com/microsoft/aspire-samples/tree/main/samples/maildev-mailkit", - "readme": "# MailDev and MailKit custom integrations\n\nThis sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.5. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).\n\n## Projects\n\n- `MailDev.Hosting` models a MailDev container, its web and SMTP endpoints, and a deferred connection string.\n- `MailKit.Client` registers a scoped MailKit SMTP client, health checks, tracing, and metrics in a consuming service.\n- `NewsletterService` sends subscription and unsubscription messages through the integrations.\n- `ServiceDefaults` configures standard Aspire health checks and OpenTelemetry.\n- `CSharpAppHost` is a compile-validated C# equivalent of the runnable TypeScript AppHost.\n\n## Aspire Type System\n\nThe hosting integration marks `MailDevResource` and `AddMailDev` with `[AspireExport]`. The local project reference in `aspire.config.json` lets `aspire restore` inspect those exports and generate the TypeScript `addMailDev` API under `.aspire/modules`.\n\nGenerated files under `.aspire/modules` are not source files and must not be edited or committed.\n\n```json\n\"packages\": {\n \"MailDev.Hosting\": \"MailDev.Hosting/MailDev.Hosting.csproj\"\n}\n```\n\nThe TypeScript AppHost uses the generated API:\n\n```typescript\nconst maildev = await builder.addMailDev(\"maildev\");\n\nawait builder.addCSharpApp(\"newsletterservice\", \"./NewsletterService\")\n .withHttpHealthCheck({ path: \"/health\" })\n .withExternalHttpEndpoints()\n .withReference(maildev)\n .waitFor(maildev);\n```\n\nThe equivalent C# AppHost code is:\n\n```csharp\nvar maildev = builder.AddMailDev(\"maildev\");\n\nbuilder.AddProject(\"newsletterservice\", \"../NewsletterService/NewsletterService.csproj\")\n .WithHttpHealthCheck(\"/health\")\n .WithExternalHttpEndpoints()\n .WithReference(maildev)\n .WaitFor(maildev);\n```\n\n## Secure credentials\n\n`AddMailDev` creates a secret password parameter by default. The password is passed to MailDev through `MAILDEV_INCOMING_PASS` and to the newsletter service through a deferred connection string:\n\n```text\nEndpoint=smtp://{maildev.bindings.smtp.host}:{maildev.bindings.smtp.port};Username=mail-dev;Password={maildev-password.value}\n```\n\nThe AppHost model retains parameter and endpoint references instead of embedding a password or allocated port in source code. MailKit parses the resolved connection string, connects to SMTP, and authenticates for each service scope.\n\n## Run the sample\n\nPrerequisites are .NET 10, Node.js 24 or a supported Node.js 20/22 release, Docker, and Aspire CLI 13.5.\n\n```powershell\naspire update --self --channel staging\naspire restore\nnpm install\nnpm run aspire:build\naspire run\n```\n\nUse the newsletter service endpoint shown in the Aspire dashboard:\n\n```http\nPOST /subscribe\nContent-Type: application/json\n\n{ \"email\": \"reader@example.com\" }\n```\n\nOpen the MailDev web endpoint from the dashboard to inspect the generated message. Use `POST /unsubscribe` with the same payload to send the unsubscription message.\n\n## Tests\n\n```powershell\ndotnet test MailDev.Hosting.Tests/MailDev.Hosting.Tests.csproj\ndotnet test MailKit.Client.Tests/MailKit.Client.Tests.csproj\nnpm run aspire:build\nnpm run aspire:lint\ndotnet build CSharpAppHost/CSharpAppHost.csproj\n```", - "readmeRaw": "# MailDev and MailKit custom integrations\n\nThis sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.5. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).\n\n## Projects\n\n- `MailDev.Hosting` models a MailDev container, its web and SMTP endpoints, and a deferred connection string.\n- `MailKit.Client` registers a scoped MailKit SMTP client, health checks, tracing, and metrics in a consuming service.\n- `NewsletterService` sends subscription and unsubscription messages through the integrations.\n- `ServiceDefaults` configures standard Aspire health checks and OpenTelemetry.\n- `CSharpAppHost` is a compile-validated C# equivalent of the runnable TypeScript AppHost.\n\n## Aspire Type System\n\nThe hosting integration marks `MailDevResource` and `AddMailDev` with `[AspireExport]`. The local project reference in `aspire.config.json` lets `aspire restore` inspect those exports and generate the TypeScript `addMailDev` API under `.aspire/modules`.\n\nGenerated files under `.aspire/modules` are not source files and must not be edited or committed.\n\n```json\n\"packages\": {\n \"MailDev.Hosting\": \"MailDev.Hosting/MailDev.Hosting.csproj\"\n}\n```\n\nThe TypeScript AppHost uses the generated API:\n\n```typescript\nconst maildev = await builder.addMailDev(\"maildev\");\n\nawait builder.addCSharpApp(\"newsletterservice\", \"./NewsletterService\")\n .withHttpHealthCheck({ path: \"/health\" })\n .withExternalHttpEndpoints()\n .withReference(maildev)\n .waitFor(maildev);\n```\n\nThe equivalent C# AppHost code is:\n\n```csharp\nvar maildev = builder.AddMailDev(\"maildev\");\n\nbuilder.AddProject(\"newsletterservice\", \"../NewsletterService/NewsletterService.csproj\")\n .WithHttpHealthCheck(\"/health\")\n .WithExternalHttpEndpoints()\n .WithReference(maildev)\n .WaitFor(maildev);\n```\n\n## Secure credentials\n\n`AddMailDev` creates a secret password parameter by default. The password is passed to MailDev through `MAILDEV_INCOMING_PASS` and to the newsletter service through a deferred connection string:\n\n```text\nEndpoint=smtp://{maildev.bindings.smtp.host}:{maildev.bindings.smtp.port};Username=mail-dev;Password={maildev-password.value}\n```\n\nThe AppHost model retains parameter and endpoint references instead of embedding a password or allocated port in source code. MailKit parses the resolved connection string, connects to SMTP, and authenticates for each service scope.\n\n## Run the sample\n\nPrerequisites are .NET 10, Node.js 24 or a supported Node.js 20/22 release, Docker, and Aspire CLI 13.5.\n\n```powershell\naspire update --self --channel staging\naspire restore\nnpm install\nnpm run aspire:build\naspire run\n```\n\nUse the newsletter service endpoint shown in the Aspire dashboard:\n\n```http\nPOST /subscribe\nContent-Type: application/json\n\n{ \"email\": \"reader@example.com\" }\n```\n\nOpen the MailDev web endpoint from the dashboard to inspect the generated message. Use `POST /unsubscribe` with the same payload to send the unsubscription message.\n\n## Tests\n\n```powershell\ndotnet test MailDev.Hosting.Tests/MailDev.Hosting.Tests.csproj\ndotnet test MailKit.Client.Tests/MailKit.Client.Tests.csproj\nnpm run aspire:build\nnpm run aspire:lint\ndotnet build CSharpAppHost/CSharpAppHost.csproj\n```", + "readme": "# MailDev and MailKit custom integrations\n\nThis sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.6. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).\n\n## Projects\n\n- `MailDev.Hosting` models a MailDev container, its web and SMTP endpoints, and a deferred connection string.\n- `MailKit.Client` registers a scoped MailKit SMTP client, health checks, tracing, and metrics in a consuming service.\n- `NewsletterService` sends subscription and unsubscription messages through the integrations.\n- `ServiceDefaults` configures standard Aspire health checks and OpenTelemetry.\n- `CSharpAppHost` is a compile-validated C# equivalent of the runnable TypeScript AppHost.\n\n## Aspire Type System\n\nThe hosting integration marks `MailDevResource` and `AddMailDev` with `[AspireExport]`. The local project reference in `aspire.config.json` lets `aspire restore` inspect those exports and generate the TypeScript `addMailDev` API under `.aspire/modules`.\n\nGenerated files under `.aspire/modules` are not source files and must not be edited or committed.\n\n```json\n\"packages\": {\n \"MailDev.Hosting\": \"MailDev.Hosting/MailDev.Hosting.csproj\"\n}\n```\n\nThe TypeScript AppHost uses the generated API:\n\n```typescript\nconst maildev = await builder.addMailDev(\"maildev\");\n\nawait builder.addCSharpApp(\"newsletterservice\", \"./NewsletterService\")\n .withHttpHealthCheck({ path: \"/health\" })\n .withExternalHttpEndpoints()\n .withReference(maildev)\n .waitFor(maildev);\n```\n\nThe equivalent C# AppHost code is:\n\n```csharp\nvar maildev = builder.AddMailDev(\"maildev\");\n\nbuilder.AddProject(\"newsletterservice\", \"../NewsletterService/NewsletterService.csproj\")\n .WithHttpHealthCheck(\"/health\")\n .WithExternalHttpEndpoints()\n .WithReference(maildev)\n .WaitFor(maildev);\n```\n\n## Secure credentials\n\n`AddMailDev` creates a secret password parameter by default. The password is passed to MailDev through `MAILDEV_INCOMING_PASS` and to the newsletter service through a deferred connection string:\n\n```text\nEndpoint=smtp://{maildev.bindings.smtp.host}:{maildev.bindings.smtp.port};Username=mail-dev;Password={maildev-password.value}\n```\n\nThe AppHost model retains parameter and endpoint references instead of embedding a password or allocated port in source code. MailKit parses the resolved connection string, connects to SMTP, and authenticates for each service scope.\n\n## Run the sample\n\nPrerequisites are .NET 10, Node.js 24 or a supported Node.js 20/22 release, Docker, and the [Aspire 13.6 prerelease CLI used by this repository](../../README.md#aspire-version).\n\n```powershell\naspire restore\nnpm install\nnpm run aspire:build\naspire run\n```\n\nUse the newsletter service endpoint shown in the Aspire dashboard:\n\n```http\nPOST /subscribe\nContent-Type: application/json\n\n{ \"email\": \"reader@example.com\" }\n```\n\nOpen the MailDev web endpoint from the dashboard to inspect the generated message. Use `POST /unsubscribe` with the same payload to send the unsubscription message.\n\n## Tests\n\n```powershell\ndotnet test MailDev.Hosting.Tests/MailDev.Hosting.Tests.csproj\ndotnet test MailKit.Client.Tests/MailKit.Client.Tests.csproj\nnpm run aspire:build\nnpm run aspire:lint\ndotnet build CSharpAppHost/CSharpAppHost.csproj\n```", + "readmeRaw": "# MailDev and MailKit custom integrations\n\nThis sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.6. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).\n\n## Projects\n\n- `MailDev.Hosting` models a MailDev container, its web and SMTP endpoints, and a deferred connection string.\n- `MailKit.Client` registers a scoped MailKit SMTP client, health checks, tracing, and metrics in a consuming service.\n- `NewsletterService` sends subscription and unsubscription messages through the integrations.\n- `ServiceDefaults` configures standard Aspire health checks and OpenTelemetry.\n- `CSharpAppHost` is a compile-validated C# equivalent of the runnable TypeScript AppHost.\n\n## Aspire Type System\n\nThe hosting integration marks `MailDevResource` and `AddMailDev` with `[AspireExport]`. The local project reference in `aspire.config.json` lets `aspire restore` inspect those exports and generate the TypeScript `addMailDev` API under `.aspire/modules`.\n\nGenerated files under `.aspire/modules` are not source files and must not be edited or committed.\n\n```json\n\"packages\": {\n \"MailDev.Hosting\": \"MailDev.Hosting/MailDev.Hosting.csproj\"\n}\n```\n\nThe TypeScript AppHost uses the generated API:\n\n```typescript\nconst maildev = await builder.addMailDev(\"maildev\");\n\nawait builder.addCSharpApp(\"newsletterservice\", \"./NewsletterService\")\n .withHttpHealthCheck({ path: \"/health\" })\n .withExternalHttpEndpoints()\n .withReference(maildev)\n .waitFor(maildev);\n```\n\nThe equivalent C# AppHost code is:\n\n```csharp\nvar maildev = builder.AddMailDev(\"maildev\");\n\nbuilder.AddProject(\"newsletterservice\", \"../NewsletterService/NewsletterService.csproj\")\n .WithHttpHealthCheck(\"/health\")\n .WithExternalHttpEndpoints()\n .WithReference(maildev)\n .WaitFor(maildev);\n```\n\n## Secure credentials\n\n`AddMailDev` creates a secret password parameter by default. The password is passed to MailDev through `MAILDEV_INCOMING_PASS` and to the newsletter service through a deferred connection string:\n\n```text\nEndpoint=smtp://{maildev.bindings.smtp.host}:{maildev.bindings.smtp.port};Username=mail-dev;Password={maildev-password.value}\n```\n\nThe AppHost model retains parameter and endpoint references instead of embedding a password or allocated port in source code. MailKit parses the resolved connection string, connects to SMTP, and authenticates for each service scope.\n\n## Run the sample\n\nPrerequisites are .NET 10, Node.js 24 or a supported Node.js 20/22 release, Docker, and the [Aspire 13.6 prerelease CLI used by this repository](../../README.md#aspire-version).\n\n```powershell\naspire restore\nnpm install\nnpm run aspire:build\naspire run\n```\n\nUse the newsletter service endpoint shown in the Aspire dashboard:\n\n```http\nPOST /subscribe\nContent-Type: application/json\n\n{ \"email\": \"reader@example.com\" }\n```\n\nOpen the MailDev web endpoint from the dashboard to inspect the generated message. Use `POST /unsubscribe` with the same payload to send the unsubscription message.\n\n## Tests\n\n```powershell\ndotnet test MailDev.Hosting.Tests/MailDev.Hosting.Tests.csproj\ndotnet test MailKit.Client.Tests/MailKit.Client.Tests.csproj\nnpm run aspire:build\nnpm run aspire:lint\ndotnet build CSharpAppHost/CSharpAppHost.csproj\n```", "tags": [ "csharp", "dashboard", @@ -458,13 +458,38 @@ "appHostPath": "apphost.mts", "appHostCode": "import { createBuilder } from \"./.aspire/modules/aspire.mjs\";\n\nconst builder = await createBuilder();\n\nconst openAiApiKey = await builder.addParameter(\"openai-api-key\", { secret: true });\n\nconst qdrant = await builder.addQdrant(\"qdrant\");\n\nawait builder.addOpenAI(\"openai\")\n .withApiKey(openAiApiKey);\n\nconst api = await builder.addUvicornApp(\"api\", \"./api\", \"main:app\")\n .withUv()\n .withHttpHealthCheck({ path: \"/health\" })\n .waitFor(qdrant)\n .withReference(qdrant)\n .withEnvironment(\"OPENAI_APIKEY\", openAiApiKey)\n .withExternalHttpEndpoints();\n\nconst frontend = await builder.addViteApp(\"frontend\", \"./frontend\")\n .withReference(api)\n .withUrl(\"\", { displayText: \"RAG UI\" });\n\nawait api.publishWithContainerFiles(frontend, \"public\");\n\nawait builder.build().run();" }, + { + "name": "spring-petclinic", + "title": "Spring Petclinic with Angular, Spring Boot, and PostgreSQL", + "description": "A compact Petclinic-style application demonstrating the official Aspire Java hosting\nintegration from a TypeScript AppHost.", + "href": "https://github.com/microsoft/aspire-samples/tree/main/samples/spring-petclinic", + "readme": "# Spring Petclinic with Angular, Spring Boot, and PostgreSQL\n\n![Screenshot of the Spring Petclinic Angular frontend](~/assets/samples/spring-petclinic/spring-petclinic.png)\n\nA compact Petclinic-style application demonstrating the official Aspire Java hosting\nintegration from a TypeScript AppHost.\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser --> Angular[Angular frontend]\n Angular -->|/api| API[Spring Boot API]\n API --> PostgreSQL\n```\n\nThe frontend lists owners, pets, and veterinarians and can create owners through the API.\nThe API persists data in an Aspire-managed PostgreSQL database.\n\n## What this demonstrates\n\n- `addJavaApp` with Maven and Spring Boot\n- `withWrapperPath` for a cross-platform Maven wrapper\n- PostgreSQL JDBC URL and credentials supplied through Aspire expressions\n- Angular-to-API endpoint wiring without hardcoded service URLs\n- Resource references, startup ordering, health checks, and external endpoints\n- A TypeScript AppHost orchestrating Java, TypeScript, and a container resource\n- Generated OpenAPI documentation and an interactive Scalar API reference\n- Java agent instrumentation exporting traces, metrics, and logs to Aspire\n\n## Prerequisites\n\n- Aspire CLI **13.6.0**, installed using the [root installation instructions](../../README.md#aspire-version)\n- Java Development Kit 21 or later\n- Node.js 20.19, 22.13, or 24 or later\n- Docker Desktop or another Docker-compatible container runtime\n\nFollow the [root installation instructions](../../README.md#aspire-version) to install\nthe matching released CLI. The CLI/SDK and other integrations use `13.6.0`, while the\nJava integration uses the accompanying `13.6.0-preview.1.26479.8` package.\nAll packages are available on NuGet.org; no staging feed is required.\n\n## Run\n\nFrom this sample's directory:\n\n```bash\naspire start\n```\n\nOpen the `frontend` endpoint from the Aspire dashboard. The AppHost starts PostgreSQL,\nbuilds the API and downloads its pinned OpenTelemetry agent, waits for the database\nto become ready, starts the Spring Boot API, and then starts Angular.\n\n## Explore the API and telemetry\n\nOpen **API Reference** on the `api` resource in the Aspire dashboard to use Scalar at\n`/scalar`. Its OpenAPI document is generated from the existing controllers and validation\nrules at `/v3/api-docs`. Scalar and its JavaScript bundle are served by the Spring Boot API;\nthere is no additional frontend service or documentation build.\n\nUse Scalar's **Test Request** to list owners or veterinarians, or create an owner in the\nAngular frontend. Then select the `api` resource in the Aspire dashboard:\n\n- **Traces** shows HTTP requests and their PostgreSQL/JDBC spans.\n- **Metrics** shows JVM and HTTP measurements.\n- **Structured logs** includes the `Created owner` event, correlated with its request trace.\n\nMaven copies the pinned Java agent to `api/target/agent/opentelemetry-javaagent.jar`.\n`withOtelAgentDefaultPath()` makes Aspire build the agent dependency before launching\nthe API. The Java integration supplies the OTLP endpoint and authentication settings;\nno collector, hardcoded telemetry endpoint, or application-level telemetry SDK is needed.\n\n## Project layout\n\n- `apphost.mts` - TypeScript AppHost and resource graph\n- `api` - Spring Boot REST API using Spring Data JPA\n- `frontend` - Angular single-page application\n\nOn Windows, `api/tools/run-mvnw.cmd` preserves the wrapper's directory-qualified path.\nThe helper invokes `.\\mvnw.cmd` explicitly so Windows resolves it from the working directory.\n\n## Security notes\n\nThis is a trusted local demo, not a production template. Its HTTP endpoints have no\napplication authentication or transport encryption. Anyone who can reach the API can\nread all owner records and create owners. Do not publicly expose or forward these\nendpoints; the Angular development server listens on all interfaces (`0.0.0.0`).\n\nUse synthetic data only. PostgreSQL uses Aspire-generated development credentials,\nhas no persistent volume configured, and should be treated as disposable. Hibernate\nautomatically updates the database schema (`spring.jpa.hibernate.ddl-auto=update`).\nBefore adapting this sample for production, add authentication, authorization,\ntransport encryption, request-rate controls, and production secret management.\n", + "readmeRaw": "# Spring Petclinic with Angular, Spring Boot, and PostgreSQL\n\n![Screenshot of the Spring Petclinic Angular frontend](./images/spring-petclinic.png)\n\nA compact Petclinic-style application demonstrating the official Aspire Java hosting\nintegration from a TypeScript AppHost.\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser --> Angular[Angular frontend]\n Angular -->|/api| API[Spring Boot API]\n API --> PostgreSQL\n```\n\nThe frontend lists owners, pets, and veterinarians and can create owners through the API.\nThe API persists data in an Aspire-managed PostgreSQL database.\n\n## What this demonstrates\n\n- `addJavaApp` with Maven and Spring Boot\n- `withWrapperPath` for a cross-platform Maven wrapper\n- PostgreSQL JDBC URL and credentials supplied through Aspire expressions\n- Angular-to-API endpoint wiring without hardcoded service URLs\n- Resource references, startup ordering, health checks, and external endpoints\n- A TypeScript AppHost orchestrating Java, TypeScript, and a container resource\n- Generated OpenAPI documentation and an interactive Scalar API reference\n- Java agent instrumentation exporting traces, metrics, and logs to Aspire\n\n## Prerequisites\n\n- Aspire CLI **13.6.0**, installed using the [root installation instructions](../../README.md#aspire-version)\n- Java Development Kit 21 or later\n- Node.js 20.19, 22.13, or 24 or later\n- Docker Desktop or another Docker-compatible container runtime\n\nFollow the [root installation instructions](../../README.md#aspire-version) to install\nthe matching released CLI. The CLI/SDK and other integrations use `13.6.0`, while the\nJava integration uses the accompanying `13.6.0-preview.1.26479.8` package.\nAll packages are available on NuGet.org; no staging feed is required.\n\n## Run\n\nFrom this sample's directory:\n\n```bash\naspire start\n```\n\nOpen the `frontend` endpoint from the Aspire dashboard. The AppHost starts PostgreSQL,\nbuilds the API and downloads its pinned OpenTelemetry agent, waits for the database\nto become ready, starts the Spring Boot API, and then starts Angular.\n\n## Explore the API and telemetry\n\nOpen **API Reference** on the `api` resource in the Aspire dashboard to use Scalar at\n`/scalar`. Its OpenAPI document is generated from the existing controllers and validation\nrules at `/v3/api-docs`. Scalar and its JavaScript bundle are served by the Spring Boot API;\nthere is no additional frontend service or documentation build.\n\nUse Scalar's **Test Request** to list owners or veterinarians, or create an owner in the\nAngular frontend. Then select the `api` resource in the Aspire dashboard:\n\n- **Traces** shows HTTP requests and their PostgreSQL/JDBC spans.\n- **Metrics** shows JVM and HTTP measurements.\n- **Structured logs** includes the `Created owner` event, correlated with its request trace.\n\nMaven copies the pinned Java agent to `api/target/agent/opentelemetry-javaagent.jar`.\n`withOtelAgentDefaultPath()` makes Aspire build the agent dependency before launching\nthe API. The Java integration supplies the OTLP endpoint and authentication settings;\nno collector, hardcoded telemetry endpoint, or application-level telemetry SDK is needed.\n\n## Project layout\n\n- `apphost.mts` - TypeScript AppHost and resource graph\n- `api` - Spring Boot REST API using Spring Data JPA\n- `frontend` - Angular single-page application\n\nOn Windows, `api/tools/run-mvnw.cmd` preserves the wrapper's directory-qualified path.\nThe helper invokes `.\\mvnw.cmd` explicitly so Windows resolves it from the working directory.\n\n## Security notes\n\nThis is a trusted local demo, not a production template. Its HTTP endpoints have no\napplication authentication or transport encryption. Anyone who can reach the API can\nread all owner records and create owners. Do not publicly expose or forward these\nendpoints; the Angular development server listens on all interfaces (`0.0.0.0`).\n\nUse synthetic data only. PostgreSQL uses Aspire-generated development credentials,\nhas no persistent volume configured, and should be treated as disposable. Hibernate\nautomatically updates the database schema (`spring.jpa.hibernate.ddl-auto=update`).\nBefore adapting this sample for production, add authentication, authorization,\ntransport encryption, request-rate controls, and production secret management.\n", + "tags": [ + "dashboard", + "databases", + "docker", + "health-checks", + "java", + "javascript", + "metrics", + "node", + "postgresql", + "typescript", + "volumes" + ], + "thumbnail": "~/assets/samples/spring-petclinic/spring-petclinic.png", + "appHost": "typescript", + "appHostPath": "apphost.mts", + "appHostCode": "import { EndpointProperty, createBuilder } from \"./.aspire/modules/aspire.mjs\";\n\nconst builder = await createBuilder();\nconst mavenWrapper = process.platform === \"win32\" ? \"tools/run-mvnw.cmd\" : \"./mvnw\";\n\n// Disposable demo database: no persistent volume; the API lets Hibernate update its schema.\nconst postgres = await builder.addPostgres(\"postgres\");\nconst database = await postgres.addDatabase(\"petclinic\");\n\n// Local demo: the HTTP API has no authentication or transport encryption. Use synthetic owner data only.\nconst api = await builder.addJavaApp(\"api\", \"./api\")\n .withWrapperPath(mavenWrapper)\n .withMavenGoal(\"spring-boot:run\", [])\n .withOtelAgentDefaultPath()\n .withJvmArgs([\"-Xms128m\", \"-Xmx512m\"])\n .withHttpEndpoint({ targetPort: 8080, env: \"SERVER_PORT\" })\n .withEnvironment(\"SPRING_DATASOURCE_URL\", await database.jdbcConnectionString())\n .withEnvironment(\"SPRING_DATASOURCE_USERNAME\", await postgres.userNameReference())\n .withEnvironment(\"SPRING_DATASOURCE_PASSWORD\", postgres.passwordParameter.get())\n .withReference(database)\n .waitFor(database)\n .withHttpHealthCheck({ path: \"/actuator/health\" })\n .withExternalHttpEndpoints()\n .withUrls(async (context) => {\n const endpoint = context.getEndpoint(\"http\");\n const urls = await context.urls();\n await urls.addForEndpoint(endpoint, `${await endpoint.url()}/scalar`, { displayText: \"API Reference\" });\n });\n\n// Angular listens on all interfaces; keep both HTTP endpoints private and do not publicly forward them.\nawait builder.addJavaScriptApp(\"frontend\", \"./frontend\", { runScriptName: \"start\" })\n .withHttpEndpoint({ targetPort: 4200 })\n .withEnvironment(\"PETCLINIC_API_URL\", api.getEndpoint(\"http\").property(EndpointProperty.Url))\n .withReference(api)\n .waitFor(api)\n .withHttpHealthCheck({ path: \"/\" })\n .withExternalHttpEndpoints();\n\nawait builder.build().run();" + }, { "name": "standalone-dashboard", "title": "Standalone Aspire dashboard sample app", "description": "View telemetry from any app in the Aspire dashboard. The dashboard supports running standalone, and apps configured with an [OpenTelemetry SDK](https://opentelemetry.io/docs/getting-started/dev/) can send it data.\n\nThis sample is a .NET console app that downloads data from [NuGet](https://nuget.org/). The app sends telemetry to the Aspire dashboard which is viewed in the dashboard telemetry UI.", "href": "https://github.com/microsoft/aspire-samples/tree/main/samples/standalone-dashboard", - "readme": "# Standalone Aspire dashboard sample app\n\nView telemetry from any app in the Aspire dashboard. The dashboard supports running standalone, and apps configured with an [OpenTelemetry SDK](https://opentelemetry.io/docs/getting-started/dev/) can send it data.\n\nThis sample is a .NET console app that downloads data from [NuGet](https://nuget.org/). The app sends telemetry to the Aspire dashboard which is viewed in the dashboard telemetry UI.\n\n![Screenshot of the standalone Aspire dashboard](~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png#gh-light-mode-only)\n![Screenshot of the standalone Aspire dashboard](~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot.png#gh-dark-mode-only)\n\n## Demonstrates\n\n- How to run the Aspire dashboard from a Docker container\n- How to configure a .NET app to export telemetry to the dashboard\n- How to view telemetry in the Aspire dashboard\n\n## Sample prerequisites\n\nThis sample is written in C# and targets .NET 10.0. It requires the [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later.\n\nThis sample runs the Aspire dashboard from a Docker container. It requires Docker to be installed.\n\n## Start Aspire dashboard\n\nThe following command starts the Aspire dashboard in a Docker container:\n\n``` bash\ndocker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest\n```\n\nThe docker command:\n\n- Starts a container from the `mcr.microsoft.com/dotnet/nightly/aspire-dashboard` image.\n- The container has two ports:\n - Port `4317` receives OpenTelemetry data from apps. Apps send data using [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/).\n - Port `18888` has the dashboard UI. Navigate to http://localhost:18888 in the browser to view the dashboard.\n\n> [!NOTE]\n> The dashboard currently only supports the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc). Apps sending telemetry to the dashboard must be configured to use the `grpc` protocol. There are a couple of options for configuring apps:\n>\n> - Configure the OpenTelemetry SDK inside the app to use the gRPC OTLP protocol, or\n> - Start the app with the [`OTEL_EXPORTER_OTLP_PROTOCOL` environment variable](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options) with a value of `grpc`.\n\n## Login to the Aspire dashboard\n\nData displayed in the dashboard can be sensitive. By default, the dashboard is secured with authentication that requires a token to login.\n\nWhen the dashboard is run from a standalone container the login token is printed to the container logs. After copying the highlighted token into the login page, select the *Login* button.\n\n![Screenshot of the Aspire dashboard container logs](~/assets/samples/standalone-dashboard/aspire-dashboard-container-log.png)\n\nFor more information about logging into the dashboard, see [Dashboard authentication](https://aspire.dev/dashboard/explore/#dashboard-authentication).\n\n## Running the sample\n\nTo download and run the sample, follow these steps:\n\n1. Clone the `dotnet/aspire-samples` repository.\n2. Navigate to the folder that holds the sample code.\n3. At the command line, type [`dotnet run ConsoleApp.cs`](https://learn.microsoft.com/dotnet/core/tools/dotnet-run).\n\nRun the .NET app by executing the following at the command prompt (opened to the base directory of the sample):\n\n``` bash\ndotnet run ConsoleApp.cs\n```\n\n1. The console app launches, downloads information about the top NuGet packages and then exits.\n2. View the Aspire dashboard at http://localhost:18888 to see app telemetry.\n 1. View structured logs to see the list of top NuGet packages.\n 2. View traces to see HTTP requests made.\n 3. View metrics to see numeric data about the app such as average HTTP request duration.\n\n## Configure OpenTelemetry\n\nThe telemetry export endpoint is configured with the `OTEL_EXPORTER_OTLP_ENDPOINT` setting. This value is set to `http://localhost:4317` in the sample's `ConsoleApp.run.json` file. Removing the `OTEL_EXPORTER_OTLP_ENDPOINT` value disables exporting telemetry.\n", - "readmeRaw": "# Standalone Aspire dashboard sample app\n\nView telemetry from any app in the Aspire dashboard. The dashboard supports running standalone, and apps configured with an [OpenTelemetry SDK](https://opentelemetry.io/docs/getting-started/dev/) can send it data.\n\nThis sample is a .NET console app that downloads data from [NuGet](https://nuget.org/). The app sends telemetry to the Aspire dashboard which is viewed in the dashboard telemetry UI.\n\n![Screenshot of the standalone Aspire dashboard](./images/aspire-dashboard-screenshot.png)\n\n## Demonstrates\n\n- How to run the Aspire dashboard from a Docker container\n- How to configure a .NET app to export telemetry to the dashboard\n- How to view telemetry in the Aspire dashboard\n\n## Sample prerequisites\n\nThis sample is written in C# and targets .NET 10.0. It requires the [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later.\n\nThis sample runs the Aspire dashboard from a Docker container. It requires Docker to be installed.\n\n## Start Aspire dashboard\n\nThe following command starts the Aspire dashboard in a Docker container:\n\n``` bash\ndocker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest\n```\n\nThe docker command:\n\n- Starts a container from the `mcr.microsoft.com/dotnet/nightly/aspire-dashboard` image.\n- The container has two ports:\n - Port `4317` receives OpenTelemetry data from apps. Apps send data using [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/).\n - Port `18888` has the dashboard UI. Navigate to http://localhost:18888 in the browser to view the dashboard.\n\n> [!NOTE]\n> The dashboard currently only supports the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc). Apps sending telemetry to the dashboard must be configured to use the `grpc` protocol. There are a couple of options for configuring apps:\n>\n> - Configure the OpenTelemetry SDK inside the app to use the gRPC OTLP protocol, or\n> - Start the app with the [`OTEL_EXPORTER_OTLP_PROTOCOL` environment variable](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options) with a value of `grpc`.\n\n## Login to the Aspire dashboard\n\nData displayed in the dashboard can be sensitive. By default, the dashboard is secured with authentication that requires a token to login.\n\nWhen the dashboard is run from a standalone container the login token is printed to the container logs. After copying the highlighted token into the login page, select the *Login* button.\n\n![Screenshot of the Aspire dashboard container logs](./images/aspire-dashboard-container-log.png)\n\nFor more information about logging into the dashboard, see [Dashboard authentication](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/explore#dashboard-authentication).\n\n## Running the sample\n\nTo download and run the sample, follow these steps:\n\n1. Clone the `dotnet/aspire-samples` repository.\n2. Navigate to the folder that holds the sample code.\n3. At the command line, type [`dotnet run ConsoleApp.cs`](https://learn.microsoft.com/dotnet/core/tools/dotnet-run).\n\nRun the .NET app by executing the following at the command prompt (opened to the base directory of the sample):\n\n``` bash\ndotnet run ConsoleApp.cs\n```\n\n1. The console app launches, downloads information about the top NuGet packages and then exits.\n2. View the Aspire dashboard at http://localhost:18888 to see app telemetry.\n 1. View structured logs to see the list of top NuGet packages.\n 2. View traces to see HTTP requests made.\n 3. View metrics to see numeric data about the app such as average HTTP request duration.\n\n## Configure OpenTelemetry\n\nThe telemetry export endpoint is configured with the `OTEL_EXPORTER_OTLP_ENDPOINT` setting. This value is set to `http://localhost:4317` in the sample's `ConsoleApp.run.json` file. Removing the `OTEL_EXPORTER_OTLP_ENDPOINT` value disables exporting telemetry.\n", + "readme": "# Standalone Aspire dashboard sample app\n\nView telemetry from any app in the Aspire dashboard. The dashboard supports running standalone, and apps configured with an [OpenTelemetry SDK](https://opentelemetry.io/docs/getting-started/dev/) can send it data.\n\nThis sample is a .NET console app that downloads data from [NuGet](https://nuget.org/). The app sends telemetry to the Aspire dashboard which is viewed in the dashboard telemetry UI.\n\n![Screenshot of the standalone Aspire dashboard (light theme)](~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png#gh-light-mode-only)\n![Screenshot of the standalone Aspire dashboard (dark theme)](~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-dark.png#gh-dark-mode-only)\n\n## Demonstrates\n\n- How to run the Aspire dashboard from a Docker container\n- How to configure a .NET app to export telemetry to the dashboard\n- How to view telemetry in the Aspire dashboard\n\n## Sample prerequisites\n\nThis sample is written in C# and targets .NET 10.0. It requires the [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later.\n\nThis sample runs the Aspire dashboard from a Docker container. It requires Docker to be installed.\n\n## Start Aspire dashboard\n\nThe following command starts the Aspire dashboard in a Docker container:\n\n``` bash\ndocker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/nightly/aspire-dashboard:13.6\n```\n\nThe docker command:\n\n- Starts a container from the `mcr.microsoft.com/dotnet/nightly/aspire-dashboard:13.6` prerelease image.\n- The container has two ports:\n - Port `4317` receives OpenTelemetry data from apps. Apps send data using [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/).\n - Port `18888` has the dashboard UI. Navigate to http://localhost:18888 in the browser to view the dashboard.\n\n> [!NOTE]\n> The dashboard currently only supports the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc). Apps sending telemetry to the dashboard must be configured to use the `grpc` protocol. There are a couple of options for configuring apps:\n>\n> - Configure the OpenTelemetry SDK inside the app to use the gRPC OTLP protocol, or\n> - Start the app with the [`OTEL_EXPORTER_OTLP_PROTOCOL` environment variable](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options) with a value of `grpc`.\n\n## Login to the Aspire dashboard\n\nData displayed in the dashboard can be sensitive. By default, the dashboard is secured with authentication that requires a token to login.\n\nWhen the dashboard is run from a standalone container the login token is printed to the container logs. After copying the highlighted token into the login page, select the *Login* button.\n\n![Screenshot of the Aspire dashboard container logs](~/assets/samples/standalone-dashboard/aspire-dashboard-container-log.png)\n\nFor more information about logging into the dashboard, see [Dashboard authentication](https://aspire.dev/dashboard/explore/#dashboard-authentication).\n\n## Running the sample\n\nTo download and run the sample, follow these steps:\n\n1. Clone the `dotnet/aspire-samples` repository.\n2. Navigate to the folder that holds the sample code.\n3. At the command line, type [`dotnet run ConsoleApp.cs`](https://learn.microsoft.com/dotnet/core/tools/dotnet-run).\n\nRun the .NET app by executing the following at the command prompt (opened to the base directory of the sample):\n\n``` bash\ndotnet run ConsoleApp.cs\n```\n\n1. The console app launches, downloads information about the top NuGet packages and then exits.\n2. View the Aspire dashboard at http://localhost:18888 to see app telemetry.\n 1. View structured logs to see the list of top NuGet packages.\n 2. View traces to see HTTP requests made.\n 3. View metrics to see numeric data about the app such as average HTTP request duration.\n\n## Configure OpenTelemetry\n\nThe telemetry export endpoint is configured with the `OTEL_EXPORTER_OTLP_ENDPOINT` setting. This value is set to `http://localhost:4317` in the sample's `ConsoleApp.run.json` file. Removing the `OTEL_EXPORTER_OTLP_ENDPOINT` value disables exporting telemetry.\n", + "readmeRaw": "# Standalone Aspire dashboard sample app\n\nView telemetry from any app in the Aspire dashboard. The dashboard supports running standalone, and apps configured with an [OpenTelemetry SDK](https://opentelemetry.io/docs/getting-started/dev/) can send it data.\n\nThis sample is a .NET console app that downloads data from [NuGet](https://nuget.org/). The app sends telemetry to the Aspire dashboard which is viewed in the dashboard telemetry UI.\n\n![Screenshot of the standalone Aspire dashboard (light theme)](./images/aspire-dashboard-screenshot-light.png#gh-light-mode-only)\n![Screenshot of the standalone Aspire dashboard (dark theme)](./images/aspire-dashboard-screenshot-dark.png#gh-dark-mode-only)\n\n## Demonstrates\n\n- How to run the Aspire dashboard from a Docker container\n- How to configure a .NET app to export telemetry to the dashboard\n- How to view telemetry in the Aspire dashboard\n\n## Sample prerequisites\n\nThis sample is written in C# and targets .NET 10.0. It requires the [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later.\n\nThis sample runs the Aspire dashboard from a Docker container. It requires Docker to be installed.\n\n## Start Aspire dashboard\n\nThe following command starts the Aspire dashboard in a Docker container:\n\n``` bash\ndocker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/nightly/aspire-dashboard:13.6\n```\n\nThe docker command:\n\n- Starts a container from the `mcr.microsoft.com/dotnet/nightly/aspire-dashboard:13.6` prerelease image.\n- The container has two ports:\n - Port `4317` receives OpenTelemetry data from apps. Apps send data using [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/).\n - Port `18888` has the dashboard UI. Navigate to http://localhost:18888 in the browser to view the dashboard.\n\n> [!NOTE]\n> The dashboard currently only supports the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc). Apps sending telemetry to the dashboard must be configured to use the `grpc` protocol. There are a couple of options for configuring apps:\n>\n> - Configure the OpenTelemetry SDK inside the app to use the gRPC OTLP protocol, or\n> - Start the app with the [`OTEL_EXPORTER_OTLP_PROTOCOL` environment variable](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options) with a value of `grpc`.\n\n## Login to the Aspire dashboard\n\nData displayed in the dashboard can be sensitive. By default, the dashboard is secured with authentication that requires a token to login.\n\nWhen the dashboard is run from a standalone container the login token is printed to the container logs. After copying the highlighted token into the login page, select the *Login* button.\n\n![Screenshot of the Aspire dashboard container logs](./images/aspire-dashboard-container-log.png)\n\nFor more information about logging into the dashboard, see [Dashboard authentication](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/explore#dashboard-authentication).\n\n## Running the sample\n\nTo download and run the sample, follow these steps:\n\n1. Clone the `dotnet/aspire-samples` repository.\n2. Navigate to the folder that holds the sample code.\n3. At the command line, type [`dotnet run ConsoleApp.cs`](https://learn.microsoft.com/dotnet/core/tools/dotnet-run).\n\nRun the .NET app by executing the following at the command prompt (opened to the base directory of the sample):\n\n``` bash\ndotnet run ConsoleApp.cs\n```\n\n1. The console app launches, downloads information about the top NuGet packages and then exits.\n2. View the Aspire dashboard at http://localhost:18888 to see app telemetry.\n 1. View structured logs to see the list of top NuGet packages.\n 2. View traces to see HTTP requests made.\n 3. View metrics to see numeric data about the app such as average HTTP request duration.\n\n## Configure OpenTelemetry\n\nThe telemetry export endpoint is configured with the `OTEL_EXPORTER_OTLP_ENDPOINT` setting. This value is set to `http://localhost:4317` in the sample's `ConsoleApp.run.json` file. Removing the `OTEL_EXPORTER_OTLP_ENDPOINT` value disables exporting telemetry.\n", "tags": [ "csharp", "dashboard", @@ -474,12 +499,31 @@ ], "thumbnail": { "light": "~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-light.png#gh-light-mode-only", - "dark": "~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot.png#gh-dark-mode-only" + "dark": "~/assets/samples/standalone-dashboard/aspire-dashboard-screenshot-dark.png#gh-dark-mode-only" }, "appHost": null, "appHostPath": null, "appHostCode": null }, + { + "name": "terminals", + "title": "Aspire terminals", + "description": "Use interactive tools alongside your application without installing them on the host, copying connection strings, or leaving the Aspire dashboard.\n\n| Sample | AppHost | API | Terminal features |\n| --- | --- | --- | --- |\n| [C# basics](./basics-csharp/) | File-based C# | ASP.NET Core | `WithTerminal()` and Redis `WithRepl()` |\n| [TypeScript basics](./basics-typescript/) | TypeScript | Express/TypeScript | `withTerminal()` and Redis `withRepl()` |\n| [Terminal automation](./automation-csharp/) | File-based C# | ASP.NET Core | Drive Slumber with `SendKeyAsync` / `WaitForTextAsync`, verify CRUD, handle repeat runs |\n| [Custom docked SQL REPL](./docked-repl-csharp/) | File-based C# | rqlite HTTP API | Custom container resource and `TerminalService` / `TerminalPlacement.Dock` |\n\nEach basic sample is standalone: its own API, Redis database, Dockerfile, and saved request collection. Both demonstrate the same round trip: create a note in a REST terminal UI, edit its underlying Redis value in a REPL, then read the changed value through the API.\n\nThe automation sample reuses the C# basic sample's API and Slumber image source, but launches its own resources and supplies a separate request collection. The custom REPL sample is independent and uses rqlite's official image and SQL shell. Each sample has its own AppHost and can run separately.", + "href": "https://github.com/microsoft/aspire-samples/tree/main/samples/terminals", + "readme": "# Aspire terminals\n\nUse interactive tools alongside your application without installing them on the host, copying connection strings, or leaving the Aspire dashboard.\n\n| Sample | AppHost | API | Terminal features |\n| --- | --- | --- | --- |\n| [C# basics](./basics-csharp/) | File-based C# | ASP.NET Core | `WithTerminal()` and Redis `WithRepl()` |\n| [TypeScript basics](./basics-typescript/) | TypeScript | Express/TypeScript | `withTerminal()` and Redis `withRepl()` |\n| [Terminal automation](./automation-csharp/) | File-based C# | ASP.NET Core | Drive Slumber with `SendKeyAsync` / `WaitForTextAsync`, verify CRUD, handle repeat runs |\n| [Custom docked SQL REPL](./docked-repl-csharp/) | File-based C# | rqlite HTTP API | Custom container resource and `TerminalService` / `TerminalPlacement.Dock` |\n\nEach basic sample is standalone: its own API, Redis database, Dockerfile, and saved request collection. Both demonstrate the same round trip: create a note in a REST terminal UI, edit its underlying Redis value in a REPL, then read the changed value through the API.\n\nThe automation sample reuses the C# basic sample's API and Slumber image source, but launches its own resources and supplies a separate request collection. The custom REPL sample is independent and uses rqlite's official image and SQL shell. Each sample has its own AppHost and can run separately.\n\n## Two different terminal experiences\n\n**Slumber** runs as the main process of a container. `WithTerminal()` / `withTerminal()` makes that process interactive in the dashboard's console view, including keyboard input and terminal resizing.\n\n**Redis** continues running as a normal server. `WithRepl()` / `withRepl()` adds an on-demand **REPL** resource action that launches an authenticated `redis-cli` terminal. It does not attach interactive input to the Redis server process.\n\nThe REST client is [Slumber](https://github.com/LucasPickering/slumber), an MIT-licensed Rust application with YAML request collections and environment substitution. The basic samples package checksum-verified Slumber 5.3.0 binaries for Linux ARM64/AMD64 in a non-root container, with Vim as the default editor. No host Slumber or Vim installation is required.\n\n## Aspire version\n\nThese samples use the released Aspire **13.6.0** CLI and packages, matching the repository.\n\nInstall the matching CLI using the [root installation instructions](../../README.md#aspire-version).\nPackages restore from NuGet.org without staging feeds or sample-specific NuGet configuration.\n\nThe terminal APIs remain experimental in Aspire 13.6.0, but terminal CLI commands no longer require a feature flag.\nWhen updating, align the CLI and package versions across the repository and repeat the interactive walkthroughs.\n\nThese are local-development examples, not deployment samples. Only give trusted users dashboard access: the Redis and SQL REPLs grant database access. Data is disposable, and the HTTP APIs deliberately have no application authentication.\n", + "readmeRaw": "# Aspire terminals\n\nUse interactive tools alongside your application without installing them on the host, copying connection strings, or leaving the Aspire dashboard.\n\n| Sample | AppHost | API | Terminal features |\n| --- | --- | --- | --- |\n| [C# basics](./basics-csharp/) | File-based C# | ASP.NET Core | `WithTerminal()` and Redis `WithRepl()` |\n| [TypeScript basics](./basics-typescript/) | TypeScript | Express/TypeScript | `withTerminal()` and Redis `withRepl()` |\n| [Terminal automation](./automation-csharp/) | File-based C# | ASP.NET Core | Drive Slumber with `SendKeyAsync` / `WaitForTextAsync`, verify CRUD, handle repeat runs |\n| [Custom docked SQL REPL](./docked-repl-csharp/) | File-based C# | rqlite HTTP API | Custom container resource and `TerminalService` / `TerminalPlacement.Dock` |\n\nEach basic sample is standalone: its own API, Redis database, Dockerfile, and saved request collection. Both demonstrate the same round trip: create a note in a REST terminal UI, edit its underlying Redis value in a REPL, then read the changed value through the API.\n\nThe automation sample reuses the C# basic sample's API and Slumber image source, but launches its own resources and supplies a separate request collection. The custom REPL sample is independent and uses rqlite's official image and SQL shell. Each sample has its own AppHost and can run separately.\n\n## Two different terminal experiences\n\n**Slumber** runs as the main process of a container. `WithTerminal()` / `withTerminal()` makes that process interactive in the dashboard's console view, including keyboard input and terminal resizing.\n\n**Redis** continues running as a normal server. `WithRepl()` / `withRepl()` adds an on-demand **REPL** resource action that launches an authenticated `redis-cli` terminal. It does not attach interactive input to the Redis server process.\n\nThe REST client is [Slumber](https://github.com/LucasPickering/slumber), an MIT-licensed Rust application with YAML request collections and environment substitution. The basic samples package checksum-verified Slumber 5.3.0 binaries for Linux ARM64/AMD64 in a non-root container, with Vim as the default editor. No host Slumber or Vim installation is required.\n\n## Aspire version\n\nThese samples use the released Aspire **13.6.0** CLI and packages, matching the repository.\n\nInstall the matching CLI using the [root installation instructions](../../README.md#aspire-version).\nPackages restore from NuGet.org without staging feeds or sample-specific NuGet configuration.\n\nThe terminal APIs remain experimental in Aspire 13.6.0, but terminal CLI commands no longer require a feature flag.\nWhen updating, align the CLI and package versions across the repository and repeat the interactive walkthroughs.\n\nThese are local-development examples, not deployment samples. Only give trusted users dashboard access: the Redis and SQL REPLs grant database access. Data is disposable, and the HTTP APIs deliberately have no application authentication.\n", + "tags": [ + "csharp", + "dashboard", + "databases", + "redis", + "typescript" + ], + "thumbnail": null, + "appHost": "typescript", + "appHostPath": "basics-typescript/apphost.mts", + "appHostCode": "import { createBuilder } from './.aspire/modules/aspire.mjs';\n\nconst builder = await createBuilder();\n\n// No persistent volume: use disposable data. The authenticated Redis REPL is for trusted dashboard users only.\nconst redis = await builder.addRedis('redis').withRepl();\n\n// Local demo: anyone who can reach this unauthenticated HTTP API can read, replace, or delete demo notes.\nconst api = await builder.addNodeApp('api', './api', 'src/index.ts')\n .withEnvironment('NODE_OPTIONS', '--import tsx --import ./src/instrumentation.ts')\n .withHttpEndpoint({ env: 'PORT' })\n .withHttpHealthCheck({ path: '/health' })\n .withReference(redis)\n .withOtlpExporter()\n .waitFor(redis);\n\nawait builder.addDockerfile('slumber', './slumber')\n .withEnvironment('BASE_URL', api.getEndpoint('http'))\n .waitFor(api)\n .withTerminal();\n\nawait builder.build().run();" + }, { "name": "vite-csharp-postgres", "title": "ASP.NET Core Minimal API + PostgreSQL + Vite", diff --git a/src/frontend/src/utils/sample-tags.ts b/src/frontend/src/utils/sample-tags.ts index 5cdb75fde..d805a8331 100644 --- a/src/frontend/src/utils/sample-tags.ts +++ b/src/frontend/src/utils/sample-tags.ts @@ -11,6 +11,7 @@ export const TAG_LABELS: Record = { 'typescript': 'TypeScript', 'node': 'Node.js', 'go': 'Go', + 'java': 'Java', 'redis': 'Redis', 'postgresql': 'PostgreSQL', 'sql-server': 'SQL Server', diff --git a/src/frontend/src/utils/samples.ts b/src/frontend/src/utils/samples.ts index ff27849a7..84a9710c5 100644 --- a/src/frontend/src/utils/samples.ts +++ b/src/frontend/src/utils/samples.ts @@ -153,12 +153,41 @@ export function sampleDetailHref(base: string, name: string): string { return `${normalizedBase}/reference/samples/${sampleSlug(name)}/`; } +/** Matches a Markdown table separator row, with or without leading/trailing pipes (e.g. `| --- | --- |`, `--- | ---`, `:--|--:`). */ +const TABLE_SEPARATOR_ROW = /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/; + +/** Strips whole Markdown table blocks (header, separator, and body rows), tolerating missing leading/trailing pipes. */ +function stripMarkdownTables(text: string): string { + const lines = text.split('\n'); + const result: string[] = []; + + for (let i = 0; i < lines.length; i++) { + if (TABLE_SEPARATOR_ROW.test(lines[i])) { + // Drop the header row that precedes the separator, if one was just pushed. + if (result.length > 0 && result[result.length - 1].includes('|')) { + result.pop(); + } + // Skip the separator and any following body rows that still look like table rows. + i++; + while (i < lines.length && lines[i].includes('|')) { + i++; + } + i--; + continue; + } + + result.push(lines[i]); + } + + return result.join('\n'); +} + export function sampleDescriptionText(description: string | null): string | null { if (!description) { return null; } - const text = description + const text = stripMarkdownTables(description) .replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1') .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') .replace(/`([^`]+)`/g, '$1') diff --git a/src/frontend/tests/e2e/featured-samples.spec.ts b/src/frontend/tests/e2e/featured-samples.spec.ts index eb702ad3c..1da91a66c 100644 --- a/src/frontend/tests/e2e/featured-samples.spec.ts +++ b/src/frontend/tests/e2e/featured-samples.spec.ts @@ -25,7 +25,7 @@ test('featured samples filter real metadata and retain language when browsing al const languages = samples.getByRole('group', { name: 'Sample languages' }); const visible = samples.locator('[data-sample-languages]:visible h3'); await expect(samples).toHaveAttribute('data-ready', ''); - await expect(languages.getByRole('checkbox')).toHaveCount(5); + await expect(languages.getByRole('checkbox')).toHaveCount(6); await expect(languages.getByRole('checkbox', { checked: true })).toHaveCount(0); await expect(visible).toHaveCount(6); await expect(samples.getByRole('status')).toHaveText('6 featured samples: All languages'); diff --git a/src/frontend/tests/unit/custom-components.vitest.test.ts b/src/frontend/tests/unit/custom-components.vitest.test.ts index 90e38b6ec..4fec9f394 100644 --- a/src/frontend/tests/unit/custom-components.vitest.test.ts +++ b/src/frontend/tests/unit/custom-components.vitest.test.ts @@ -1450,6 +1450,34 @@ describe('custom Astro component render coverage', () => { expect(html).toContain('Zoom image: Aspire dashboard'); }); + it('drops markdown tables from plain-text sample descriptions', async () => { + const { sampleDescriptionText } = await import('@utils/samples'); + + expect( + sampleDescriptionText( + 'Intro paragraph.\n\n| Sample | AppHost |\n| --- | --- |\n| [C# basics](./basics/) | C# |\n\nClosing paragraph.' + ) + ).toBe('Intro paragraph.\n\nClosing paragraph.'); + }); + + it('drops markdown tables missing leading/trailing pipes', async () => { + const { sampleDescriptionText } = await import('@utils/samples'); + + expect( + sampleDescriptionText('Intro paragraph.\n\nSample | AppHost\n--- | ---\nC# basics | C#\n\nClosing paragraph.') + ).toBe('Intro paragraph.\n\nClosing paragraph.'); + }); + + it('drops markdown tables with alignment markers', async () => { + const { sampleDescriptionText } = await import('@utils/samples'); + + expect( + sampleDescriptionText( + 'Intro paragraph.\n\n| Sample | AppHost |\n|:---|---:|\n| C# basics | C# |\n\nClosing paragraph.' + ) + ).toBe('Intro paragraph.\n\nClosing paragraph.'); + }); + it('builds sample markdown payload with absolute image URLs and metadata preamble', async () => { const { appHostLabel, buildSampleMarkdown } = await import('@utils/samples');