Добавлено автоверсионирование SemVer и версия v0.1.1 в футере.
This commit is contained in:
+8
-2
@@ -14,6 +14,7 @@ from app.config import get_settings
|
||||
from app.db import SessionLocal
|
||||
from app.services.settings_service import get_or_create_settings
|
||||
from app.services.storage import storage
|
||||
from app.version import get_app_version, get_app_version_label
|
||||
|
||||
OPENAPI_TAGS = [
|
||||
{
|
||||
@@ -73,9 +74,10 @@ def custom_openapi(app: FastAPI):
|
||||
def create_app() -> FastAPI:
|
||||
settings = get_settings()
|
||||
docs_on = settings.is_docs_enabled
|
||||
app_version = get_app_version()
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
version="0.1.0",
|
||||
version=app_version,
|
||||
description=(
|
||||
"Wrapped — zero-knowledge one-time encrypted drop.\n\n"
|
||||
"Public wrap APIs are anonymous. Admin APIs require HTTP Basic."
|
||||
@@ -97,10 +99,14 @@ def create_app() -> FastAPI:
|
||||
app.include_router(admin.api_router)
|
||||
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
templates.env.globals["app_version"] = app_version
|
||||
templates.env.globals["app_version_label"] = get_app_version_label()
|
||||
admin.templates.env.globals["app_version"] = app_version
|
||||
admin.templates.env.globals["app_version_label"] = get_app_version_label()
|
||||
|
||||
@app.get("/health", tags=["System"], summary="Health check", include_in_schema=docs_on)
|
||||
async def health():
|
||||
return {"status": "ok", "service": settings.app_name}
|
||||
return {"status": "ok", "service": settings.app_name, "version": app_version}
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
async def index(request: Request):
|
||||
|
||||
@@ -886,6 +886,24 @@ html[data-theme="light"] .footer-pillars li {
|
||||
margin: 0.85rem 0 0;
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.footer-copy-left {
|
||||
min-width: 0;
|
||||
}
|
||||
.footer-copy-version {
|
||||
margin-left: auto;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--muted);
|
||||
opacity: 0.9;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.footer-copy a {
|
||||
color: var(--accent-2);
|
||||
|
||||
@@ -66,8 +66,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
<p class="footer-copy">
|
||||
© <span data-i18n="footer.copy.author">Сергей Антропов</span>
|
||||
· <a href="https://devops.org.ru" target="_blank" rel="noopener noreferrer">devops.org.ru</a>
|
||||
<span class="footer-copy-left">
|
||||
© <span data-i18n="footer.copy.author">Сергей Антропов</span>
|
||||
· <a href="https://devops.org.ru" target="_blank" rel="noopener noreferrer">devops.org.ru</a>
|
||||
</span>
|
||||
<span class="footer-copy-version" title="Version">{{ app_version_label | default('v0.1.0') }}</span>
|
||||
</p>
|
||||
</footer>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
_VERSION_FILE = Path(__file__).resolve().parents[1] / "VERSION"
|
||||
_PROJECT_ROOT = _VERSION_FILE.parent
|
||||
_PYPROJECT_FILE = _PROJECT_ROOT / "pyproject.toml"
|
||||
_CHART_FILE = _PROJECT_ROOT / "helm" / "wrapped" / "Chart.yaml"
|
||||
_VALUES_FILE = _PROJECT_ROOT / "helm" / "wrapped" / "values.yaml"
|
||||
_DEFAULT_VERSION = "0.1.0"
|
||||
_VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
|
||||
|
||||
|
||||
def _read_version_file(path: Path) -> str | None:
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8").strip()
|
||||
except OSError:
|
||||
return None
|
||||
if _VERSION_RE.match(text):
|
||||
return text
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_app_version() -> str:
|
||||
for path in (
|
||||
_VERSION_FILE,
|
||||
Path("/app/VERSION"),
|
||||
Path.cwd() / "VERSION",
|
||||
):
|
||||
found = _read_version_file(path)
|
||||
if found:
|
||||
return found
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
|
||||
meta = version("wrapped")
|
||||
if _VERSION_RE.match(meta):
|
||||
return meta
|
||||
except Exception:
|
||||
pass
|
||||
return _DEFAULT_VERSION
|
||||
|
||||
|
||||
def get_app_version_label() -> str:
|
||||
return f"v{get_app_version()}"
|
||||
|
||||
|
||||
def parse_version(value: str) -> tuple[int, int, int]:
|
||||
match = _VERSION_RE.match(value.strip())
|
||||
if not match:
|
||||
raise ValueError(f"Invalid semantic version: {value!r}")
|
||||
return int(match.group(1)), int(match.group(2)), int(match.group(3))
|
||||
|
||||
|
||||
def format_version(major: int, minor: int, patch: int) -> str:
|
||||
return f"{major}.{minor}.{patch}"
|
||||
|
||||
|
||||
def clear_version_cache() -> None:
|
||||
get_app_version.cache_clear()
|
||||
|
||||
|
||||
def write_project_version(version: str) -> None:
|
||||
parse_version(version)
|
||||
_VERSION_FILE.write_text(f"{version}\n", encoding="utf-8")
|
||||
|
||||
pyproject = _PYPROJECT_FILE.read_text(encoding="utf-8")
|
||||
pyproject, count = re.subn(
|
||||
r'^version = ".*"$',
|
||||
f'version = "{version}"',
|
||||
pyproject,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
if count != 1:
|
||||
raise RuntimeError("Failed to update pyproject.toml version")
|
||||
_PYPROJECT_FILE.write_text(pyproject, encoding="utf-8")
|
||||
|
||||
chart = _CHART_FILE.read_text(encoding="utf-8")
|
||||
chart, chart_count = re.subn(
|
||||
r"^version: .*$",
|
||||
f"version: {version}",
|
||||
chart,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
chart, app_count = re.subn(
|
||||
r'^appVersion: ".*"$',
|
||||
f'appVersion: "{version}"',
|
||||
chart,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
if chart_count != 1 or app_count != 1:
|
||||
raise RuntimeError("Failed to update Chart.yaml version")
|
||||
_CHART_FILE.write_text(chart, encoding="utf-8")
|
||||
|
||||
values = _VALUES_FILE.read_text(encoding="utf-8")
|
||||
values, values_count = re.subn(
|
||||
r'^(\s*tag:\s*)".*"$',
|
||||
rf'\1"{version}"',
|
||||
values,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
if values_count != 1:
|
||||
raise RuntimeError("Failed to update helm values.yaml image.tag")
|
||||
_VALUES_FILE.write_text(values, encoding="utf-8")
|
||||
|
||||
clear_version_cache()
|
||||
|
||||
|
||||
def bump_patch_version() -> str:
|
||||
major, minor, patch = parse_version(get_app_version())
|
||||
version = format_version(major, minor, patch + 1)
|
||||
write_project_version(version)
|
||||
return version
|
||||
|
||||
|
||||
def bump_minor_version() -> str:
|
||||
major, minor, patch = parse_version(get_app_version())
|
||||
version = format_version(major, minor + 1, 0)
|
||||
write_project_version(version)
|
||||
return version
|
||||
Reference in New Issue
Block a user