Skip to content

feat(frontend): identify the build, not just the release, in the changelog - #74

Merged
sgort merged 2 commits into
accfrom
feat/changelog-build-id
Sep 5, 2026
Merged

feat(frontend): identify the build, not just the release, in the changelog#74
sgort merged 2 commits into
accfrom
feat/changelog-build-id

Conversation

@sgort

@sgort sgort commented Sep 5, 2026

Copy link
Copy Markdown
Owner

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:

Changelog
Track new features, improvements, and bug fixes in Linked Data Explorer.
build 570fd98 · #412

The full 40-character SHA is on the title attribute so it can be copied for a lookup. With nothing injected the line reads local 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_command to Azure/static-web-apps-deploy with no skip_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:

  • The 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.
  • "Do not derive the SHA from git at build time" stops being a preference and becomes a requirement: neither git nor .git is 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 index chunk instead of a split one.

Design notes

A separate module, not logic in the component. src/utils/buildInfo.ts exposes getBuildInfo(); 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

Check Result
VITE_BUILD_SHA=… VITE_BUILD_RUN=412 npm run build:acc both values present in the bundle
npm run build:acc (no env) local build present, zero stale SHA, chunk hash changed
buildInfo unit tests 8/8, written RED-first
Full frontend suite 69 files, 1028 tests, all passing
Lint / Typecheck / check-format clean
Semgrep (494 Pro-tier rules, 6 files) 0 findings

The 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 is skip_app_build: true plus an explicit build step on the runner, matching what RBA does.

Expected oddity on this PR

On a pull request github.sha is 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; .gitignore already excluded it, .prettierignore did not, so check-format failed on files no one authored.

…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.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://brave-bay-04f351e03-74.westeurope.4.azurestaticapps.net

@sgort
sgort merged commit 9db0ab3 into acc Sep 5, 2026
3 checks passed
@sgort
sgort deleted the feat/changelog-build-id branch September 5, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant