# AspireC4
> Live LikeC4 architecture diagrams from the Aspire resource graph.
An Aspire extension that generates live LikeC4 architectural diagrams from the Aspire resource graph, with a Roslyn source generator for compile-time validation of tags, kinds, groups, and metadata.
- Repository: https://github.com/purview-dev/aspirec4
- Package: https://www.nuget.org/packages/AspireC4.Hosting
- Project page: https://purview.dev/projects/aspirec4/
- Documentation: https://purview.dev/docs/aspirec4/
- Full machine-readable content: https://purview.dev/projects/aspirec4/llms-full.txt
# Getting Started
This guide walks through installing AspireC4 and generating your first live architecture diagram.
## Prerequisites
- **.NET 8 or later**.
- **Aspire 13.5.3 or later**. Aspire AppHost projects must reference both the AppHost SDK and `Aspire.Hosting.AppHost`.
- **Docker** is used by default to run the LikeC4 sidecar container.
- Optional: a local Node.js CLI runtime (`npx`, `pnpm`, `yarn`, `bun`, or `deno`) if you call `.WithLocalCLI()`.
## Installation
Add AspireC4 to the AppHost project:
```bash
dotnet add package AspireC4.Hosting
dotnet add package Aspire.Hosting.AppHost
```
An Aspire 13.5 AppHost project should contain the equivalent of:
```xml
```
:::note
The generator, registry attributes, and enums are injected automatically by the `AspireC4.Hosting` package. Do not declare or reference a separate `AspireC4.SourceGenerators` package.
:::
## Quick start
Add the visualization to your AppHost:
```csharp
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAspireC4();
builder.Build().Run();
```
That is all it takes. On startup AspireC4:
1. Writes the generated model to `./likec4/gen/model.gen.c4` (relative to the AppHost working directory).
2. Starts the official `ghcr.io/likec4/likec4` container as a sidecar resource named `aspirec4`.
3. Refreshes the diagram whenever the Aspire application changes — for example when a resource transitions to `Running` or `Exited`.
Open the Aspire dashboard and select the `aspirec4` resource (or its **View LikeC4 Diagram** link) to see the live diagram.
## Customize the diagram
Pass a configuration callback to `AddAspireC4`:
```csharp
builder.AddAspireC4(options =>
options
.WithTitle("My distributed application")
.WithViewTitle("Architecture")
.WithViewDescription("Generated from the Aspire resource graph")
);
```
See [Configuration](configuration/) for the full options reference and [Customizing Resources](customizing-resources/) for per-resource details.
## Use a local CLI instead of Docker
When Docker is not available, or you prefer a local Node.js-based workflow:
```csharp
builder.AddAspireC4().WithLocalCLI();
```
The first runtime available on the system `PATH` is selected automatically (npx → pnpm → yarn → bun → deno). See [Local CLI Runtimes](local-cli/).
## Run the TypeScript sample
AspireC4 supports both C# and TypeScript AppHosts. See [TypeScript AppHosts](typescript-apphost/) for the TypeScript API and a runnable sample.
## Next pages
- [Configuration](configuration/)
- [Customizing Resources](customizing-resources/)
- [Generated Output](generated-output/)
- [Dashboard Integration](dashboard-integration/)
- [Source Generator Validation](source-generator/)
---
# Configuration
Configure the diagram through `AspireC4DiagramOptions`. Pass a callback to `AddAspireC4`:
```csharp
builder.AddAspireC4(options =>
options
.WithTitle("My App")
.WithAutoIcons(false)
.WithHideFromDashboard("Architecture")
);
```
:::tip
Options can also be bound from configuration. The section name is `AspireC4`, so `appsettings.json` entries such as `"AspireC4": { "ViewTitle": "Architecture" }` (or matching environment variables) are applied on top of the builder-time snapshot.
:::
## Options reference
| Property | Default | Description |
| --- | --- | --- |
| `GeneratedViewId` | `null` (`index`) | LikeC4 view ID emitted in the generated `.c4` file (e.g. `view index { ... }`). Change it if the ID conflicts with a hand-authored view. |
| `DefaultViewId` | `"index"` | View ID used in the `/view/{id}` URL the Aspire dashboard links to. `null`/empty links to the server root instead. |
| `Title` | `null` | Title shown in the LikeC4 application. |
| `ViewTitle` | `"Architecture"` | Title shown in the generated view. |
| `ViewDescription` | `null` | Optional view description (Markdown supported by recent LikeC4 versions). |
| `OutputDirectory` | `"./likec4/gen/"` | Directory where the generated `.c4` file is written. |
| `FileName` | `"model.gen"` | Generated file name without extension. |
| `DisableHMR` | `false` | Disable the Hot Module Replacement channel. |
| `HMRPort` | `null` (dynamic) | Fixed HMR port when the LikeC4 server supports configurable ports (v1.57+); ignored on older versions, which always use port `24678`. |
| `ContainerImageTag` | `null` (`latest`) | Pin the `ghcr.io/likec4/likec4` image tag (ignored with `.WithLocalCLI()`). |
| `CheckLatestImageVersion` | `true` | When using the `latest` tag, run a throwaway container at startup to resolve the actual version so version-gated features (e.g. HMR port mode) are configured correctly. |
| `AutoIconsEnabled` | `true` | Infer LikeC4 icons from resource type and name. |
| `HideFromDashboard` | `false` | Hide the sidecar from the dashboard and surface the diagram as a link/command on project resources. |
| `DashboardLinkDisplayName` | `"Architecture Diagram"` | Display name for the diagram link/command when hidden from the dashboard. |
| `RelationshipKindSyntax` | `Dot` | DSL syntax for typed relationships: `Dot` (`SOURCE .KIND TARGET`) or `Bracket` (`SOURCE -[KIND]-> TARGET`). |
| `FormatGeneratedFile` | `false` | Run `npx likec4 format --files ` after writing the generated file. Failures are ignored. |
| `ExternalProcessTimeoutSeconds` | `30` | Max seconds to wait for the external formatter process before killing it. |
| `UseDotIfAvailable` | `true` | Use GraphViz' `dot` executable for more accurate icon inference when on `PATH`. |
| `ElementKindSpecs` | `[]` | Custom element kind specifications emitted in the `specification {}` block (style, notation, technology). |
| `RelationshipKindSpecs` | `[]` | Custom relationship kind specifications emitted in the `specification {}` block (technology). |
| `AutoIncludeAspireMetadata` | `All` | Which Aspire metadata is auto-injected: `None`, `Metadata` (`aspire-name`, `aspire-type`), `Links` (endpoint URLs), or `All`. |
| `NormaliseMetadataBehaviour` | `Normalise` | How invalid characters in metadata keys are handled: `Normalise`, `NormaliseLowercase`, or `Throw`. |
| `AdditionalDSLFiles` | `[]` | Extra user-managed `.c4` files copied into the output directory and synced to the container volume. |
| `AdditionalDSLFolders` | `[]` | Directories scanned recursively for `.c4` files, added to `include.paths` in the generated config. |
| `ImageAliases` | `{}` | Image alias definitions (keys start with `@`) written to the `imageAliases` section of the generated config. |
| `GenerateConfigFile` | `true` | Generate a `likec4.config.json` in the output directory. |
| `IncludeAspireDashboardLinks` | `true` | Add links from each element to the Aspire dashboard console/structured-logs pages (requires `AutoIncludeAspireMetadata.Links`). |
| `IncludeAspireTokenInDashboardLinks` | `false` | **Security risk** — embed the Aspire browser token in dashboard links. See below. |
| `StateTagMap` | `{}` | Override the `aspire-run-state-*` tag applied for a given resource state; `null` suppresses the tag. |
| `IncludeDefaultStateStyles` | `true` | Emit default `style element.tag = #aspire-run-state-* {}` rules in the generated view. |
| `IncludeAspireC4InternalResource` | `false` | Include the internal AspireC4 server resource in the diagram (for debugging). |
| `ExcludedResourceTypes` | `{ParameterResource}` | Resource types excluded from the diagram (type and subclasses). |
| `IconResolvers` | `[]` | Custom icon resolvers evaluated before built-in icon inference. |
| `ConfigFileMetadata` | `{}` | Additional metadata included in the generated `likec4.config.json`. |
## Fluent methods
Every property has a corresponding fluent `With*` method, for example:
- `WithGeneratedViewId(string?)`, `WithDefaultViewId(string?)`
- `WithTitle(string?)`, `WithViewTitle(string)`, `WithViewDescription(string?)`
- `WithOutputDirectory(string)`, `WithFileName(string)`
- `WithHMRDisabled(bool = true)`
- `WithContainerImageTag(string?)`, `WithCheckLatestImageVersion(bool = true)`
- `WithAutoIcons(bool = true)`
- `WithHideFromDashboard(string displayName = "Architecture Diagram")`
- `WithRelationshipKindSyntax(LikeC4RelationshipKindSyntax)`
- `WithFormatGeneratedFile(bool = true)`
- `WithAutoIncludeAspireMetadata(AspireMetadataInclusion)`
- `WithNormaliseMetadataBehaviour(NormaliseMetadataBehaviour)`
- `WithoutConfigFileGeneration()`
- `WithAspireDashboardLinks(bool = true)`, `WithAspireTokenInDashboardLinks(bool = true)`
- `WithDefaultStateStyles(bool = true)`, `WithStateTag(string state, string? tag)`
- `WithUseDotIfAvailable(bool)`
- `WithIconResolver(Func)`
- `WithElementKindSpec(LikeC4ElementKindSpec)`, `WithRelationshipKindSpec(...)`
- `WithAdditionalDSLFile(string)`, `WithAdditionalDSLFolder(string)`, `WithImageAliasFolder(string, string)`
- `WithExcludedResourceType()`, `WithoutExcludedResourceType()`
- `WithIncludeAspireC4InternalResource(bool)`
See [Advanced Configuration](../advanced-configuration/) for the folder, alias, resolver, and spec extensions, and [Source Generator Validation](../source-generator/) for the `AspireC4Strict` MSBuild property.
## Common examples
### Hide the sidecar from the dashboard
```csharp
builder.AddAspireC4(options => options.WithHideFromDashboard());
```
### Disable hot reload
```csharp
builder.AddAspireC4(options => options.WithHMRDisabled());
```
### Pin the LikeC4 container version
```csharp
builder.AddAspireC4(options => options.WithContainerImageTag("1.57"));
```
### Security note — dashboard tokens in links
`IncludeAspireTokenInDashboardLinks` embeds the Aspire browser token in generated dashboard links. Only enable this if you understand the implications: the token grants the same access as a browser session and is written into the diagram file, which may be shared or stored in source control. Keep it disabled for normal development, and consider excluding the generated file from source control if you enable it.
---
# Customizing Resources
AspireC4 annotates resources in the Aspire app model, so each resource and relationship can be customized before the `.c4` file is generated.
## Element details — `WithLikeC4Details`
Customizes how a resource appears as a node in the diagram:
```csharp
builder.AddProject("api")
.WithLikeC4Details(details =>
details
.WithLabel("Public API")
.WithTechnology(".NET")
.WithDescription("The public HTTP API surface.")
.WithSummary("Handles client requests")
.WithIcon("tech:dotnet")
.WithKind("service")
.WithTag("backend")
.WithLink("https://example.com/docs", "API docs")
.WithMetadata("Owner", "Platform Team")
);
```
### Available node options
| Method | Purpose |
| --- | --- |
| `WithLabel(string)` | Display label on the element node. |
| `WithTechnology(string?)` | Technology string shown beneath the label (e.g. `.NET`, `Redis`). |
| `WithDescription(string?)` | Longer description rendered in the detail panel (Markdown). |
| `WithSummary(string?)` | One-line summary shown in tooltips/map view. |
| `WithIcon(string?)` | Icon identifier (e.g. `tech:dotnet`, `azure:storage`); `null` reverts to automatic inference. |
| `WithAutoIcon(bool?)` | Per-element override for auto-icon inference (`null` inherits the project setting). |
| `WithKind(string?)` | Element kind override (e.g. `service`); must be a valid LikeC4 identifier. |
| `WithTag(string)` | Adds a tag; a leading `#` is accepted and stripped. |
| `WithLink(string \| Uri, string? title)` | Adds a hyperlink (absolute, or relative to the `.c4` file). |
| `WithMetadata(string key, string value)` | Adds a metadata key/value pair. |
## Relationships — `WithLikeC4Reference`
Customizes how the relationship from a resource to a target appears in the diagram. There are two overloads:
```csharp
// Target is any resource builder.
builder.AddNodeApp("app", ...)
.WithLikeC4Reference(redis, opts => opts.WithLabel("Caches sessions").WithTechnology("Redis Protocol").WithKind("RESP"));
// Target is a resource that exposes a connection string — also calls Aspire's WithReference.
builder.AddProject("api")
.WithLikeC4Reference(db, opts => opts.WithLabel("Persists data"), connectionName: "postgres");
```
:::note
The connection-string overload (`IResourceWithConnectionString`) additionally wires up Aspire's `WithReference`, so the connection string is available to the consumer. Pass `skipAspireReference: true` to opt out.
:::
### Available relationship options
| Method | Purpose |
| --- | --- |
| `WithLabel(string)` | Short label on the relationship arrow. |
| `WithTechnology(string?)` | Technology/protocol (e.g. `HTTP/2`, `gRPC`, `AMQP`). |
| `WithDescription(string?)` | Longer relationship description. |
| `WithKind(string?)` | Typed relationship kind (e.g. `async`, `sync`), declared in the `specification` block and emitted with the configured `RelationshipKindSyntax`. |
| `WithTag(string)` | Adds a tag to the relationship. |
| `WithLink(string \| Uri, string? title)` | Adds a hyperlink to the relationship. |
| `WithMetadata(string key, string value)` | Adds a metadata key/value pair. |
| `WithNavigateTo(string viewId)` | LikeC4 dynamic view to navigate to when the relationship is clicked. |
Attach multiple annotations to the same source — one per target — to customize each relationship independently.
## Groups — `WithLikeC4Group`
Assigns a resource to a named group. Resources sharing a group are emitted inside a `group 'label' { include ... }` block in the generated view:
```csharp
builder.AddRedis("cache").WithLikeC4Group("Platform");
```
## Excluding resources — `ExcludeFromLikeC4`
```csharp
builder.AddRedis("cache").ExcludeFromLikeC4();
```
The AspireC4 sidecar itself is always excluded from the diagram. Set `WithIncludeAspireC4InternalResource(true)` in the options callback if you want to inspect it.
### Type-based exclusion
By default `ParameterResource` (passwords/secrets added via `AddParameter()`/`WithParameter()`) is excluded. Add or remove types globally:
```csharp
builder.AddAspireC4(options => options
.WithExcludedResourceType()
.WithoutExcludedResourceType());
```
A resource is excluded when its runtime type is the same as, or a subclass of, any type in the set.
## Aspire metadata injection
When `AutoIncludeAspireMetadata` is `All` (the default), each element automatically receives `aspire-name`/`aspire-type` metadata and links to its allocated HTTP/HTTPS endpoints. Endpoint URLs come from resource snapshots so they use the correct public ports.
## Next pages
- [Generated Output](../generated-output/)
- [Advanced Configuration](../advanced-configuration/)
- [Source Generator Validation](../source-generator/)
---
# TypeScript AppHosts
AspireC4 supports both C# and TypeScript Aspire AppHosts. In a TypeScript AppHost, the Aspire integration exports the same diagram configuration, resource metadata, grouping, and relationship features through camel-cased asynchronous APIs. The C# registry source generator is not applied to TypeScript; TypeScript applications configure tags, kinds, groups, and metadata through the generated fluent API.
## Generated modules
The Aspire CLI generates the TypeScript API surface under `.aspire/modules/`. Import `createBuilder` from the generated Aspire module, then add AspireC4 to the builder:
```typescript
import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();
await builder
.addAspireC4({
configure: async (options) => {
options
.withTitle("My distributed application")
.withViewTitle("Architecture")
.withViewDescription("Generated from the Aspire resource graph");
},
})
.configureServer(async (resource) => {
resource.withLikeC4Details({
configure: async (options) => {
options.withLabel("Architecture diagram");
},
});
});
const api = await builder.addNodeApp("api", "../api", "index.ts");
await api.withLikeC4Details({
configure: async (options) => {
options
.withLabel("API")
.withTechnology("Node.js")
.withTag("backend");
},
});
const app = await builder.build();
await app.run();
```
## aspire.config.json
Declare the integration and its Aspire dependencies in `aspire.config.json`:
```json
{
"appHost": {
"path": "apphost.mts",
"language": "typescript/nodejs"
},
"sdk": {
"version": "13.5.3"
},
"packages": {
"Aspire.Hosting.JavaScript": "13.5.3",
"AspireC4.Hosting": "13.5.3"
}
}
```
Use the AspireC4 package version appropriate for the application. The repository sample points `AspireC4.Hosting` at `../../src/src/AspireC4/AspireC4.csproj` so it exercises the local source instead of a published package.
## Run the TypeScript sample
The sample at `samples/typescript-app-host` demonstrates:
- AspireC4 configuration from `apphost.mts`.
- Azure Redis and PostgreSQL resources running as local containers.
- Redis Commander and PgWeb dashboard resources.
- A TypeScript Node.js service with Redis and PostgreSQL references.
- LikeC4 labels, descriptions, links, icons, metadata, tags, groups, and relationships.
- Additional LikeC4 DSL and image folders from the repository `assets` directory.
Prerequisites are Docker, the Aspire CLI, and Bun. From the repository root:
```bash
cd samples/typescript-app-host
bun install
aspire restore
aspire start
```
`aspire restore` restores the integrations declared in `aspire.config.json` and regenerates `.aspire/modules/`. Once `aspire start` completes, open the Aspire dashboard URL printed by the CLI and select the LikeC4 resource or its architecture-diagram link. The sample also exposes the `node-app` `/health`, `/ping/redis`, and `/ping/postgres` endpoints through Aspire-assigned URLs.
For an interactive foreground session, the sample's Bun script is equivalent to `aspire run`:
```bash
bun run dev
```
Stop a background session with:
```bash
aspire stop
```
## Generated modules notes
Do not edit files under `.aspire/modules/`; Aspire owns and regenerates them. If the folder is missing or stale after a pull, clean, or branch switch, run:
```bash
aspire restore
```
When adding another Aspire integration, use `aspire add ` so Aspire updates `aspire.config.json` and regenerates the TypeScript API. Inspect `.aspire/modules/aspire.mts` to see the APIs currently available to `apphost.mts`.
## Next pages
- [Configuration](../configuration/)
- [Customizing Resources](../customizing-resources/)
---
# Generated Output
AspireC4 writes a LikeC4 project into the output directory (`./likec4/gen/` by default) and serves it with the LikeC4 server.
## Files
| File | Purpose |
| --- | --- |
| `model.gen.c4` | The generated model — see below. |
| `likec4.config.json` | LikeC4 project configuration (`name`, `title`, `include.paths`, `imageAliases`, `metadata`). Generated when `GenerateConfigFile` is `true`. |
| Additional `.c4` files | Any files registered through `WithAdditionalDSLFile` are copied here; LikeC4 discovers all `.c4` files in the project directory automatically. |
## The generated `.c4` file
The file starts with an auto-generated header and is split into `specification {}`, `model {}`, and `views {}` blocks:
- **`specification {}`** declares every element kind, relationship kind, and tag used in the model, plus any specs from `ElementKindSpecs`/`RelationshipKindSpecs`. When `IncludeDefaultStateStyles` is enabled, all known state tags are declared up-front so the block is stable regardless of the current resource states.
- **`model {}`** emits each element as ` = 'label' { ... }` with tags, technology, summary, description, icon, links, and metadata, plus relationships using the configured `RelationshipKindSyntax` (`SOURCE .KIND TARGET` or `SOURCE -[KIND]-> TARGET`).
- **`views {}`** emits the `index` view (or the `GeneratedViewId`) with its title/description, `group 'label' { include ... }` blocks, `include *`, and `style element.tag = #aspire-run-state-* {}` rules for the built-in state styles.
:::caution
The file is auto-generated. Do not edit it manually — changes are overwritten on the next regeneration (at startup or on a resource state change).
:::
## Regeneration behavior
- The file is written **before** the application starts.
- At runtime, resource state changes trigger a **debounced** regeneration (300 ms), so the diagram reflects the current state of each resource.
- Writes are skipped when the generated content is unchanged, avoiding needless file churn and git noise.
- In publish mode (`aspire publish`), the file is generated once and the server is not started.
## Formatting
When `FormatGeneratedFile` is `true`, AspireC4 runs `npx likec4 format --files ` against the generated file immediately after writing it. The formatter modifies the file in place so the on-disk copy is human-readable; the formatted content is also what is synced to the container workspace. Failures are silently ignored. `ExternalProcessTimeoutSeconds` caps how long this can block startup (default 30 s).
## Config file
`likec4.config.json` uses the LikeC4 `$schema` and includes:
- `name` — the LikeC4 project name (unique within the workspace).
- `title` — when `Title` is set.
- `include.paths` — for each `AdditionalDSLFolders` entry (LikeC4 recursively scans the folder for `.c4` files).
- `imageAliases` — for each `ImageAliases` entry (keys start with `@`).
- `metadata` — for each `ConfigFileMetadata` entry.
Set `WithoutConfigFileGeneration()` (or `GenerateConfigFile = false`) to manage `likec4.config.json` manually, for example when the output directory is already part of a hand-curated LikeC4 project.
## Icons
- **Auto icons** (`AutoIconsEnabled`, default `true`): icons are inferred from the resource type and name using the bundled icon manifest.
- **Custom resolvers** (`IconResolvers`): evaluated in registration order before built-in inference; the first non-`null` result wins. Each resolver receives an `IconResolverContext` exposing the visible `Resource` and the `HiddenOriginal` Azure resource (when a local surrogate was created via `RunAsContainer()`).
- **GraphViz `dot`** (`UseDotIfAvailable`, default `true`): when `dot` is on `PATH`, LikeC4 uses it for more accurate inference (e.g. database icons) and can infer icons from container/component relationships.
## Version detection
When the container image tag resolves to `latest` (default), `CheckLatestImageVersion` (default `true`) runs a throwaway container at startup to call `likec4 --version` and resolve the actual version. The resolved version configures version-gated features (such as configurable HMR ports) and is surfaced as a **LikeC4 Version** property on the dashboard resource. Set it to `false` for faster startup if you pin an explicit tag or accept possible misconfiguration.
## Live state reflection
Each element is tagged `aspire-run-state-` based on the live Aspire resource state, and the default view styles color it accordingly:
| State tag | Default style |
| --- | --- |
| `aspire-run-state-starting` | sky |
| `aspire-run-state-waiting` | sky |
| `aspire-run-state-running` | green |
| `aspire-run-state-stopping` | slate, 60% opacity |
| `aspire-run-state-exited` | muted, 30% opacity |
| `aspire-run-state-finished` | muted, 30% opacity |
| `aspire-run-state-runtimeunhealthy` | amber |
| `aspire-run-state-failedtostart` | red |
Override per-state tags with `WithStateTag(state, tag)` (pass `null` to suppress), and disable the built-in style rules with `WithDefaultStateStyles(false)` if you prefer to style state tags in your own DSL.
## Next pages
- [Dashboard Integration](../dashboard-integration/)
- [Configuration](../configuration/)
- [Advanced Configuration](../advanced-configuration/)
---
# Dashboard Integration
AspireC4 integrates with the Aspire dashboard so the diagram is easy to reach and links back to resource telemetry.
## The sidecar resource
`AddAspireC4()` creates an `aspirec4` resource (name configurable via the `name` parameter) of container type. Its state, URLs, and properties are forwarded from the inner LikeC4 server resource, which is always kept hidden. The dashboard shows:
- A **View LikeC4 Diagram** URL on the resource, opening `/view/index` (or the `DefaultViewId`).
- The **LikeC4 Version** property, resolved from the pinned tag or the startup version check.
- The server's console logs, relayed onto the outer resource's Console tab.
## Hiding the sidecar
When `HideFromDashboard` is set (via `WithHideFromDashboard(displayName)`), the `aspirec4` resource is also hidden and the diagram is instead surfaced on every **project** resource as:
- A **link** (`architecture-diagram`) injected into the project resource's URLs once the server is running.
- A **command** (`likec4-architecture-diagram`) that opens the diagram. The command is disabled until the LikeC4 server is `Running`.
`DashboardLinkDisplayName` controls the label shown (default `Architecture Diagram`).
## Dashboard links on diagram elements
`IncludeAspireDashboardLinks` (default `true`) adds links from each LikeC4 element back to the Aspire dashboard's console logs and structured logs pages for that resource. This requires `AutoIncludeAspireMetadata` to include `Links` (the default `All` includes it). The links are constructed at runtime once the dashboard URL is discovered.
### Security: browser tokens in links
`IncludeAspireTokenInDashboardLinks` (default `false`) embeds the Aspire browser token in those dashboard links so the browser is authenticated when the link is clicked (`/login?t=…&returnUrl=…`).
:::caution
The token grants the same access as a browser session and is written into the generated diagram file, which may be shared or stored in an insecure location/source control. Only enable it if you understand the implications. Consider excluding the generated `.c4` file from source control and sharing it only over secure channels. For normal development, keep it disabled and navigate to the dashboard manually.
:::
## View selection
The dashboard link opens `/view/{DefaultViewId}` (`index` by default — the ID of the auto-generated view). Set `DefaultViewId` to `null`/empty to link to the server root instead, which is useful when you prefer the LikeC4 server's own landing page. If you change `GeneratedViewId` in the generated file, set `DefaultViewId` to the same value so the dashboard link still opens the correct diagram.
## Hot Module Replacement
HMR keeps the diagram up to date in the browser as the file regenerates. The HMR endpoint is exposed in the dashboard (details view) and, on Windows/Docker Desktop, `CHOKIDAR_USEPOLLING`/`CHOKIDAR_INTERVAL` environment variables are set on the container so file changes are detected via polling. Disable HMR with `WithHMRDisabled()`. See [Configuration](../configuration/) for `HMRPort` behavior across LikeC4 versions.
## Next pages
- [Generated Output](../generated-output/)
- [Configuration](../configuration/)
---
# Local CLI Runtimes
By default the LikeC4 server runs as the `ghcr.io/likec4/likec4` Docker container. When Docker is not available — or you prefer a local Node.js-based workflow — switch to a local CLI with `.WithLocalCLI()`.
```csharp
builder.AddAspireC4().WithLocalCLI();
```
The selected runtime must be installed and accessible on the system `PATH`.
## Runtime selection
```csharp
builder.AddAspireC4().WithLocalCLI(LocalCLIRuntime.Bun);
```
`LocalCLIRuntime` supports:
| Runtime | Command |
| --- | --- |
| `Auto` | Detects the first available runtime in order: npx → pnpm → yarn → bun → deno. |
| `Npx` | `npx likec4 serve --port ` |
| `Pnpm` | `pnpm dlx --ignore-workspace likec4 serve --port ` |
| `Yarn` | `yarn dlx --package likec4 --package react --package react-dom likec4 serve --port ` |
| `Bun` | `bunx --bun likec4 serve --port ` |
| `Deno` | `deno run --allow-all --node-modules-dir=none npm:likec4 serve --port ` |
:::note
`Auto` throws a `DistributedApplicationException` when no supported package manager is found. Install one of Node.js (`npx`), pnpm, yarn, bun, or Deno, or remove `WithLocalCLI()` to use the Docker container.
:::
## Behavior notes
- The output directory is passed as an absolute path; the server process uses the system temp directory as its working directory so package managers do not walk up and treat the AppHost's parent `package.json` as a workspace root.
- `ContainerImageTag` is ignored — it only applies to the Docker container.
- Yarn's `dlx` does not install optional peer dependencies (`react`, `react-dom`) by default, so AspireC4 passes them explicitly as `--package` arguments.
- HMR is enabled with `--hmr-port` when not disabled; `DisableHMR` still applies.
## ConfigureServer
Use `ConfigureServer` to apply annotations directly to the inner server resource (e.g. `WithLikeC4Details`):
```csharp
builder.AddAspireC4()
.ConfigureServer(server => server.WithLikeC4Details(options => options.WithLabel("Architecture diagram")));
```
:::tip
Call `ConfigureServer` **after** `.WithLocalCLI()` if you want to configure the CLI resource. Calling it first configures the Docker container that is subsequently replaced.
:::
## Next pages
- [Getting Started](../)
- [Configuration](../configuration/)
---
# Source Generator Validation
AspireC4 includes an incremental Roslyn source generator (built on `Purview.SourceGeneratorFramework`) that validates constant values passed to:
- `.WithTag()`
- `.WithKind()`
- `.WithLikeC4Group()`
- `.WithMetadata()`
The generator injects the registry attributes and enums automatically — the `AspireC4.Hosting` package references it, so no separate generator package is needed. TypeScript AppHosts are not validated this way; they use the generated fluent API.
## Registry class
Add one `[LikeC4Registry]` class to the AppHost assembly. Its accessibility and nesting do not matter. Values are declared as `const string` fields in conventionally named nested classes:
```csharp
using Aspire.Hosting.AspireC4;
[LikeC4Registry]
internal static class ArchitectureRegistry
{
public static class Tags
{
public const string External = "external";
public const string LocalDevelopment = "local-dev";
}
public static class ElementKinds
{
public const string Service = "service";
}
public static class RelationshipKinds
{
public const string Async = "async";
}
public static class Groups
{
public const string Platform = "Platform";
}
public static class MetadataKeys
{
public const string AzureSku = "Azure_SKU";
}
}
```
Supported nested-class names are:
| Registry type | Accepted class names |
| --- | --- |
| Tag | `Tag`, `Tags` |
| Element kind | `ElementKind`, `ElementKinds`, `Element`, `Elements` |
| Relationship kind | `RelationshipKind`, `RelationshipKinds`, `Relationship`, `Relationships` |
| Group | `Group`, `Groups` |
| Metadata key | `MetadataKey`, `MetadataKeys` |
Use the constants at call sites to make refactoring safe:
```csharp
builder.AddProject("api")
.WithLikeC4Details(details =>
details
.WithTag(ArchitectureRegistry.Tags.External)
.WithKind(ArchitectureRegistry.ElementKinds.Service)
.WithMetadata(ArchitectureRegistry.MetadataKeys.AzureSku, "Standard_LRS")
)
.WithLikeC4Group(ArchitectureRegistry.Groups.Platform);
```
## Individual registry fields
For a flat registry, annotate each constant with `[KnownType]`:
```csharp
[LikeC4Registry]
internal static class ArchitectureRegistry
{
[KnownType(LikeC4RegistryType.Tag)]
public const string External = "external";
[KnownType(LikeC4RegistryType.Group, Strict = LikeC4Severity.Warning)]
public const string Platform = "Platform";
}
```
Do not declare the same registry type using both a named nested class and `[KnownType]` fields. Doing so produces `ASPIREC4005`.
## Validation severity
Without an explicit strict setting, a registry class enables suggestion-level validation. Severity can be configured at three levels, from broadest to most specific:
1. The `AspireC4Strict` MSBuild property.
2. `[LikeC4Registry(Strict = ...)]` for the registry.
3. `[Severity(...)]` on a named nested class, or `KnownType.Strict` on an individual field.
```csharp
[LikeC4Registry(Strict = LikeC4Severity.Warning)]
internal static class ArchitectureRegistry
{
[Severity(LikeC4Severity.Error)]
public static class Tags
{
public const string External = "external";
}
[KnownType(LikeC4RegistryType.Group, Strict = LikeC4Severity.Off)]
public const string UnvalidatedGroup = "Temporary";
}
```
`LikeC4Severity` supports `Inherit`, `Off`, `Suggestion`, `Warning`, and `Error`.
## The `AspireC4Strict` MSBuild property
The project-wide setting can be placed in the AppHost project or `Directory.Build.props`:
```xml
warning
```
Accepted values:
| Value | Behavior |
| --- | --- |
| `off` or an unset/unknown value | Disables DSL-file strict validation |
| `suggestion` | Reports undeclared DSL values as suggestions |
| `warning` | Reports undeclared DSL values as warnings |
| `error`, `true`, `yes`, or `all` | Reports undeclared DSL values as errors |
| `allincludingmetadata` | Error-level validation including metadata keys |
Metadata-key comparison is case-insensitive and normalizes punctuation and whitespace to underscores. For example, `Azure SKU`, `azure sku`, and `Azure_SKU` identify the same key.
To disable all AspireC4 source-generator diagnostics while retaining the injected registry types:
```xml
true
```
## Validate LikeC4 files
Add `.c4` or `.likec4` specification files as compiler additional files, then set `AspireC4Strict`:
```xml
warning
```
Tags, element kinds, and relationship kinds in `specification` blocks are merged with registry-class definitions.
## Diagnostics
| ID | Meaning |
| --- | --- |
| `ASPIREC4001` | A tag passed to `.WithTag()` is undeclared |
| `ASPIREC4002` | An element or relationship kind passed to `.WithKind()` is undeclared |
| `ASPIREC4003` | More than one class in the assembly has `[LikeC4Registry]` |
| `ASPIREC4004` | A group passed to `.WithLikeC4Group()` is undeclared |
| `ASPIREC4005` | A registry type uses both a nested class and `[KnownType]` fields |
| `ASPIREC4006` | A metadata key passed to `.WithMetadata()` is undeclared |
| `ASPIREC4007` | A nested class in the registry uses a name that is not a recognized registry type |
## Injected types
The generator emits `LikeC4RegistryAttribute.g.cs`, `KnownTypeAttribute.g.cs`, `SeverityAttribute.g.cs`, `LikeC4RegistryType.g.cs`, and `LikeC4Severity.g.cs`. Do not define these types manually in your own code.
## Next pages
- [Migration Guide](../migration/)
- [Customizing Resources](../customizing-resources/)
---
# Advanced Configuration
Configuration topics beyond the core options in [Configuration](../configuration/).
## Additional DSL files
Copy extra user-managed `.c4` files into the output directory (and sync them to the Docker volume in container mode). LikeC4 discovers all `.c4` files in the project directory, so they are included in the diagram without further configuration:
```csharp
builder.AddAspireC4(options => options.WithAdditionalDSLFile("likec4/views.c4"));
```
Relative paths are resolved from the current working directory; the file must exist.
## Additional DSL folders
Register directories whose `.c4` files are included via the `include.paths` field of the generated `likec4.config.json`. LikeC4 recursively scans each directory:
```csharp
builder.AddAspireC4(options => options.WithAdditionalDSLFolder("../../assets/likec4-extensions"));
```
Each entry must be an absolute path to an existing directory (the method validates this at call time). In Docker container mode, each folder is bind-mounted read-only into the container at a deterministic path under `/data/ext/`.
## Image aliases
Register a shorthand key (e.g. `@icons`) mapping to a directory of image files. Aliases are written to the `imageAliases` section of the generated `likec4.config.json`:
```csharp
builder.AddAspireC4(options => options.WithImageAliasFolder("@", "C:/images"));
```
The key must start with `@` and the directory must exist at call time. In Docker container mode, each image directory is bind-mounted read-only at a deterministic path under `/data/img/`. Use aliases in DSL files, for example `icon @/likec4/likec4-logo.svg`.
## Custom icon resolvers
Resolvers are evaluated before built-in auto-icon inference, in registration order; the first non-`null` result wins. Each receives an `IconResolverContext` with:
- `Resource` — the visible Aspire resource being rendered.
- `HiddenOriginal` — the hidden Azure resource when a local surrogate was created via `RunAsContainer()` (useful for richer type-based icon selection).
```csharp
builder.AddAspireC4(options =>
options.WithIconResolver(ctx =>
ctx.Resource is MyCustomResource ? "tech:dotnet" : null));
```
## Element kind specifications
Declare custom element kinds with optional style, notation, and technology in the `specification {}` block. These are additive — kinds listed here but not present in the model are still declared:
```csharp
builder.AddAspireC4(options =>
options.WithElementKindSpec(
new LikeC4ElementKindSpec("service")
.WithTechnology("HTTP")
.WithNotation("API service")
.WithStyle(new LikeC4ElementKindStyle(
Shape: "queue",
Color: "blue",
Icon: "tech:dotnet",
Border: "dashed",
Opacity: 80))
));
```
`LikeC4ElementKindStyle` accepts `Shape`, `Color`, `Icon`, `Border`, and `Opacity` tokens.
## Relationship kind specifications
Declare custom relationship kinds with an optional default technology:
```csharp
builder.AddAspireC4(options => options.WithRelationshipKindSpec("async", "AMQP"));
```
When a relationship kind in the model matches an entry with a technology, the full body is emitted rather than a bare `relationship KIND` line.
## Relationship kind syntax
`RelationshipKindSyntax` controls how typed relationships are emitted:
- `Dot` (default): `SOURCE .KIND TARGET`
- `Bracket`: `SOURCE -[KIND]-> TARGET`
```csharp
builder.AddAspireC4(options => options.WithRelationshipKindSyntax(LikeC4RelationshipKindSyntax.Bracket));
```
## Metadata key normalization
`NormaliseMetadataBehaviour` controls how invalid characters in metadata keys are handled. Valid LikeC4 metadata key characters are letters, digits, hyphens, and underscores:
- `Normalise` (default): replace any other character with `_` (`"Azure SKU"` → `"Azure_SKU"`).
- `NormaliseLowercase`: same, but also lowercases (`"Azure SKU"` → `"azure_sku"`).
- `Throw`: throw an `ArgumentException` for invalid keys.
## Aspire metadata inclusion
`AutoIncludeAspireMetadata` (`WithAutoIncludeAspireMetadata`) controls which Aspire runtime metadata is injected into generated elements:
- `None` — no automatic metadata.
- `Metadata` — `aspire-name` and `aspire-type` entries.
- `Links` — allocated HTTP/HTTPS endpoint URLs as element links.
- `All` (default) — both.
## Config file generation
`GenerateConfigFile` (default `true`) produces `likec4.config.json` in the output directory with the project title, `include.paths`, and `imageAliases`. Disable with `WithoutConfigFileGeneration()` to manage the config manually. When generation is enabled, `ConfigFileMetadata` adds extra key/value pairs to the config's `metadata` section.
## Type-based exclusions
`ExcludedResourceTypes` controls which resource types are omitted from the diagram (type and subclasses). The default excludes `ParameterResource`. See [Customizing Resources](../customizing-resources/).
## Formatting timeouts
`ExternalProcessTimeoutSeconds` (default 30) caps how long the optional `npx likec4 format` step can block startup. See [Generated Output](../generated-output/).
## Including the internal resource
`WithIncludeAspireC4InternalResource(true)` includes the AspireC4 server sidecar in the diagram, which exists purely for debugging/monitoring. It is excluded by default.
## Next pages
- [Configuration](../configuration/)
- [Generated Output](../generated-output/)
---
# Migration Guide
This guide covers breaking changes introduced by the migration of the source generator to the current `Purview.SourceGeneratorFramework` incremental APIs. Existing applications should review the following when upgrading:
- **Aspire AppHost dependency is explicit.** Aspire 13.5 AppHosts must reference `Aspire.Hosting.AppHost`; relying on the AppHost SDK alone produces `ASPIRE002`.
- **Only one registry class is supported per assembly.** Merge multiple `[LikeC4Registry]` classes into one class.
- **Registry declaration styles cannot be mixed per type.** For example, choose either a `Tags` nested class or `[KnownType(LikeC4RegistryType.Tag)]` fields. Mixing both now produces `ASPIREC4005`.
- **Strict settings are severity-based.** Replace older boolean-only assumptions with `suggestion`, `warning`, `error`, `all`, or `allincludingmetadata`. `true` remains accepted as an alias for error-level validation.
- **Metadata validation is opt-in at the global level.** Use `allincludingmetadata`, or apply an explicit metadata severity through `[Severity]`/`[KnownType]`. Plain `all` does not validate metadata keys.
- **Generated source files are split by type.** The generator now emits `LikeC4RegistryAttribute.g.cs`, `KnownTypeAttribute.g.cs`, `SeverityAttribute.g.cs`, `LikeC4RegistryType.g.cs`, and `LikeC4Severity.g.cs` instead of a combined `LikeC4RegistryAttributes.g.cs`. This affects generator snapshot tests and tooling that inspected hint names; normal application source code is unaffected.
- **Do not define generated registry types manually.** Remove compatibility copies of `LikeC4RegistryAttribute`, `KnownTypeAttribute`, `SeverityAttribute`, `LikeC4RegistryType`, or `LikeC4Severity` to avoid duplicate-type errors.
- **Generator packaging is automatic.** Consumers should reference only `AspireC4.Hosting`; remove direct references to `AspireC4.SourceGenerators` or `Purview.SourceGeneratorFramework` that were added solely to make the AspireC4 generator run.
- **TypeScript APIs are generated by Aspire.** TypeScript AppHosts import from `.aspire/modules/aspire.mjs`; generated files must not be copied between projects or edited manually. Run `aspire restore` after upgrading AspireC4 so the exported API matches the installed integration version.
## See also
- [Source Generator Validation](../source-generator/)
- [TypeScript AppHosts](../typescript-apphost/)
---
# Contributing
Thank you for contributing! This guide covers the tools, conventions, and processes used in the repository.
## Prerequisites
| Tool | Purpose |
|---|---|
| [.NET SDK](https://dotnet.microsoft.com/download) (version from `global.json`) | Build and test |
| [Bun](https://bun.sh/) (version from `package.json` → `packageManager`) | Repository scripts and commit hooks |
| [just](https://just.systems/man/en/packages.html) | Task runner |
| [Lefthook](https://github.com/evilmartians/lefthook) | Git hooks |
| [Docker](https://www.docker.com/) | Integration tests, local diagram viewer |
After cloning, install all dependencies:
```sh
just init # JS dependencies (Bun), NuGet packages, local tools, and Git hooks
```
## Getting started
```sh
just build # Build the solution (Debug by default; pass Release to build Release)
just test # Run all tests (unit + integration)
just lint-check # Check formatting
```
## Branding
Two distinct brands exist in this repository. Use them consistently:
| Brand | What it is | Examples |
|---|---|---|
| **AspireC4** | This library / plugin | `AspireC4.Hosting` NuGet package, `AspireC4DiagramOptions`, `IAspireC4Builder`, `AddAspireC4()` |
| **LikeC4** | The third-party visualisation tool this library integrates | `ghcr.io/likec4/likec4` container, `LikeC4Model`, `LikeC4DslGenerator`, `.c4` file format |
**Rules:**
- Public extension methods and user-facing types use the `AspireC4` prefix.
- Types that directly represent LikeC4 DSL concepts keep the `LikeC4` prefix.
- Never use `LikeC4` to refer to this library, and never use `AspireC4` to refer to the third-party tool.
## Just — task runner
`just` is the single entry point for all development tasks. Run `just` with no arguments to list all recipes.
### .NET
| Recipe | Description |
|---|---|
| `just restore` | Restore NuGet packages and local .NET tools |
| `just build [Debug\|Release]` | Build the solution (default: `Debug`) |
| `just clean` | Clean build outputs |
| `just test` | **Run all tests** (unit + integration) |
| `just test-unit` | Run unit tests only |
| `just test-integration` | Run integration tests only |
| `just lint-check` | Check formatting with CSharpier |
| `just lint-fix` | Auto-fix formatting with CSharpier |
| `just pack` | Build and pack NuGet artifacts into `artifacts/nuget/` |
### Container runtime tests (local only)
| Recipe | Description |
|---|---|
| `just test-e2e-docker` | Integration tests against the host Docker daemon |
| `just test-e2e` | Docker + all local CLI runtimes (npm, pnpm, yarn, bun, deno) |
| `just test-e2e-cli` | All local CLI runtimes only (npm, pnpm, yarn, bun, deno) |
| `just test-e2e-npm` | Single CLI runtime (also `-pnpm`, `-yarn`, `-bun`, `-deno`) |
### Diagrams
| Recipe | Description |
|---|---|
| `just diagrams` | Open the live LikeC4 diagram viewer for this repository |
### Filtering a single test
```sh
dotnet test src/tests/AspireC4.UnitTests/AspireC4.UnitTests.csproj \
-- --filter "FullyQualifiedName~MyTestMethod"
dotnet test src/tests/AspireC4.IntegrationTests/AspireC4.IntegrationTests.csproj \
-- --filter "FullyQualifiedName~MyTestMethod"
```
## Code style — CSharpier
All C# code is formatted with [CSharpier](https://csharpier.com/), pinned to the version in `.config/dotnet-tools.json`. It is installed as a local .NET tool via `just restore`.
```sh
just lint-check # Report formatting violations
just lint-fix # Auto-fix formatting violations
```
CSharpier runs automatically on every `git commit` via Lefthook. **Do not pin a specific CSharpier version in `.csproj` files** — the version lives exclusively in `.config/dotnet-tools.json`.
## Git hooks — Lefthook
[Lefthook](https://github.com/evilmartians/lefthook) manages two hooks, configured in `.config/lefthook.yml`:
| Hook | What it does |
|---|---|
| `pre-commit` | Runs `just lint-check` (CSharpier over the repo root). Rejects the commit if any file is mis-formatted. |
| `commit-msg` | Runs `commitlint` to enforce conventional commit format. |
Lefthook installs when you run `just init`. To verify it is active:
```sh
bunx lefthook install
```
To bypass a hook temporarily (e.g. a work-in-progress commit you will amend):
```sh
git commit --no-verify -m "wip: ..."
```
Do not bypass hooks on commits intended for `main`.
## Commit messages
Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/) and are enforced by `commitlint` (via Lefthook).
**Format:**
```
():
```
**Allowed types:** `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `ci`, `build`, `chore`, `style`, `revert`.
**Rules:**
- Subject must be lower-case, no trailing period, max 100 characters.
- Body lines max 100 characters.
- Breaking changes: append `!` after the type/scope, or add `BREAKING CHANGE:` in the footer.
```sh
# Good
feat(core): add image alias resolution for azure resources
fix: correct hmr port fallback on windows
chore(deps): bump aspire.hosting to 9.2.0
# Bad — upper-case subject, trailing period
Fix: Correct HMR port fallback on Windows.
```
## Tests
All tests in this repository **must use [TUnit](https://github.com/thomhurst/TUnit)**. Do not use xUnit, NUnit, or MSTest. Test projects declare just ``; the SDK (Purview.BuildSdk) wires TUnit, TUnit.Mocks, and Bogus into them automatically.
```csharp
[Test]
public async Task Something_Should_DoX()
{
// Arrange
// Act
// Assert
await Assert.That(result).IsEqualTo(expected);
}
```
Mocking uses TUnit.Mocks (`.Returns(...)` API); NSubstitute is not referenced.
### Project structure
| Project | What to test here |
|---|---|
| `AspireC4.UnitTests` | `LikeC4ModelBuilder`, `LikeC4DslGenerator`, annotations, options — no Docker required |
| `AspireC4.IntegrationTests` | Full Aspire lifecycle: container startup, file generation, endpoint availability |
Integration tests require Docker to be running. They pull `ghcr.io/likec4/likec4` on first run.
## See also
- [Release Flow](../release-flow/)
---
# Release Flow
The version in `package.json` is maintained manually and is the sole version source used by the release pipeline. Versions must use valid SemVer, including an optional prerelease suffix when required.
## Preparing a release
1. Choose an unused version and update `package.json`.
2. Use conventional commit subjects for noteworthy changes:
- `feat:` for features
- `fix:` for bug fixes
- `perf:`, `refactor:`, or `revert:` for other noteworthy changes
3. Commit the version update and merge or push it to `main`.
Commits beginning with `chore:`, `build:`, `ci:`, `test:`, `docs:`, or `style:` are intentionally omitted from release notes. When no noteworthy commits exist, the release notes contain "Improvements ongoing."
## Running the release pipeline
The pipeline is driven by the `Purview.Build` tool through `just`:
```sh
just pipeline-release # restore, build, lint, tests, pack, publish, GitHub release
just pipeline-local-release # Same but to a local NuGet feed (use forward slashes in paths)
```
## CI release workflow
A push to `main` triggers `.github/workflows/release.yml`, which delegates to the `purview-dev/build` reusable `purview-release.yml` with `release-mode: NuGet`. It:
1. Reads and validates the version from `package.json`.
2. Builds the solution and runs unit and integration tests.
3. Packs the NuGet package using the exact manual version.
4. Builds release notes from noteworthy commits since the previous release.
5. Creates a GitHub Release with the `.nupkg` and `.snupkg` files attached.
The workflow does not publish to NuGet. Download the package from GitHub Releases and push it to the desired feed manually.
## Analyzer release tracking
The source generator ships Roslyn diagnostics (`ASPIREC4001`–`ASPIREC4007`). Their release history is tracked with
the standard `Microsoft.CodeAnalysis.Analyzers` release-tracking files:
- `src/src/SourceGenerators/AnalyzerReleases.Shipped.md` — rules that have already shipped, grouped under a
`## Release ` heading.
- `src/src/SourceGenerators/AnalyzerReleases.Unshipped.md` — rules added, changed, or removed since the last release.
This file starts empty at the beginning of every release.
Use these exact file names, at the `SourceGenerators` project root. `Microsoft.CodeAnalysis.Analyzers` only
auto-includes files called `AnalyzerReleases.Shipped.md` / `AnalyzerReleases.Unshipped.md` as compiler
`AdditionalFiles`; any other name (for example `Analysis.Shipped.md`) is silently ignored and no tracking occurs.
### During a release
1. Add any new, changed, or removed diagnostics to `AnalyzerReleases.Unshipped.md` as they are introduced.
2. When the release is cut, move every row from `AnalyzerReleases.Unshipped.md` into a new
`## Release ` section in `AnalyzerReleases.Shipped.md` and leave the unshipped file empty again.
3. If the release adds, removes, or changes no diagnostics, do not add a shipped section — the unshipped file
simply stays empty.
Only three descriptor attributes count as a "change": category, default severity, and enabled-by-default status.
Message text and descriptions can change without a tracking entry.
Build the generator project to verify the tracking files remain valid and complete:
```sh
dotnet build src/src/SourceGenerators/SourceGenerators.csproj -c Release
```
The build must produce no `RS2000`–`RS2008` diagnostics.
## Pack validation
`purview-build.json` declares the package content that must be present in the packed `.nupkg`
(`PackValidation:RequiredContent`). Keep the `lib/...` entries pinned to the concrete target framework
(`lib/net8.0/...`) rather than the `$(TFM)` token.
The validator expands `$(TFM)` into one entry per framework group it discovers in the package — including the
framework-agnostic `any` group contributed by the root-level `build/.props`. That makes
`lib/$(TFM)/...` also require a non-existent `lib/any/...` entry, so validation fails with
`Required content 'lib/any/...' is missing`. Update the pinned framework if the package ever multi-targets.
## See also
- [Contributing](../contributing/)