Files
Link-Log/scripts/release/validate_release.py
T
olaf af5d38a16b
Build LinkLog Development Image / development-image (push) Successful in 10s
Release LinkLog / release (push) Successful in 9s
Bump backend version to 0.1.1 and validate plugin manifest sync
2026-08-27 18:04:49 +02:00

82 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Validate the version and checked-in artifacts for a LinkLog release."""
import json
import re
import sys
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
SETTINGS_PATH = ROOT / 'backend' / 'app' / 'core' / 'config.py'
SIGNED_DIR = ROOT / 'XPI' / 'signed'
MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json'
VERSION_RE = re.compile(r'\d+\.\d+\.\d+')
def fail(message: str) -> None:
raise SystemExit(f'release validation failed: {message}')
def version_key(version: str) -> tuple[int, int, int]:
return tuple(int(part) for part in version.split('.'))
def find_latest_signed_xpi() -> tuple[str, Path]:
candidates = []
for xpi_path in SIGNED_DIR.glob('LinkLog-*.xpi'):
match = re.fullmatch(r'LinkLog-(\d+\.\d+\.\d+)\.xpi', xpi_path.name)
if match:
candidates.append((match.group(1), xpi_path))
if not candidates:
fail(f'no signed plugin artifacts found in {SIGNED_DIR.relative_to(ROOT)}')
return max(candidates, key=lambda candidate: version_key(candidate[0]))
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)
if not VERSION_RE.fullmatch(backend_version):
fail(f'backend version {backend_version} is not a valid three-part version')
extension_version, xpi_path = find_latest_signed_xpi()
source_manifest = json.loads(MANIFEST_PATH.read_text())
if source_manifest.get('version') != extension_version:
fail(
f'webextension/manifest.json version {source_manifest.get("version")!r} does not match '
f'the latest signed XPI version {extension_version!r}'
)
with zipfile.ZipFile(xpi_path) as archive:
try:
packaged_manifest = json.loads(archive.read('manifest.json'))
except KeyError:
fail('signed XPI does not contain manifest.json')
if packaged_manifest.get('version') != extension_version:
fail('signed XPI manifest version does not match its filename')
gecko_settings = packaged_manifest.get('browser_specific_settings', {}).get('gecko', {})
data_permissions = gecko_settings.get('data_collection_permissions')
if data_permissions != {'required': ['websiteActivity'], 'optional': []}:
fail('Firefox data_collection_permissions must require websiteActivity and have no optional categories')
if archive.testzip() is not None:
fail('signed XPI contains a corrupt member')
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:
print(f'backend_version={backend_version}', file=output)
print(f'plugin_version={extension_version}', file=output)
print(f'signed_xpi={signed_xpi}', file=output)
elif len(sys.argv) != 1:
fail('usage: validate_release.py [--github-output <path>]')
print(f'validated LinkLog backend release {backend_version}')
print(f'plugin_version={extension_version}')
print(f'signed_xpi={signed_xpi}')
if __name__ == '__main__':
main()