mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-04-25 16:52:06 +00:00
123 lines
3.8 KiB
Python
123 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Version resolution utilities for MeshCore Bot.
|
|
|
|
Centralizes runtime version lookup so bot command output, web viewer, and
|
|
services all report consistent version information.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def _normalize_tag(value: str | None) -> str | None:
|
|
if not value:
|
|
return None
|
|
value = value.strip()
|
|
if not value:
|
|
return None
|
|
return value if value.startswith("v") else f"v{value}"
|
|
|
|
|
|
def _safe_git_run(repo_root: Path, args: list[str]) -> str | None:
|
|
try:
|
|
result = subprocess.run(
|
|
["git", "-C", str(repo_root)] + args,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
)
|
|
if result.returncode != 0:
|
|
return None
|
|
out = (result.stdout or "").strip()
|
|
return out or None
|
|
except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError, OSError):
|
|
return None
|
|
|
|
|
|
def _read_version_file(repo_root: Path) -> str | None:
|
|
version_file = repo_root / ".version_info"
|
|
if not version_file.is_file():
|
|
return None
|
|
try:
|
|
with open(version_file, encoding="utf-8") as fh:
|
|
data = json.load(fh)
|
|
version = data.get("installer_version") or data.get("tag")
|
|
return _normalize_tag(version)
|
|
except (OSError, json.JSONDecodeError, AttributeError):
|
|
return None
|
|
|
|
|
|
def _read_pyproject_version(repo_root: Path) -> str | None:
|
|
pyproject_path = repo_root / "pyproject.toml"
|
|
if not pyproject_path.is_file():
|
|
return None
|
|
try:
|
|
text = pyproject_path.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return None
|
|
|
|
in_project_section = False
|
|
for raw_line in text.splitlines():
|
|
line = raw_line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if line.startswith("[") and line.endswith("]"):
|
|
in_project_section = line == "[project]"
|
|
continue
|
|
if not in_project_section:
|
|
continue
|
|
match = re.match(r'version\s*=\s*"([^"]+)"', line)
|
|
if match:
|
|
return _normalize_tag(match.group(1))
|
|
return None
|
|
|
|
|
|
def resolve_runtime_version(repo_root: Path | str) -> dict[str, str | None]:
|
|
"""Resolve version metadata and a single runtime display value.
|
|
|
|
Returns a dict with:
|
|
- baked: release-like version from env/.version_info/pyproject (v-prefixed)
|
|
- tag: same as baked for template compatibility
|
|
- branch, commit, date: git metadata when available
|
|
- display: final runtime version string
|
|
"""
|
|
root = Path(repo_root).resolve()
|
|
|
|
env_version = _normalize_tag(os.environ.get("MESHCORE_BOT_VERSION", "").strip())
|
|
file_version = _read_version_file(root)
|
|
pyproject_version = _read_pyproject_version(root)
|
|
baked = env_version or file_version or pyproject_version
|
|
|
|
branch = _safe_git_run(root, ["rev-parse", "--abbrev-ref", "HEAD"])
|
|
commit = _safe_git_run(root, ["rev-parse", "--short", "HEAD"])
|
|
date_raw = _safe_git_run(root, ["show", "-s", "--format=%ci", "HEAD"])
|
|
date = None
|
|
if date_raw:
|
|
# %ci format is "YYYY-MM-DD HH:MM:SS +TZ"; keep date only.
|
|
date = date_raw.split()[0] if " " in date_raw else date_raw
|
|
|
|
display: str | None
|
|
if branch and branch != "main" and commit:
|
|
display = f"{branch}-{commit}"
|
|
elif branch == "main" and baked:
|
|
display = baked
|
|
else:
|
|
# Fallbacks for non-git/runtime-constrained environments.
|
|
display = baked or (f"{branch}-{commit}" if branch and commit else None) or "unknown"
|
|
|
|
return {
|
|
"baked": baked,
|
|
"tag": baked,
|
|
"branch": branch,
|
|
"commit": commit,
|
|
"date": date,
|
|
"display": display,
|
|
}
|
|
|