feat(client): show git-describe version next to the brand logo

Display the app version (output of `git describe --tags`) in the header
beside the gitmost logo: a clean tag renders as `vX.Y.Z`, otherwise the
tag plus commits-since and short hash (e.g. v0.90.1-56-g25975acd).

- vite.config.ts: resolve APP_VERSION from env (Docker/CI) -> git describe
  (local) -> package.json version fallback
- app-header.tsx: render APP_VERSION after the brand block (ml="md"),
  nudge the Home nav group (ml={50} -> "xl")
- Dockerfile: accept APP_VERSION build-arg in the builder stage (.git is
  excluded from the build context)
- CI: pass APP_VERSION build-arg — release.yml uses the tag, develop.yml
  computes git describe with fetch-depth: 0
- nx.json: add APP_VERSION to the build target inputs so the cache
  invalidates when the version changes
This commit is contained in:
vvzvlad
2026-06-18 04:51:21 +03:00
parent ea56985efd
commit a0a8d3c97f
6 changed files with 58 additions and 3 deletions

View File

@@ -23,6 +23,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -34,11 +36,17 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve version
id: version
run: echo "value=$(git describe --tags --always)" >> "$GITHUB_OUTPUT"
- name: Build and push develop image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
build-args: |
APP_VERSION=${{ steps.version.outputs.value }}
push: true
tags: ${{ env.IMAGE }}:develop
cache-from: type=gha,scope=develop-amd64

View File

@@ -50,6 +50,8 @@ jobs:
with:
context: .
platforms: ${{ matrix.platform }}
build-args: |
APP_VERSION=${{ env.VERSION }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.suffix }}
cache-to: type=gha,scope=${{ matrix.suffix }},mode=max,ignore-error=true
@@ -76,6 +78,8 @@ jobs:
with:
context: .
platforms: ${{ matrix.platform }}
build-args: |
APP_VERSION=${{ env.VERSION }}
push: false
tags: |
${{ env.IMAGE }}:latest

View File

@@ -10,6 +10,9 @@ WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
# Version string shown in the UI (computed outside Docker because .git is not in the build context).
ARG APP_VERSION=""
ENV APP_VERSION=$APP_VERSION
RUN pnpm build
FROM base AS installer

View File

@@ -1,6 +1,7 @@
import {
Box,
Group,
Text,
Tooltip,
} from "@mantine/core";
import classes from "./app-header.module.css";
@@ -76,7 +77,20 @@ export function AppHeader() {
</Box>
</Link>
<Group ml={50} gap={5} className={classes.links} visibleFrom="sm">
<Tooltip label={t("Version")}>
<Text
size="xs"
c="dimmed"
lh={1}
ml="md"
visibleFrom="sm"
style={{ userSelect: "text", whiteSpace: "nowrap" }}
>
{APP_VERSION}
</Text>
</Tooltip>
<Group ml="xl" gap={5} className={classes.links} visibleFrom="sm">
{items}
</Group>
</Group>

View File

@@ -1,9 +1,28 @@
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import * as path from "path";
import { execSync } from "node:child_process";
const envPath = path.resolve(process.cwd(), "..", "..");
// Resolve the version string shown in the UI.
// Priority: explicit APP_VERSION env (injected by Docker/CI, where .git is absent),
// then `git describe` for local builds, then the package.json version as a fallback.
function resolveAppVersion(cwd: string): string {
const fromEnv = process.env.APP_VERSION?.trim();
if (fromEnv) return fromEnv;
try {
return execSync("git describe --tags --always", {
cwd,
stdio: ["ignore", "pipe", "ignore"],
})
.toString()
.trim();
} catch {
return `v${process.env.npm_package_version ?? "0.0.0"}`;
}
}
export default defineConfig(({ mode }) => {
const {
APP_URL,
@@ -32,7 +51,7 @@ export default defineConfig(({ mode }) => {
POSTHOG_HOST,
POSTHOG_KEY,
},
APP_VERSION: JSON.stringify(process.env.npm_package_version),
APP_VERSION: JSON.stringify(resolveAppVersion(envPath)),
},
plugins: [react()],
build: {

View File

@@ -4,7 +4,14 @@
"dependsOn": [
"^build"
],
"cache": true
"cache": true,
"inputs": [
"default",
"^default",
{
"env": "APP_VERSION"
}
]
},
"start:dev": {
"dependsOn": [