fix(version): BUG-047 la version suit chaque livraison (source unique VERSION + bump SemVer automatique)
CI / lint (push) Successful in 1m27s
CI / security (push) Successful in 57s
CI / test (push) Successful in 3m11s
CI / build (push) Successful in 54s
CI / e2e (push) Successful in 10m59s

VERSION (racine) devient la source unique de verite MAJEUR.MINEUR.CORRECTIF, incrementee a chaque commit par le hook prepare-commit-msg (BREAKING -> majeur, feat -> mineur, sinon correctif) ; post-commit rattache les fichiers derives au commit et cree le tag vX.Y.Z, publie au push (push.followTags). Tous les derives sont resynchronises : package.json, desktop Tauri, README.md/README.fr.md, docs/ROADMAP.md, CHANGELOG.md. Backend, image Docker et desktop lisent le meme fichier (plus de numero code en dur). Outils : scripts/bump_version.py, scripts/install-hooks.sh. Tests : tests/test_version.py (46, garde-fou de coherence globale).
This commit is contained in:
2026-09-16 11:02:47 -04:00
parent 3261917d62
commit d98c0c2f33
24 changed files with 1423 additions and 249 deletions
+51 -21
View File
@@ -1,23 +1,34 @@
"""
Version management for ObsiGate.
The canonical version is the numeric SemVer of the LATEST release tag
(MAJOR.MINOR.PATCH). get_version() always returns a clean "x.y.z" string so
the UI never shows "-dev", "0.0.0-dev" or a "-N-gHASH" suffix — the same
number appears in the header badge, the About modal and the API health.
Source unique de vérité : le fichier `VERSION` à la racine du dépôt, au format
`MAJEUR.MINEUR.CORRECTIF` (incrémenté à chaque livraison par
`scripts/bump_version.py`, hook git `commit-msg`). get_version() retourne
toujours une chaîne propre "x.y.z" — jamais "-dev", "0.0.0-dev" ou "-N-gHASH" :
le même numéro s'affiche dans le badge d'en-tête, la boîte À propos et /api/health.
Examples:
tag v2.0.0 -> "2.0.0"
HEAD 31 commits after -> "2.0.0" (release version, no dev clutter)
no git / no VERSION -> "0.0.0"
Les sources sont consultées dans cet ordre :
1. variable d'environnement OBSIGATE_VERSION (surcharge explicite / tests) ;
2. `VERSION` à la racine du dépôt (source unique, copiée dans l'image Docker) ;
3. `backend/VERSION` (ancien emplacement, encore produit par certains builds) ;
4. dernier tag git (`git describe --tags --abbrev=0`) ;
5. "0.0.0".
Exemples :
VERSION = 2.3.0 -> "2.3.0"
tag v2.3.0, 4 commits -> "2.3.0"
aucun VERSION / git -> "0.0.0"
"""
from __future__ import annotations
import os
import subprocess
from pathlib import Path
_ROOT = Path(__file__).resolve().parent.parent # ObsiGate repo root
_VERSION_FILE = Path(__file__).resolve().parent / "VERSION"
_ROOT = Path(__file__).resolve().parent.parent # racine du dépôt ObsiGate
VERSION_FILE = _ROOT / "VERSION" # source unique de vérité
LEGACY_VERSION_FILE = Path(__file__).resolve().parent / "VERSION" # backend/VERSION
_ENV_VAR = "OBSIGATE_VERSION"
def _run_git(args: list[str]) -> str:
@@ -57,6 +68,16 @@ def _clean_base(raw: str) -> str:
return ".".join(nums)
def _read_version_file(path: Path) -> str:
"""Clean base of a VERSION file, or '' when absent/unreadable/invalid."""
try:
if not path.exists():
return ""
return _clean_base(path.read_text(encoding="utf-8", errors="replace"))
except OSError:
return ""
def get_git_describe() -> str:
"""Full `git describe` string (e.g. "2.0.0-31-gabc1234") or '' if no git."""
return _run_git(["describe", "--tags", "--dirty=-dirty"]).lstrip("v")
@@ -68,23 +89,32 @@ def get_git_commit() -> str:
def get_version() -> str:
"""Return the clean release version x.y.z (latest tag) — never a -suffix.
"""Return the clean release version x.y.z — never a -suffix.
Priority: latest git tag -> backend/VERSION file -> "0.0.0".
Priority: OBSIGATE_VERSION -> ./VERSION -> backend/VERSION -> latest git tag
-> "0.0.0".
"""
# 1) Latest tag from git (works even with commits beyond the tag)
tag = _run_git(["describe", "--tags", "--abbrev=0"])
base = _clean_base(tag)
# 1) Explicit override (docker-compose, tests, builds hors dépôt)
override = _clean_base(os.environ.get(_ENV_VAR, ""))
if override:
return override
# 2) Source unique de vérité : VERSION à la racine du dépôt
base = _read_version_file(VERSION_FILE)
if base:
return base
# 2) backend/VERSION file (baked at build time by build.sh / CI / Docker)
if _VERSION_FILE.exists():
base = _clean_base(_VERSION_FILE.read_text(encoding="utf-8"))
if base:
return base
# 3) Ancien emplacement (backend/VERSION, baké par certains builds)
base = _read_version_file(LEGACY_VERSION_FILE)
if base:
return base
# 3) Nothing available
# 4) Dernier tag git
base = _clean_base(_run_git(["describe", "--tags", "--abbrev=0"]))
if base:
return base
# 5) Rien d'exploitable
return "0.0.0"