feat(frontend): identify the build, not just the release, in the changelog - #74
Merged
Conversation
…gelog The version in the changelog comes from changelog.json and is authored at release time, so it names a release rather than a build of it. ACC and production can serve different builds of the same version string, and redeploying unchanged code produces a new artifact carrying the same version. "Which build am I looking at?" was therefore unanswerable from the running app. The commit SHA says what was built; the run number distinguishes two builds of identical code, which is what makes this a build id rather than a code id. Both render as one small monospace line under the changelog subtitle — "build 570fd98 · #412" — with the full 40-character SHA on the title attribute so it can be copied for a lookup. Four decisions worth recording: Separate module rather than logic in the component. The fallback rules are then testable without rendering anything, which is most of buildInfo.test.ts. The env is read inside getBuildInfo() on every call, not captured at module scope. A module-scope capture is evaluated once at import and cannot be stubbed per test, which would leave the fallback path untestable. Half-configured counts as untracked. A run number with no SHA renders "local build", not "#412" — showing a run number with no commit behind it implies a provenance the bundle does not have. Likewise a SHA with no run number, which cannot tell two builds of one commit apart. Blank and whitespace-only values are treated as absent for the same reason. Nothing is derived from git at build time, and here that is not merely a preference. The Static Web Apps action builds inside its own Oryx container rather than on the runner — app_build_command, no skip_app_build — so neither git nor .git is guaranteed to exist at build time. The values are injected instead, on the deploy step itself, which is where the action picks up the runner environment to forward into that container. There is no separate build step to hang them on. No env file defines them, so every local run — dev server or production build — falls through to "local build". Never blank, never looking like a deployed artifact. Verified against real builds rather than only stubbed tests, since a build-time injection is exactly the kind of change that passes unit tests and puts nothing in the artifact. With the values set, both appear in the output bundle; without them, "local build" appears and no stale SHA survives. Both land in the main index chunk here, since the changelog is a page rather than a lazily loaded drawer. What the local greps cannot prove is that the environment crosses into the Oryx container. Only a deployed preview answers that. If it reports "local build", the fallback is skip_app_build with an explicit build step on the runner. On a pull request github.sha is the merge commit GitHub synthesises, so a preview deployment's SHA matches no commit in the branch history. That is the commit that was built and is expected. On a push it is the real commit. Scope is frontend only. The backend ships its version separately — this line describes the frontend bundle being viewed.
Guardian writes a .semgrep/ directory into the working tree, one per directory it scans, and check-format failed on the guardian.yml inside them. .gitignore already excludes .semgrep/, so the files never reach a commit; .prettierignore did not, so a clean tree still reported two style violations in files no one authored.
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://brave-bay-04f351e03-74.westeurope.4.azurestaticapps.net |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third and last of the three apps to get this — after the CPSV Editor (ttl-editor #83) and RONL Business API (#79).
The problem
The version in the Changelog page is authored at release time, so it identifies a release, not a build of it. ACC and production can serve different builds of the same version string, and redeploying unchanged code produces a new artifact carrying the same version. So "which build am I looking at?" was not answerable from the running app.
The commit SHA says what was built. The run number distinguishes two builds of identical code — that is what makes this a build id rather than a code id.
What it looks like
One small monospace line under the existing subtitle:
The full 40-character SHA is on the
titleattribute so it can be copied for a lookup. With nothing injected the line readslocal build— never blank, never looking like a deployed artifact.What is different here from RBA and ttl-editor
Oryx builds this one, not the runner. Both frontend workflows pass
app_build_commandtoAzure/static-web-apps-deploywith noskip_app_build, so the build happens inside the action's own container. The workflow already says as much: "The Static Web Apps action builds inside its own container and runs none of our scripts."Two consequences:
env:block sits on the deploy step, not a build step, because there is no build step. That is where the action picks up the runner environment to forward into the container.gitnor.gitis guaranteed to exist in that container.Also unlike RBA: the changelog here is a page rather than a lazily loaded drawer, so the build string lands in the main
indexchunk instead of a split one.Design notes
A separate module, not logic in the component.
src/utils/buildInfo.tsexposesgetBuildInfo(); the fallback rules are then testable without rendering anything.The env is read inside the function, not captured at module scope. A module-scope capture is evaluated once at import and cannot be stubbed per test, which would leave the fallback untestable.
Half-configured counts as untracked. A run number with no SHA renders
local build, not#412— showing a run number with no commit behind it implies a provenance the bundle does not have. A SHA with no run number is equally untracked, since it cannot tell two builds of one commit apart. Blank and whitespace-only values are treated as absent.Verification
VITE_BUILD_SHA=… VITE_BUILD_RUN=412 npm run build:accnpm run build:acc(no env)local buildpresent, zero stale SHA, chunk hash changedThe one thing still unproven
Every check above establishes the code path. None of them prove the environment crosses into the Oryx container — that boundary does not exist in RBA and cannot be exercised locally. The preview deployment on this PR is the first and only evidence.
If the preview reads
local build, the wiring is wrong rather than the code, and the fallback isskip_app_build: trueplus an explicit build step on the runner, matching what RBA does.Expected oddity on this PR
On a pull request
github.shais the merge commit GitHub synthesises, not the head of the branch, so the SHA on the preview will match no commit in this branch's history. That is normal and it is what got built. On a push it is the real commit.Second commit
chore(frontend): keep prettier off Semgrep Guardian's scratch files— unrelated to the feature, kept separate. Guardian writes a.semgrep/directory into the working tree;.gitignorealready excluded it,.prettierignoredid not, socheck-formatfailed on files no one authored.