Versioning consistency and bump both the XPI and backend to version 0.2.0
Build LinkLog Development Image / development-image (push) Successful in 11s
Release LinkLog / release (push) Failing after 2s

This commit is contained in:
Olaf
2026-08-28 10:38:27 +02:00
parent 5696c89dee
commit 9bf9c94dd6
16 changed files with 241 additions and 21 deletions
+63 -6
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Validate the version and checked-in artifacts for a LinkLog release."""
import hashlib
import json
import re
import sys
@@ -9,9 +10,12 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
SETTINGS_PATH = ROOT / 'backend' / 'app' / 'core' / 'config.py'
VERSION_FILE = ROOT / 'frontend' / 'version.json'
SIGNED_DIR = ROOT / 'XPI' / 'signed'
MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json'
UPDATES_PATH = ROOT / 'webextension' / 'updates.json'
ABOUT_TEMPLATE_PATH = ROOT / 'frontend' / 'templates' / 'about.html'
RAW_BASE_URL = 'https://git.kolkman.org/olaf/Link-Log/raw/branch/main'
VERSION_RE = re.compile(r'\d+\.\d+\.\d+')
@@ -34,12 +38,62 @@ def find_latest_signed_xpi() -> tuple[str, Path]:
return max(candidates, key=lambda candidate: version_key(candidate[0]))
def validate_self_update(source_manifest: dict, extension_version: str, xpi_path: Path) -> None:
gecko_settings = source_manifest.get('browser_specific_settings', {}).get('gecko', {})
addon_id = gecko_settings.get('id')
strict_min_version = gecko_settings.get('strict_min_version')
if not addon_id:
fail('webextension/manifest.json is missing browser_specific_settings.gecko.id')
updates_data = json.loads(UPDATES_PATH.read_text())
addon_entry = updates_data.get('addons', {}).get(addon_id)
if not addon_entry:
fail(f'{UPDATES_PATH.relative_to(ROOT)} has no entry for add-on id {addon_id!r}')
entry = next((u for u in addon_entry.get('updates', []) if u.get('version') == extension_version), None)
if entry is None:
fail(
f'{UPDATES_PATH.relative_to(ROOT)} has no update entry for version {extension_version!r}; '
'add one alongside the signed XPI so the self-update mechanism can find it'
)
expected_link = f'{RAW_BASE_URL}/{xpi_path.relative_to(ROOT).as_posix()}'
if entry.get('update_link') != expected_link:
fail(
f'{UPDATES_PATH.relative_to(ROOT)} update_link {entry.get("update_link")!r} does not match '
f'the expected raw signed XPI URL {expected_link!r}'
)
with xpi_path.open('rb') as xpi_file:
expected_hash = f'sha256:{hashlib.file_digest(xpi_file, "sha256").hexdigest()}'
if entry.get('update_hash') != expected_hash:
fail(
f'{UPDATES_PATH.relative_to(ROOT)} update_hash {entry.get("update_hash")!r} does not match '
f'the SHA-256 hash of {xpi_path.relative_to(ROOT)}'
)
entry_min_version = entry.get('applications', {}).get('gecko', {}).get('strict_min_version')
if entry_min_version != strict_min_version:
fail(
f'{UPDATES_PATH.relative_to(ROOT)} applications.gecko.strict_min_version {entry_min_version!r} '
f'does not match webextension/manifest.json strict_min_version {strict_min_version!r}'
)
def validate_about_plugin_link(xpi_path: Path) -> None:
expected_link = f'{RAW_BASE_URL}/{xpi_path.relative_to(ROOT).as_posix()}'
if expected_link not in ABOUT_TEMPLATE_PATH.read_text():
fail(
f'{ABOUT_TEMPLATE_PATH.relative_to(ROOT)} does not link to the latest signed XPI '
f'{xpi_path.relative_to(ROOT)}'
)
def main() -> None:
settings = SETTINGS_PATH.read_text()
match = re.search(r"version: str = os\.getenv\('LINKLOG_VERSION', '([^']+)'\)", settings)
if not match:
fail('backend version default could not be found')
backend_version = match.group(1)
version_data = json.loads(VERSION_FILE.read_text())
backend_version = version_data.get('version')
if not backend_version:
fail(f'version could not be found in {VERSION_FILE.relative_to(ROOT)}')
if not VERSION_RE.fullmatch(backend_version):
fail(f'backend version {backend_version} is not a valid three-part version')
@@ -64,6 +118,9 @@ def main() -> None:
if archive.testzip() is not None:
fail('signed XPI contains a corrupt member')
validate_self_update(source_manifest, extension_version, xpi_path)
validate_about_plugin_link(xpi_path)
signed_xpi = xpi_path.relative_to(ROOT)
if len(sys.argv) == 3 and sys.argv[1] == '--github-output':
with Path(sys.argv[2]).open('a') as output: