diff --git a/Docs/Tasks.md b/Docs/Tasks.md index d35afa5..916816d 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -157,6 +157,8 @@ This document breaks the entire BanGUI project into development stages, ordered - Bumping only `Docker/VERSION` (e.g. `v0.9.9`) causes both layers to pick up the new version without touching any other file. - All existing tests pass (`pytest backend/`). +**Status:** ✅ Completed (2026-03-19) + --- ### Task GV-2 — Expose the BanGUI version through the API diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 065f373..21839c7 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -30,21 +30,39 @@ def _read_pyproject_version() -> str: return str(data["project"]["version"]) +def _read_docker_version() -> str: + """Read the project version from ``Docker/VERSION``. + + This file is the single source of truth for release scripts and must not be + out of sync with the frontend and backend versions. + """ + + repo_root = Path(__file__).resolve().parents[2] + version_path = repo_root / "Docker" / "VERSION" + if not version_path.exists(): + raise FileNotFoundError(f"Docker/VERSION not found at {version_path}") + + version = version_path.read_text(encoding="utf-8").strip() + return version.lstrip("v") + + def _read_version() -> str: """Return the current package version. - Prefer the project metadata in ``pyproject.toml`` when available, since this - is the single source of truth for local development and is kept in sync with - the frontend and Docker release version. + Prefer the release artifact in ``Docker/VERSION`` when available so the + backend version always matches what the release tooling publishes. - When running from an installed distribution where the ``pyproject.toml`` - is not available, fall back to installed package metadata. + If that file is missing (e.g. in a production wheel or a local checkout), + fall back to ``pyproject.toml`` and finally installed package metadata. """ try: - return _read_pyproject_version() + return _read_docker_version() except FileNotFoundError: - return importlib.metadata.version(PACKAGE_NAME) + try: + return _read_pyproject_version() + except FileNotFoundError: + return importlib.metadata.version(PACKAGE_NAME) __version__ = _read_version() diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 3ae85a1..5938a4c 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "bangui-backend" -version = "0.9.4" +version = "0.9.8" description = "BanGUI backend — fail2ban web management interface" requires-python = ">=3.12" dependencies = [ diff --git a/backend/tests/test_version.py b/backend/tests/test_version.py new file mode 100644 index 0000000..3a53898 --- /dev/null +++ b/backend/tests/test_version.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from pathlib import Path + +import app + + +def test_app_version_matches_docker_version() -> None: + """The backend version should match the signed off Docker release version.""" + + repo_root = Path(__file__).resolve().parents[2] + version_file = repo_root / "Docker" / "VERSION" + expected = version_file.read_text(encoding="utf-8").strip().lstrip("v") + + assert app.__version__ == expected diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 28df111..1d3bd6e 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,16 +3,19 @@ import react from "@vitejs/plugin-react"; import { resolve } from "path"; import { readFileSync } from "node:fs"; -const pkg = JSON.parse( - readFileSync(resolve(__dirname, "package.json"), "utf-8"), -) as { version: string }; +const appVersion = readFileSync( + resolve(__dirname, "../Docker/VERSION"), + "utf-8", +) + .trim() + .replace(/^v/, ""); // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], define: { - /** BanGUI application version injected at build time from package.json. */ - __APP_VERSION__: JSON.stringify(pkg.version), + /** BanGUI application version injected at build time from Docker/VERSION. */ + __APP_VERSION__: JSON.stringify(appVersion), }, resolve: { alias: {