feat(versioning): show runtime version and git hash in app footers

This commit is contained in:
2026-02-11 19:01:53 +01:00
parent 14c3df623a
commit 3de4d5732e
6 changed files with 71 additions and 5 deletions

View File

@@ -10,5 +10,7 @@ CMS_SUPPORT_EMAIL="support@cms.local"
CMS_SUPPORT_PASSWORD="change-me-support-password" CMS_SUPPORT_PASSWORD="change-me-support-password"
CMS_SUPPORT_NAME="Technical Support" CMS_SUPPORT_NAME="Technical Support"
CMS_SUPPORT_LOGIN_KEY="support-access-change-me" CMS_SUPPORT_LOGIN_KEY="support-access-change-me"
NEXT_PUBLIC_APP_VERSION="0.1.0-dev"
NEXT_PUBLIC_GIT_SHA="local"
# Optional dev bypass role for admin middleware. Leave empty to require auth login. # Optional dev bypass role for admin middleware. Leave empty to require auth login.
# CMS_DEV_ROLE="admin" # CMS_DEV_ROLE="admin"

19
TODO.md
View File

@@ -85,12 +85,12 @@ This file is the single source of truth for roadmap and delivery progress.
- [x] [P2] Bun-based Dockerfiles for public and admin apps - [x] [P2] Bun-based Dockerfiles for public and admin apps
- [x] [P2] Staging and production docker-compose templates - [x] [P2] Staging and production docker-compose templates
- [x] [P1] Registry credentials and image push strategy - [x] [P1] Registry credentials and image push strategy
- [x] [P1] Staging deployment automation against real host - [~] [P1] Staging deployment automation against real host
- [x] [P1] Production promotion and rollback procedure - [~] [P1] Production promotion and rollback procedure
### Git Flow And Branching ### Git Flow And Branching
- [x] [P1] Protect `main` and `staging` branches in Gitea - [~] [P1] Protect `main` and `staging` branches in Gitea
- [x] [P1] Define PR gates: lint + typecheck + unit + e2e list minimum - [x] [P1] Define PR gates: lint + typecheck + unit + e2e list minimum
- [x] [P1] Enforce one todo item per branch naming convention - [x] [P1] Enforce one todo item per branch naming convention
- [x] [P2] Add PR template requiring linked TODO step - [x] [P2] Add PR template requiring linked TODO step
@@ -101,10 +101,19 @@ This file is the single source of truth for roadmap and delivery progress.
- [x] [P1] Source of truth for version (`package.json` root) and release tagging rules (`vX.Y.Z`) - [x] [P1] Source of truth for version (`package.json` root) and release tagging rules (`vX.Y.Z`)
- [x] [P1] Build metadata policy for git hash (`+sha.<short>`) in app runtime footer - [x] [P1] Build metadata policy for git hash (`+sha.<short>`) in app runtime footer
- [x] [P1] App footer implementation plan for version + commit hash (admin + web) - [x] [P1] App footer implementation plan for version + commit hash (admin + web)
- [x] [P2] Automated version injection in CI (stamping build from tag + commit hash) - [~] [P2] Automated version injection in CI (stamping build from tag + commit hash)
- [x] [P2] Validation tests for displayed version/hash consistency per deployment - [ ] [P2] Validation tests for displayed version/hash consistency per deployment
- [x] [P1] Release tagging and changelog publication policy in CI - [x] [P1] Release tagging and changelog publication policy in CI
### MVP0 Close-Out Checklist
- [ ] [P1] Verify and document protected branch rules in Gitea (`main`, `staging`)
- [ ] [P1] Run first staging deployment against a real host with deploy workflow and document result
- [ ] [P1] Replace release workflow placeholders with real release-notes and rollback execution steps
- [x] [P1] Expose runtime version + short git hash in admin and public app footer
- [ ] [P2] Add CI build stamping for version/hash values consumed by app footers
- [ ] [P2] Add automated tests validating displayed version/hash format and consistency
## MVP 1: Core CMS Business Features ## MVP 1: Core CMS Business Features
### Admin App (Primary Focus) ### Admin App (Primary Focus)

View File

@@ -4,6 +4,7 @@ import type { ReactNode } from "react"
import { LogoutButton } from "@/app/logout-button" import { LogoutButton } from "@/app/logout-button"
import { AdminLocaleSwitcher } from "@/components/admin-locale-switcher" import { AdminLocaleSwitcher } from "@/components/admin-locale-switcher"
import { getBuildInfo } from "@/lib/build-info"
type AdminShellProps = { type AdminShellProps = {
role: Role role: Role
@@ -57,6 +58,8 @@ export function AdminShell({
actions, actions,
children, children,
}: AdminShellProps) { }: AdminShellProps) {
const buildInfo = getBuildInfo()
return ( return (
<div className="mx-auto flex min-h-screen w-full max-w-7xl gap-8 px-6 py-10"> <div className="mx-auto flex min-h-screen w-full max-w-7xl gap-8 px-6 py-10">
<aside className="sticky top-0 hidden h-fit w-64 shrink-0 space-y-4 lg:block"> <aside className="sticky top-0 hidden h-fit w-64 shrink-0 space-y-4 lg:block">
@@ -111,6 +114,10 @@ export function AdminShell({
</header> </header>
{children} {children}
<footer className="border-t border-neutral-200 pt-4 text-xs text-neutral-500">
Build v{buildInfo.version} +sha.{buildInfo.sha}
</footer>
</div> </div>
</div> </div>
) )

View File

@@ -0,0 +1,21 @@
const FALLBACK_VERSION = "0.0.1-dev"
const FALLBACK_SHA = "local"
function shortenSha(input: string): string {
const value = input.trim()
if (!value) {
return FALLBACK_SHA
}
return value.slice(0, 7)
}
export function getBuildInfo() {
const version = process.env.NEXT_PUBLIC_APP_VERSION?.trim() || FALLBACK_VERSION
const sha = shortenSha(process.env.NEXT_PUBLIC_GIT_SHA ?? "")
return {
version,
sha,
}
}

View File

@@ -2,9 +2,12 @@
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { getBuildInfo } from "@/lib/build-info"
export function PublicSiteFooter() { export function PublicSiteFooter() {
const t = useTranslations("Layout") const t = useTranslations("Layout")
const year = new Date().getFullYear() const year = new Date().getFullYear()
const buildInfo = getBuildInfo()
return ( return (
<footer className="border-t border-neutral-200 bg-neutral-50"> <footer className="border-t border-neutral-200 bg-neutral-50">
@@ -15,6 +18,9 @@ export function PublicSiteFooter() {
})} })}
</p> </p>
<p>{t("footer.tagline")}</p> <p>{t("footer.tagline")}</p>
<p className="font-mono text-xs text-neutral-500">
Build v{buildInfo.version} +sha.{buildInfo.sha}
</p>
</div> </div>
</footer> </footer>
) )

View File

@@ -0,0 +1,21 @@
const FALLBACK_VERSION = "0.0.1-dev"
const FALLBACK_SHA = "local"
function shortenSha(input: string): string {
const value = input.trim()
if (!value) {
return FALLBACK_SHA
}
return value.slice(0, 7)
}
export function getBuildInfo() {
const version = process.env.NEXT_PUBLIC_APP_VERSION?.trim() || FALLBACK_VERSION
const sha = shortenSha(process.env.NEXT_PUBLIC_GIT_SHA ?? "")
return {
version,
sha,
}
}