Shirubasoft.Aspire.E2E
2.0.4
dotnet add package Shirubasoft.Aspire.E2E --version 2.0.4
NuGet\Install-Package Shirubasoft.Aspire.E2E -Version 2.0.4
<PackageReference Include="Shirubasoft.Aspire.E2E" Version="2.0.4" />
<PackageVersion Include="Shirubasoft.Aspire.E2E" Version="2.0.4" />
<PackageReference Include="Shirubasoft.Aspire.E2E" />
paket add Shirubasoft.Aspire.E2E --version 2.0.4
#r "nuget: Shirubasoft.Aspire.E2E, 2.0.4"
#:package Shirubasoft.Aspire.E2E@2.0.4
#addin nuget:?package=Shirubasoft.Aspire.E2E&version=2.0.4
#tool nuget:?package=Shirubasoft.Aspire.E2E&version=2.0.4
Aspire E2E
Share .NET Aspire services across multiple repositories. A service runs as a Project in its home repo and as a Container everywhere else — with a single declaration.
The Problem
In a multi-repo setup, you want payments-service to:
- Run as a Project in the
paymentsrepo (for inner-loop development with hot reload, debugging, etc.) - Run as a Container in the
frontendrepo (consuming it as a dependency)
Aspire has no built-in way to do this. You'd need to maintain separate AppHost configurations, manually build container images, and keep them in sync.
How It Works
Aspire E2E introduces a SharedResourceReference MSBuild item and a global configuration file (~/.aspire-e2e/resources.json) that tracks how each shared service should run. A Roslyn source generator creates type-safe builders at compile time, and MSBuild targets handle container image builds automatically.
graph TD
CLI["CLI tool<br/><code>a2a</code>"] -->|reads/writes| Config["~/.aspire-e2e/<br/>resources.json"]
MSBuild["MSBuild targets<br/>(at build time)"] -->|reads| Config
Config -->|feeds| Generator["Source generator<br/>(typed builders)"]
Core Flows
Register (producer)
Set up a repo's service projects as shared resources. The CLI discovers .csproj files, injects EnableSdkContainerSupport and ContainerRepository properties, and saves the resource definitions to the global config. In interactive mode, all resource fields are prompted (container image, tag, build settings, registry, GitHub repository). You can also write a full SharedResourceReference with metadata directly into an AppHost .csproj in the same git repo — the project path is stored relative to the AppHost so references work across machines. This is the producer's home repo — before any consumer exists.
See samples/register for a working example.
Init (consumer)
Bootstrap an AppHost to consume shared resources. The CLI adds the hosting package, injects AddGlobalResourceConfiguration(), and scaffolds type-safe builder.Add*() calls from a multi-select prompt. This is the consumer's repo — pulling in dependencies registered elsewhere.
See samples/init for a working example.
Import (onboarding)
Onboard a new developer or CI environment. When you clone a repo whose AppHost already has SharedResourceReference elements with <GitHubRepository> metadata, import enriches entries from already-cloned repos found at the parent of the git root. If a repo isn't already cloned, it auto-clones it (unless --no-auto-clone is set). This decouples enrichment from cloning — pre-existing clones are always used regardless of auto-clone settings. Import also reads .aspire-e2e.json manifests from cloned repos, preferring manifest data over csproj SharedResourceReference elements.
See samples/import for a working example.
Manifest (universal)
For repos without an AppHost — including non-.NET repos (Go, Node, Python), SQL projects (.sqlproj), or .NET services that don't need an AppHost — you can declare shared resources in a .aspire-e2e.json manifest file at the repo root. The manifest uses the same format as ~/.aspire-e2e/resources.json (without the Overrides section).
a2a register creates a manifest automatically when no .csproj files are found (generic mode), or when you choose "Save to repo manifest" in interactive mode. a2a import reads manifests from cloned repos and merges their entries into the global config. For .NET projects, ProjectPath in the manifest is relative to the manifest file location.
{
"Aspire": {
"Resources": {
"payments-api": {
"Id": "payments-api",
"Name": "PaymentsApi",
"Mode": "Container",
"ContainerImage": "payments-api",
"ContainerTag": "latest",
"BuildImage": true,
"BuildImageCommand": "docker build -t payments-api .",
"ImageRegistry": "ghcr.io/myorg",
"GitHubRepository": "myorg/payments"
}
}
}
}
Packages
| Package | Description |
|---|---|
Shirubasoft.Aspire.E2E |
MSBuild targets to enable container support on service projects |
Shirubasoft.Aspire.E2E.Hosting |
Runtime types, MSBuild targets, and source generator for AppHost projects |
Shirubasoft.Aspire.E2E.Cli |
Global CLI tool for managing shared resources |
Getting Started
The CLI has three core flows: register, init, and import.
1. Install the CLI tool
dotnet tool install -g Shirubasoft.Aspire.E2E.Cli
2. Register (producer): discover and register projects
Use register to find projects and register them as shared resources. It discovers all .csproj files, lets you pick which ones to share, and automatically injects MSBuild container support properties. If no .csproj files are found, it enters generic mode for non-.NET repos and creates a .aspire-e2e.json manifest:
a2a register /path/to/payments-repo
In interactive mode, all resource fields are prompted: resource ID, name, mode, container image, container tag, build image, build image command, image registry, and GitHub repository. After registration, you can optionally write a full SharedResourceReference element with metadata into an AppHost .csproj in the same git repo. The project path is stored relative to the AppHost directory so that references remain valid across different machines and checkout locations.
Use --defaults to skip interactive prompts and auto-generate resource configuration:
a2a register /path/to/payments-repo --defaults
This saves discovered resources to ~/.aspire-e2e/resources.json and injects EnableSdkContainerSupport and ContainerRepository properties into each selected .csproj.
3. Init (consumer): configure an AppHost
Bootstrap an AppHost project with the hosting package, global configuration, and shared resource references:
a2a init ./src/MyAppHost/MyAppHost.csproj
If the AppHost path is omitted, it's auto-detected from the current directory (or from the --search path). This:
- Adds the
Shirubasoft.Aspire.E2E.Hostingpackage reference - Injects
AddGlobalResourceConfiguration()into the entry point file (Program.csorAppHost.cs) - Shows a multi-select prompt to pick which registered resources to wire up
- Adds
<SharedResourceReference>elements to the.csproj - Scaffolds
builder.Add*()calls in the entry point file
To combine discovery and initialization in one step, use --search:
a2a init --search /path/to/repo
4. Import (onboarding): import from an existing AppHost
When you clone a repo that already has SharedResourceReference elements in its AppHost .csproj, use import to populate your global config:
a2a import ./src/MyAppHost/MyAppHost.csproj
This:
- Reads
SharedResourceReferenceelements from the.csproj - Reads
.aspire-e2e.jsonmanifests from the repo root and from cloned repos (preferred over csproj) - Enriches entries from already-cloned repos at the parent of the git root
- Auto-clones GitHub repositories when
<GitHubRepository>is present and the repo isn't already cloned - Populates global config from discovered data
- Adds the hosting package and scaffolds builder calls
Use --no-auto-clone to skip automatic cloning:
a2a import ./src/MyAppHost/MyAppHost.csproj --no-auto-clone
5. Use in your AppHost code
var builder = DistributedApplication.CreateBuilder(args);
builder.Configuration.AddGlobalResourceConfiguration();
var payments = builder.AddPaymentsService()
.ConfigureProject(project => project
.WithHttpHealthCheck("/health"))
.ConfigureContainer(container => container
.WithHttpEndpoint(targetPort: 8080)
.WithHttpHealthCheck("/health"));
builder.Build().Run();
The AddPaymentsService() method is generated at compile time by the source generator. ConfigureProject and ConfigureContainer only execute their callbacks when the resource is in the matching mode.
Use Cases
Multi-repo microservices
A team owns orders-service in its own repo. Other teams reference it in their AppHosts as a container. The orders team runs it as a project for development:
# In orders repo — set mode to Project
a2a update orders-service --mode Project
# In frontend repo — set mode to Container
a2a update orders-service --mode Container
CI/CD container builds
The CLI can build container images with git-aware tagging:
a2a build payments-service
This runs the configured build command, tags the image with the current git branch and commit hash, and updates the global config with the new tag.
Switching modes during development
Temporarily switch a dependency to project mode for debugging, then switch back:
a2a update payments-service --mode Project --project-path /path/to/payments.csproj
# ... debug the issue ...
a2a update payments-service --mode Container
Skip image builds for faster iteration
When iterating on your own service, skip rebuilding container images for dependencies:
a2a update orders-service --build-image false
CLI Commands
| Command | Description |
|---|---|
a2a register [path] |
Discover projects and register them as shared resources |
a2a init [apphost-path] |
Configure an AppHost to consume shared resources |
a2a import <csproj-path> |
Import shared resources from a .csproj into global config |
a2a list |
Show all registered resources (--json for JSON output) |
a2a info <id> |
Show detailed info for a resource (--json for JSON output) |
a2a update <id> |
Update a resource configuration |
a2a remove <id> |
Remove a resource |
a2a build <id> |
Build a container image with git tagging |
a2a get-mode <id> |
Get the current mode (machine-readable) |
a2a get-project-path <id> |
Get the project path (machine-readable) |
a2a get-config <id> <key> |
Get a config value (machine-readable) |
a2a modes |
Interactively toggle Project/Container mode for resources |
a2a clear |
Delete all resources from the global configuration |
a2a override set <key> <value> |
Set a global override (Mode, BuildImage, or AutoClone, case-insensitive) |
a2a override set-registry <from> <to> |
Add an image registry rewrite rule |
a2a override set-image <from> <to> |
Add an image rewrite rule (e.g. rabbitmq:4-management → rabbitmq:4) |
a2a override remove <key> |
Remove a global override (case-insensitive key) |
a2a override remove-registry <from> |
Remove a registry rewrite rule |
a2a override remove-image <from> |
Remove an image rewrite rule |
a2a override list |
Show current overrides |
a2a override clear |
Remove all overrides |
Configuration
Resources are stored in ~/.aspire-e2e/resources.json:
{
"Aspire": {
"Overrides": {
"Mode": "Container",
"BuildImage": false,
"AutoClone": true,
"ImageRegistryRewrites": {
"docker.io": "ghcr.io/myorg"
},
"ImageRewrites": {
"rabbitmq:4-management": "rabbitmq:4"
}
},
"Resources": {
"payments-service": {
"Id": "payments-service",
"Name": "PaymentsService",
"Mode": "Container",
"ProjectPath": "/path/to/payments-service.csproj",
"ContainerImage": "payments-service",
"ContainerTag": "main-abc1234",
"BuildImage": true,
"BuildImageCommand": "dotnet publish --os linux --arch x64 /t:PublishContainer",
"ImageRegistry": "ghcr.io/myorg",
"GitHubRepository": "myorg/payments-service"
}
}
}
}
Overrides
The Overrides section applies values to all resources at load time without modifying the saved configuration. This is useful for switching every resource to container mode in CI, or rewriting image registries across environments.
Mode— overrides theModeon every resourceBuildImage— overridesBuildImageon every resourceAutoClone— controls whetherimportautomatically clones GitHub repositories when aGitHubRepositorytag is present (default:true)ImageRegistryRewrites— dictionary offrom → torewrites applied to each resource'sImageRegistryImageRewrites— dictionary offrom → torewrites applied to each resource's full image (ContainerImage:ContainerTag). For example,"rabbitmq:4-management": "rabbitmq:4"rewrites any resource with that exact image and tag.
Override keys in CLI commands (set, remove) are case-insensitive.
Overrides can also be set in a local e2e-resources.json file. Local overrides merge on top of global overrides.
Auto-clone and enrichment
When a SharedResourceReference includes a <GitHubRepository> tag (e.g. myorg/myrepo), import looks for the repository one level above the current git repo root. If the repo directory already exists, the entry is enriched from it regardless of auto-clone settings. If the directory doesn't exist and auto-clone is enabled, import clones the repository first, then enriches.
The clone prefers gh repo clone (GitHub CLI) and falls back to git clone https://github.com/{org/repo}.git.
To disable auto-clone for a single invocation:
a2a import ./MyAppHost.csproj --no-auto-clone
To disable globally:
a2a override set AutoClone false
Requirements
- .NET 10.0 or later
- .NET Aspire 13.1 or later
- Docker or Podman (for container mode)
Contributing
Contributions are welcome. Please open an issue first to discuss what you'd like to change.
License
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.4 | 350 | 2/1/2026 |
| 2.0.2 | 338 | 2/1/2026 |
| 1.0.53 | 334 | 2/1/2026 |
| 1.0.49 | 338 | 2/1/2026 |
| 1.0.47 | 339 | 2/1/2026 |
| 1.0.44 | 337 | 2/1/2026 |
| 1.0.41 | 339 | 2/1/2026 |
| 1.0.39 | 335 | 2/1/2026 |
| 1.0.36 | 348 | 2/1/2026 |
| 1.0.33 | 350 | 1/31/2026 |
| 1.0.31 | 344 | 1/31/2026 |
| 1.0.20 | 335 | 1/31/2026 |
| 1.0.18 | 333 | 1/31/2026 |
| 1.0.6 | 334 | 1/31/2026 |
| 1.0.5 | 337 | 1/31/2026 |
| 1.0.3 | 341 | 1/31/2026 |
| 0.1.54 | 338 | 1/31/2026 |
| 0.1.53 | 338 | 1/31/2026 |
| 0.1.43 | 340 | 1/31/2026 |
| 0.1.39 | 344 | 1/31/2026 |