Repository navigation
[Spike] Add GitHub Pages static hosting and bundle generation - #20924
Mitch Denny (mitchdenny) wants to merge 2 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3f065579-78f8-48d8-8f2c-35c77892a1f5
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 20924Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 20924" |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
Parent-symlink handling, cleanup behavior, rollback coverage, and package publication intent need resolution.
3 open findings
What changed in this PR
Adds an experimental GitHub Pages hosting integration that builds static frontend images and extracts deployable website bundles without GitHub credentials.
Changes:
- Adds GitHub Pages environment/site resources and bundle pipeline.
- Extends Vite metadata and container-image file extraction.
- Adds integration tests, snapshots, documentation, and solution entries.
| File | Description |
|---|---|
Aspire.slnx |
Registers new source and test projects. |
src/Aspire.Hosting.GitHubPages/Aspire.Hosting.GitHubPages.csproj |
Defines the integration package. |
src/Aspire.Hosting.GitHubPages/GitHubPagesEnvironmentResource.cs |
Models Pages addresses and endpoints. |
src/Aspire.Hosting.GitHubPages/GitHubPagesHostingExtensions.cs |
Implements configuration and bundle generation. |
src/Aspire.Hosting.GitHubPages/GitHubPagesSiteResource.cs |
Models the static-site deployment target. |
src/Aspire.Hosting.GitHubPages/README.md |
Documents usage and limitations. |
src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs |
Marks Vite resources for publish transformations. |
src/Aspire.Hosting.JavaScript/ViteAppAnnotation.cs |
Adds persistent Vite identity metadata. |
src/Aspire.Hosting/Publishing/ContainerRuntimeBase.cs |
Implements stopped-container extraction. |
src/Aspire.Hosting/Publishing/IContainerRuntime.cs |
Adds the image-file copy contract. |
tests/Aspire.Hosting.GitHubPages.Tests/Aspire.Hosting.GitHubPages.Tests.csproj |
Defines integration tests. |
tests/Aspire.Hosting.GitHubPages.Tests/GitHubPagesHostingTests.cs |
Tests modeling, validation, and bundling. |
tests/Aspire.Hosting.GitHubPages.Tests/Snapshots/...ViteDockerfile....txt |
Verifies generated Vite Dockerfile. |
tests/Aspire.Hosting.GitHubPages.Tests/Snapshots/.../site.verified.json |
Verifies bundle metadata. |
tests/Aspire.Hosting.GitHubPages.Tests/Snapshots/.../index.verified.html |
Verifies generated HTML output. |
tests/Aspire.Hosting.GitHubPages.Tests/Snapshots/.../app.verified.js |
Verifies generated JavaScript asset. |
tests/Aspire.Hosting.Tests/Publishing/ContainerRuntimeBaseTests.cs |
Tests extraction and cleanup behavior. |
tests/Aspire.Hosting.TestUtilities/Publishing/FakeContainerRuntime.cs |
Supports extraction tests. |
🧠 Review effort: Balanced
| Directory.CreateDirectory(outputDirectory); | ||
| if (Directory.Exists(directory) && (File.GetAttributes(directory) & FileAttributes.ReparsePoint) != 0) | ||
| { | ||
| throw new DistributedApplicationException($"GitHub Pages output directory '{directory}' cannot be a symbolic link."); |
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>$(DefaultTargetFramework)</TargetFramework> | ||
| <IsPackable>true</IsPackable> |
| await ExecuteContainerCommandForOutputAsync( | ||
| ["rm", "--force", "--volumes", containerName], | ||
| "remove file-export container", imageName, cleanup.Token).ConfigureAwait(false); |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3f065579-78f8-48d8-8f2c-35c77892a1f5
Tests selectorSelects the full PR test matrix + all PR-gated jobs (ALL) — a rule matching 'Aspire.slnx' selects ALL Selection computed for commit |
| catch (Exception cleanupError) when (copyError is not null) | ||
| { | ||
| throw new AggregateException("Copying image files and removing the temporary container both failed.", copyError, cleanupError); |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |


Description
Explore GitHub Pages as a static compute environment for Aspire frontends, so applications can use the existing image-build pipeline to produce a complete static website bundle without requiring GitHub credentials or publishing to a real Pages site.
This draft contains Stage 1 of the spike: the experimental
Aspire.Hosting.GitHubPagesintegration, intended site addresses, Vite base-path configuration, and independently selectable bundle generation. Local Vite development behavior remains unchanged.The environment supports a public project site at
https://owner.github.io/repository/, an account-root site, or an explicitly declared canonical hostname. These addresses are declared rather than verified against GitHub. One repository receives one static website.User-facing usage
C# AppHost:
TypeScript AppHost (exported API; full generated-SDK validation is deferred):
Use
.WithCustomDomain("app.example.com")to declare an existing canonical hostname and switch the content base path to/. Without it, Vite's generated build uses--base=/repository/for a project site.Generate the frontend bundle and its build prerequisites:
aspire do bundle-frontend --output-path ./artifactsThe generated output is
github-pages/pages/dist/plussite.json.aspire publishalso generates the bundle. This is a source-only, unpublished integration; evaluating its C# APIs requires suppressing the experimental diagnostics.Implementation
IContainerRuntime.CopyFilesFromImageAsyncwith stopped-container extraction for Docker/Podman and an explicit unsupported default for other implementations. Extraction does not start, pull, or push the source image.index.html, and symbolic-link rejection. Prepare replacement output on the output filesystem, with rollback and independent container cleanup.Dependencies are the existing
Aspire.HostingandAspire.Hosting.JavaScriptprojects. No new external package dependency is introduced.Remaining scope
This is not ready to ship. Backend CORS wiring, late-bound backend URLs for frontend builds, a Pages publisher, simulator/browser coverage, provider preflight, and full generated-SDK/preview readiness remain follow-up stages.
aspire deployfails explicitly rather than reporting a skipped Pages deployment as successful. There are no live Pages, Azure, DNS, certificate, or repository-settings changes.Validation
./restore.shdotnet test --project tests/Aspire.Hosting.GitHubPages.Tests/Aspire.Hosting.GitHubPages.Tests.csproj --no-launch-profile -- --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj --no-launch-profile -- --filter-class "*.ContainerRuntimeBaseTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Hosting.JavaScript.Tests/Aspire.Hosting.JavaScript.Tests.csproj --no-launch-profile -- --filter-class "*.AddViteAppTests" --filter-class "*.AddViteAppWithPnpmTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet pack src/Aspire.Hosting.GitHubPages/Aspire.Hosting.GitHubPages.csproj --nologo -v:qgit diff --checkBundle and generated Vite Dockerfile snapshots were reviewed. The native fixture image and export containers were cleaned up. Real Vite compilation, live Pages compatibility, full generated polyglot SDK validation, and the full repository build/test suite were not run for this checkpoint.
Security considerations
The integration invokes the selected container runtime and copies image-provided files into temporary and caller-selected output directories. Export commands use argument lists, the export container is not started, bundle validation rejects symbolic links, and output replacement uses same-filesystem staging. No GitHub credentials, cloud permissions, or new network listeners are introduced. Security review is still needed for path handling, image-file extraction, replacement/rollback, and cleanup behavior; no threat model or formal security review has been completed.
Fixes # (issue)
Checklist
<remarks />and<code />elements on your triple slash comments?