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/
|
COPY src/ src/
|
||||||
RUN uv sync --no-dev
|
RUN uv sync --no-dev
|
||||||
|
|
||||||
COPY config.yaml ./
|
COPY config.yaml VERSION ./
|
||||||
COPY web/ web/
|
COPY web/ web/
|
||||||
|
|
||||||
ENV PRESA_MANO_CONFIG=/app/config.yaml
|
ENV PRESA_MANO_CONFIG=/app/config.yaml
|
||||||
ENV PRESA_MANO_DATA=/app/data
|
ENV PRESA_MANO_DATA=/app/data
|
||||||
ENV PRESA_MANO_PORT=8890
|
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
|
EXPOSE 8890
|
||||||
|
|
||||||
# Shell form so $PRESA_MANO_PORT viene espanso a runtime: cambi la porta in
|
# 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 .clients import G1StatusError
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
|
from .version import build_info
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||||
|
|
||||||
@@ -102,6 +103,11 @@ def health() -> dict:
|
|||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/version", tags=["meta"])
|
||||||
|
def version() -> dict:
|
||||||
|
return build_info()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", include_in_schema=False)
|
@app.get("/", include_in_schema=False)
|
||||||
def index() -> HTMLResponse:
|
def index() -> HTMLResponse:
|
||||||
idx = WEB_DIR / "index.html"
|
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();
|
refreshHomeRobotSelector();
|
||||||
startHomePoll();
|
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 ----------
|
// ---------- init ----------
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="status-strip" id="statusStrip"></div>
|
<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="activeRobotPill" title="Robot attualmente collegato">robot: —</span>
|
||||||
|
<span class="active-robot-pill" id="appVersionPill" title="versione build">V—</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
Reference in new issue
Block a user