96 lines
2.9 KiB
Python
Executable File
96 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Synchronize frontend package versions with pyproject.toml.
|
|
|
|
pyproject.toml is the single source of truth for the project version.
|
|
Run this script before building the frontend or deploying so the two
|
|
files never drift apart.
|
|
|
|
Usage:
|
|
scripts/sync_version.py # update package.json and package-lock.json
|
|
scripts/sync_version.py --check # exit 1 if they already differ
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
PYPROJECT = ROOT / "pyproject.toml"
|
|
PACKAGE_JSON = ROOT / "frontend" / "web" / "package.json"
|
|
PACKAGE_LOCK = ROOT / "frontend" / "web" / "package-lock.json"
|
|
|
|
|
|
_VERSION_RE = re.compile(r'^version\s*=\s*"([^"]+)"', re.MULTILINE)
|
|
|
|
|
|
def read_pyproject_version() -> str:
|
|
text = PYPROJECT.read_text(encoding="utf-8")
|
|
match = _VERSION_RE.search(text)
|
|
if not match:
|
|
raise RuntimeError(f"could not find version= in {PYPROJECT}")
|
|
return match.group(1)
|
|
|
|
|
|
def package_versions_match(version: str) -> bool:
|
|
package_data = json.loads(PACKAGE_JSON.read_text(encoding="utf-8"))
|
|
lock_data = json.loads(PACKAGE_LOCK.read_text(encoding="utf-8"))
|
|
return (
|
|
package_data.get("version") == version
|
|
and lock_data.get("version") == version
|
|
and lock_data.get("packages", {}).get("", {}).get("version") == version
|
|
)
|
|
|
|
|
|
def write_package_versions(version: str) -> bool:
|
|
"""Update package.json and package-lock.json. Returns True if changed."""
|
|
if package_versions_match(version):
|
|
return False
|
|
|
|
data = json.loads(PACKAGE_JSON.read_text(encoding="utf-8"))
|
|
data["version"] = version
|
|
PACKAGE_JSON.write_text(
|
|
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
lock_data = json.loads(PACKAGE_LOCK.read_text(encoding="utf-8"))
|
|
lock_data["version"] = version
|
|
lock_data.setdefault("packages", {}).setdefault("", {})["version"] = version
|
|
PACKAGE_LOCK.write_text(
|
|
json.dumps(lock_data, indent=2, ensure_ascii=False) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
return True
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--check", action="store_true",
|
|
help="Exit with status 1 if versions already match (CI-friendly).",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
version = read_pyproject_version()
|
|
if args.check:
|
|
if not package_versions_match(version):
|
|
print(f"OUT_OF_SYNC pyproject={version} frontend package versions differ")
|
|
return 1
|
|
print(f"OK version={version}")
|
|
return 0
|
|
|
|
changed = write_package_versions(version)
|
|
if changed:
|
|
print(f"synced frontend package versions -> {version}")
|
|
else:
|
|
print(f"already in sync: {version}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|