Fixes /admin 404 by building the admin SPA before dotnet publish, not via an MSBuild target
Continuous Integration / config (pull_request) Successful in 10s
Continuous Integration / backend-build (pull_request) Successful in 4m52s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m50s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m44s
Continuous Integration / backend-test (pull_request) Successful in 5m37s
Continuous Integration / frontend-build (pull_request) Successful in 2m11s
Continuous Integration / frontend-test (pull_request) Successful in 4m33s
Continuous Integration / frontend-lint (pull_request) Successful in 2m2s
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Continuous Integration / publish-test (pull_request) Successful in 6m14s
Deploy (SCP) / deploy (pull_request) Successful in 1m20s
Continuous Integration / deploy-test (pull_request) Successful in 1m21s

The BuildAndCopyAdminFrontend MSBuild target (BeforeTargets=Publish) never actually
worked: files it created after project evaluation were silently absent from the
publish output, and forcing them in via an explicit Content item collided with the
SDK's own static-web-asset resolution. Verified locally with a clean obj/bin and a
fresh node_modules - every deploy so far genuinely shipped without wwwroot/admin.
Moves the frontend build + copy into its own CI step ahead of dotnet publish, so the
SDK's ordinary wwwroot handling picks it up with no custom MSBuild involved.

Also fixes unreadable <code> badges on the website placeholder page (no explicit
text color, relying on inherited body color pairing unreliably with the badge
background).
This commit is contained in:
2026-07-30 11:10:17 +02:00
parent 9b44a5e85e
commit 7c1d2aa520
3 changed files with 47 additions and 21 deletions
+30 -4
View File
@@ -253,13 +253,29 @@ jobs:
exit 1
fi
- name: Publish (test)
working-directory: src/SlpModularCms.Api
# Builds the admin SPA and lands it in wwwroot/admin *before* dotnet publish runs, as its own
# step rather than an MSBuild target hooked to Build/Publish. That was tried and reliably
# failed two different ways (see SlpModularCms.Api.csproj's comment on the topic): the SDK's
# wwwroot static-web-asset item set is fixed at project evaluation time, before any target
# runs, so files a target creates afterward never make it into the publish output; and forcing
# them in as an explicit Content item collided with the SDK's own static-web-asset resolution.
# Physically existing on disk before dotnet publish/build ever runs is the only thing that
# worked, verified locally against a clean obj/bin and a fresh node_modules.
- name: Build admin frontend
working-directory: frontend
env:
VITE_APP_ENV: test
VITE_UMAMI_SCRIPT_URL: ${{ vars.VITE_UMAMI_SCRIPT_URL }}
VITE_UMAMI_WEBSITE_ID: ${{ vars.VITE_UMAMI_WEBSITE_ID_TEST }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
run: |
pnpm install --frozen-lockfile
pnpm build
mkdir -p ../src/SlpModularCms.Api/wwwroot/admin
cp -r dist/. ../src/SlpModularCms.Api/wwwroot/admin/
- name: Publish (test)
working-directory: src/SlpModularCms.Api
run: >
dotnet publish -c Release -r ${{ env.PUBLISH_RID }} --self-contained false
-o ${{ github.workspace }}/${{ env.ARTIFACT_NAME_TEST }}
@@ -300,13 +316,23 @@ jobs:
exit 1
fi
- name: Publish (production)
working-directory: src/SlpModularCms.Api
# See publish-test's "Build admin frontend" step for why this runs as its own step ahead of
# dotnet publish rather than an MSBuild target.
- name: Build admin frontend
working-directory: frontend
env:
VITE_APP_ENV: production
VITE_UMAMI_SCRIPT_URL: ${{ vars.VITE_UMAMI_SCRIPT_URL }}
VITE_UMAMI_WEBSITE_ID: ${{ vars.VITE_UMAMI_WEBSITE_ID_PRODUCTION }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
run: |
pnpm install --frozen-lockfile
pnpm build
mkdir -p ../src/SlpModularCms.Api/wwwroot/admin
cp -r dist/. ../src/SlpModularCms.Api/wwwroot/admin/
- name: Publish (production)
working-directory: src/SlpModularCms.Api
run: >
dotnet publish -c Release -r ${{ env.PUBLISH_RID }} --self-contained false
-o ${{ github.workspace }}/${{ env.ARTIFACT_NAME_PRODUCTION }}
@@ -20,7 +20,7 @@
}
@media (prefers-color-scheme: dark) {
body { background: #141414; color: #ededed; }
code { background: #262626; }
code { background: #262626; color: #141414; }
a { color: #ff6b6b; }
}
main { max-width: 34rem; }
@@ -28,6 +28,7 @@
p { margin: 0 0 1rem; }
code {
background: #ececec;
color: #1a1a1a;
padding: 0.15em 0.4em;
border-radius: 4px;
font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
+15 -16
View File
@@ -35,22 +35,21 @@
</ItemGroup>
<!--
Builds the CMS admin SPA (frontend/) and copies its output into wwwroot/admin so it's served
at the /admin path (see Program.cs). Only runs on `dotnet publish`, not on every dev build.
The public website ('/') lives outside this repo and is deployed into wwwroot separately.
The admin SPA (frontend/) is served at /admin (see Program.cs) from wwwroot/admin, but is
deliberately NOT built by an MSBuild target hooked to Build/Publish here. Both attempts tried
(a plain <Copy> after a BeforeTargets="Publish" pnpm build, and an explicit <Content> item with
a custom TargetPath) were verified locally to fail: the SDK's wwwroot static-web-asset item set
is fixed at project evaluation time, before any target runs, so files a target creates afterward
are silently absent from the publish output; and forcing them in as a Content item collided with
the SDK's own static-web-asset resolution ("Two assets found targeting the same path with
incompatible asset kinds: admin/admin/..."), reproducibly, even from a clean obj/bin and a fresh
node_modules. The only combination that published wwwroot/admin correctly was the files already
physically existing on disk *before* dotnet publish/build ever runs — see the CI workflow
(continuous_integration.yaml), which builds frontend/ and copies frontend/dist into
wwwroot/admin as its own step, ahead of `dotnet publish`. Do the same locally before publishing:
run `pnpm install` then `pnpm build` inside frontend/, then copy
`frontend/dist/*` into `wwwroot/admin/` (that folder is gitignored, so nothing to clean up
afterward), then `dotnet publish` picks it up via the SDK's ordinary wwwroot handling.
-->
<PropertyGroup>
<AdminFrontendDir>$(MSBuildProjectDirectory)/../../frontend</AdminFrontendDir>
</PropertyGroup>
<Target Name="BuildAndCopyAdminFrontend" BeforeTargets="Publish">
<Message Importance="high" Text="Building CMS admin frontend ($(AdminFrontendDir))..." />
<Exec Command="pnpm install --frozen-lockfile" WorkingDirectory="$(AdminFrontendDir)" />
<Exec Command="pnpm build" WorkingDirectory="$(AdminFrontendDir)" />
<ItemGroup>
<AdminFrontendFiles Include="$(AdminFrontendDir)/dist/**/*" />
</ItemGroup>
<Copy SourceFiles="@(AdminFrontendFiles)" DestinationFolder="$(MSBuildProjectDirectory)/wwwroot/admin/%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
</Project>