feat: badge versione build in GUI + build.sh con auto-bump
- VERSION (1.0.0) + build.sh: bump patch/minor/major e deploy via docker compose - Dockerfile: ARG/ENV APP_VERSION + BUILD_TIME (iniettati come build-arg) - version.build_info(): env in Docker, fallback file VERSION + '(local)' in dev - GET /api/version; badge #appVersionPill nell'header (tooltip = build time) - test_version.py: env vs fallback Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a88a064aa8
commit
cf726b479e
8 files changed
+126
-1
No files matched your search
+8
-1
@@ -12,13 +12,20 @@ COPY pyproject.toml README.md ./
|
||||
COPY src/ src/
|
||||
RUN uv sync --no-dev
|
||||
|
||||
COPY config.yaml ./
|
||||
COPY config.yaml VERSION ./
|
||||
COPY web/ web/
|
||||
|
||||
ENV PRESA_MANO_CONFIG=/app/config.yaml
|
||||
ENV PRESA_MANO_DATA=/app/data
|
||||
ENV PRESA_MANO_PORT=8890
|
||||
|
||||
# Versione build: iniettata da build.sh come --build-arg. Cambia ad ogni build
|
||||
# (il valore diverso invalida solo questi due layer finali, non le dipendenze).
|
||||
ARG APP_VERSION=dev
|
||||
ARG BUILD_TIME=unknown
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV BUILD_TIME=${BUILD_TIME}
|
||||
|
||||
EXPOSE 8890
|
||||
|
||||
# Shell form so $PRESA_MANO_PORT viene espanso a runtime: cambi la porta in
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build + deploy di presa-mano con auto-bump della versione.
|
||||
#
|
||||
# ./build.sh → patch++ (1.0.1 -> 1.0.2)
|
||||
# ./build.sh minor → minor++ (1.0.2 -> 1.1.0)
|
||||
# ./build.sh major → major++ (1.1.0 -> 2.0.0)
|
||||
#
|
||||
# La nuova versione + l'ora di build vengono iniettate nell'immagine come
|
||||
# build-arg e mostrate nel badge in GUI. Il file VERSION viene riscritto ma NON
|
||||
# committato automaticamente (lo committi tu con le altre modifiche).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
COMPOSE_DIR="$(dirname "$SCRIPT_DIR")" # docker-compose.yml sta un livello sopra
|
||||
VERSION_FILE="$SCRIPT_DIR/VERSION"
|
||||
|
||||
[ -f "$VERSION_FILE" ] || echo "1.0.0" > "$VERSION_FILE"
|
||||
cur="$(tr -d '[:space:]' < "$VERSION_FILE")"
|
||||
IFS='.' read -r major minor patch <<< "$cur"
|
||||
major="${major:-1}"; minor="${minor:-0}"; patch="${patch:-0}"
|
||||
|
||||
case "${1:-patch}" in
|
||||
major) major=$((major + 1)); minor=0; patch=0 ;;
|
||||
minor) minor=$((minor + 1)); patch=0 ;;
|
||||
patch) patch=$((patch + 1)) ;;
|
||||
*) echo "uso: $0 [major|minor|patch]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
new="${major}.${minor}.${patch}"
|
||||
echo "$new" > "$VERSION_FILE"
|
||||
build_time="$(date -u +"%Y-%m-%d %H:%M UTC")"
|
||||
|
||||
echo "→ build V${new} (${build_time})"
|
||||
cd "$COMPOSE_DIR"
|
||||
docker compose build \
|
||||
--build-arg APP_VERSION="$new" \
|
||||
--build-arg BUILD_TIME="$build_time" \
|
||||
presa-mano
|
||||
docker compose up -d presa-mano
|
||||
echo "✓ deployed V${new} (${build_time})"
|
||||
@@ -25,6 +25,7 @@ from .api import (
|
||||
)
|
||||
from .clients import G1StatusError
|
||||
from .config import load_config
|
||||
from .version import build_info
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||
|
||||
@@ -102,6 +103,11 @@ def health() -> dict:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/api/version", tags=["meta"])
|
||||
def version() -> dict:
|
||||
return build_info()
|
||||
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
def index() -> HTMLResponse:
|
||||
idx = WEB_DIR / "index.html"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Versione build dell'app, mostrata nel badge in GUI.
|
||||
|
||||
In Docker i valori arrivano da env (`APP_VERSION`, `BUILD_TIME`) iniettati come
|
||||
build-arg da `build.sh`. In dev locale (`uv run`) gli env non ci sono: si
|
||||
ripiega sul contenuto del file `VERSION` nella root del repo + suffisso
|
||||
`(local)`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
_VERSION_FILE = Path(__file__).resolve().parents[2] / "VERSION"
|
||||
|
||||
|
||||
def _file_version() -> str:
|
||||
try:
|
||||
return _VERSION_FILE.read_text(encoding="utf-8").strip() or "0.0.0"
|
||||
except OSError:
|
||||
return "0.0.0"
|
||||
|
||||
|
||||
def build_info() -> dict:
|
||||
"""Ritorna {'version', 'build_time'}.
|
||||
|
||||
Preferisce gli env settati al build dell'immagine; in loro assenza ripiega
|
||||
sul file VERSION marcando la versione come `(local)`.
|
||||
"""
|
||||
version = os.environ.get("APP_VERSION")
|
||||
build_time = os.environ.get("BUILD_TIME")
|
||||
if not version:
|
||||
version = f"{_file_version()} (local)"
|
||||
if not build_time:
|
||||
build_time = "(local)"
|
||||
return {"version": version, "build_time": build_time}
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Unit test per build_info(): env (Docker) vs fallback VERSION file (local)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from presa_mano.version import build_info
|
||||
|
||||
_VERSION_FILE = Path(__file__).resolve().parents[1] / "VERSION"
|
||||
|
||||
|
||||
def test_build_info_prefers_env(monkeypatch):
|
||||
monkeypatch.setenv("APP_VERSION", "1.2.3")
|
||||
monkeypatch.setenv("BUILD_TIME", "2026-06-03 14:22 UTC")
|
||||
info = build_info()
|
||||
assert info == {"version": "1.2.3", "build_time": "2026-06-03 14:22 UTC"}
|
||||
|
||||
|
||||
def test_build_info_falls_back_to_version_file(monkeypatch):
|
||||
monkeypatch.delenv("APP_VERSION", raising=False)
|
||||
monkeypatch.delenv("BUILD_TIME", raising=False)
|
||||
info = build_info()
|
||||
file_ver = _VERSION_FILE.read_text(encoding="utf-8").strip()
|
||||
assert file_ver in info["version"]
|
||||
assert info["version"].endswith("(local)")
|
||||
assert info["build_time"] == "(local)"
|
||||
+10
@@ -742,6 +742,16 @@ async function refreshHomeRobotSelector() {
|
||||
refreshHomeRobotSelector();
|
||||
startHomePoll();
|
||||
|
||||
// Badge versione build (header). Si aggiorna ad ogni rebuild via build.sh.
|
||||
async function refreshAppVersion() {
|
||||
try {
|
||||
const r = await api('GET', '/api/version');
|
||||
const pill = $('appVersionPill');
|
||||
if (pill) { pill.textContent = `V${r.version}`; pill.title = `build ${r.build_time}`; }
|
||||
} catch {}
|
||||
}
|
||||
refreshAppVersion();
|
||||
|
||||
// ---------- init ----------
|
||||
(async () => {
|
||||
try {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
</nav>
|
||||
<div class="status-strip" id="statusStrip"></div>
|
||||
<span class="active-robot-pill" id="activeRobotPill" title="Robot attualmente collegato">robot: —</span>
|
||||
<span class="active-robot-pill" id="appVersionPill" title="versione build">V—</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
Reference in new issue
Block a user