From b2db6efd634396c6e55c7644512083bcf50edd9c Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 26 Feb 2026 01:17:09 -0500 Subject: [PATCH] fix: use correct versions for the UI --- .github/workflows/next.yaml | 5 +- .github/workflows/release.yaml | 5 +- CHANGELOG.md | 1 + Dockerfile | 3 +- vite.config.ts | 120 ++++++++++++++++++--------------- 5 files changed, 75 insertions(+), 59 deletions(-) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index 895c34f..ee63087 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -25,9 +25,9 @@ jobs: matrix: include: - target: final - tag: 'next' + tag: "next" - target: debug-shell - tag: 'next-shell' + tag: "next-shell" steps: - name: Check out the repo @@ -69,6 +69,7 @@ jobs: cache-to: type=gha,mode=max build-args: | IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ matrix.tag }} + HEADPLANE_VERSION=next+${{ github.sha }} secrets: | gh_token=${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f4612c8..6517064 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,9 +23,9 @@ jobs: matrix: include: - target: final - tag_suffix: '' + tag_suffix: "" - target: debug-shell - tag_suffix: '-shell' + tag_suffix: "-shell" steps: - name: Check out the repo @@ -70,6 +70,7 @@ jobs: cache-to: type=gha,mode=max build-args: | IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} + HEADPLANE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} - name: Attestation Provenance for ${{ fromJSON(steps.meta.outputs.json).tags[0] }} uses: actions/attest-build-provenance@v2 id: attest diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d0ced..c5c2f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - Fixed pre-auth keys not showing for OIDC users without a username (via [#470](https://github.com/tale/headplane/pull/470)). - Fixed truncated pre-auth key display with longer Headscale 0.28 bcrypt tokens (closes [#435](https://github.com/tale/headplane/issues/435)). - Fixed Nix systemd service to use user-specified package (via [#454](https://github.com/tale/headplane/pull/454)). +- Version displayed in the UI is now derived from git tags and build args instead of `package.json`, fixing incorrect versions shown on beta and nightly builds. --- diff --git a/Dockerfile b/Dockerfile index 93c9f25..ce3c324 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,8 @@ COPY --from=go-base /bin/wasm_exec.js /run/app/wasm_exec.js RUN ./build.sh --app --app-install-only COPY . . -RUN ./build.sh --app +ARG HEADPLANE_VERSION +RUN HEADPLANE_VERSION=$HEADPLANE_VERSION ./build.sh --app FROM gcr.io/distroless/nodejs22-debian12:latest AS final COPY --from=js-base /run/build /app/build diff --git a/vite.config.ts b/vite.config.ts index cf70950..9a5f61c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,65 +1,77 @@ -import { readFile } from 'node:fs/promises'; -import { reactRouter } from '@react-router/dev/vite'; -import tailwindcss from '@tailwindcss/vite'; -import { reactRouterHonoServer } from 'react-router-hono-server/dev'; -import { defineConfig } from 'vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; -import { parse } from 'yaml'; +import { execSync } from "node:child_process"; +import { readFile } from "node:fs/promises"; -const prefix = process.env.__INTERNAL_PREFIX || '/admin'; -if (prefix.endsWith('/')) { - throw new Error('Prefix must not end with a slash'); +import { reactRouter } from "@react-router/dev/vite"; +import tailwindcss from "@tailwindcss/vite"; +import { reactRouterHonoServer } from "react-router-hono-server/dev"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { parse } from "yaml"; + +const prefix = process.env.__INTERNAL_PREFIX || "/admin"; +if (prefix.endsWith("/")) { + throw new Error("Prefix must not end with a slash"); } -// Load the version via package.json -const pkg = await readFile('package.json', 'utf-8'); -const isNext = process.env.IMAGE_TAG?.includes('next'); -const { version } = JSON.parse(pkg); +// Derive version: HEADPLANE_VERSION env > git describe > package.json +const isNext = process.env.IMAGE_TAG?.includes("next"); +let version: string; +if (process.env.HEADPLANE_VERSION) { + version = process.env.HEADPLANE_VERSION; +} else { + try { + const describe = execSync("git describe --tags", { encoding: "utf-8" }) + .trim() + .replace(/^v/, ""); + const tag = execSync("git describe --tags --abbrev=0", { encoding: "utf-8" }) + .trim() + .replace(/^v/, ""); + version = describe === tag ? tag : `${tag}-dev+${describe.split("-").pop()}`; + } catch { + const pkg = await readFile("package.json", "utf-8"); + version = JSON.parse(pkg).version; + } +} if (!version) { - throw new Error('Unable to read version from package.json'); + throw new Error("Unable to determine version"); } // Load the config without any environment variables (not needed here) -const config = await readFile('config.example.yaml', 'utf-8'); +const config = await readFile("config.example.yaml", "utf-8"); const { server } = parse(config); export default defineConfig(({ command, isSsrBuild }) => ({ - base: command === 'build' ? `${prefix}/` : undefined, - plugins: [ - reactRouterHonoServer(), - reactRouter(), - tailwindcss(), - tsconfigPaths(), - ], - server: { - host: server.host, - port: server.port, - }, - build: { - target: 'esnext', - sourcemap: true, - rolldownOptions: - command === 'build' - ? { - // Exclude libsql from the server side since it's a native module - // Exclude WASM from the client since it fetches from the server - external: isSsrBuild ? [/^@libsql\//] : [/\.wasm(\?url)?$/], - output: { - manualChunks: undefined, - inlineDynamicImports: isSsrBuild, - }, - } - : undefined, - }, - ssr: { - target: 'node', - noExternal: command === 'build' ? true : undefined, - }, - optimizeDeps: { - include: ['@libsql/client'], - }, - define: { - __VERSION__: JSON.stringify(isNext ? `${version}-next` : version), - __PREFIX__: JSON.stringify(prefix), - }, + base: command === "build" ? `${prefix}/` : undefined, + plugins: [reactRouterHonoServer(), reactRouter(), tailwindcss(), tsconfigPaths()], + server: { + host: server.host, + port: server.port, + }, + build: { + target: "esnext", + sourcemap: true, + rolldownOptions: + command === "build" + ? { + // Exclude libsql from the server side since it's a native module + // Exclude WASM from the client since it fetches from the server + external: isSsrBuild ? [/^@libsql\//] : [/\.wasm(\?url)?$/], + output: { + manualChunks: undefined, + inlineDynamicImports: isSsrBuild, + }, + } + : undefined, + }, + ssr: { + target: "node", + noExternal: command === "build" ? true : undefined, + }, + optimizeDeps: { + include: ["@libsql/client"], + }, + define: { + __VERSION__: JSON.stringify(isNext ? `${version}-next` : version), + __PREFIX__: JSON.stringify(prefix), + }, }));