Files
heros_web/scripts/promote-unit-sprite-approval.py

102 lines
3.7 KiB
Python

from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
MANIFEST_PATH = ROOT / "docs" / "unit-sprite-rework-manifest.json"
AUDIT_PATH = ROOT / "docs" / "unit-sprite-rework-audit.json"
def load_json(path: Path) -> dict[str, Any]:
if not path.exists():
raise FileNotFoundError(f"missing required file: {path.relative_to(ROOT)}")
return json.loads(path.read_text(encoding="utf-8"))
def write_manifest(manifest: dict[str, Any]) -> None:
MANIFEST_PATH.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def audit_row(audit: dict[str, Any], unit: str) -> dict[str, Any] | None:
return next((row for row in audit.get("rows", []) if row.get("stem") == unit), None)
def require_ok_sample(unit: str, audit: dict[str, Any]) -> None:
row = audit_row(audit, unit)
if row is None:
raise ValueError(f"{unit}: missing from audit")
if row.get("review_stage") != "pending-approval":
raise ValueError(f"{unit}: expected pending-approval stage, got {row.get('review_stage')}")
if row.get("base", {}).get("status") != "ok" or row.get("actions", {}).get("status") != "ok":
raise ValueError(f"{unit}: base/actions audit must both be ok before promotion")
def promote(manifest: dict[str, Any], unit: str, expected_version: str | None) -> dict[str, Any]:
pending = list(manifest.get("pendingApproval", []))
approved = list(manifest.get("approvedBaseline", []))
candidates = manifest.get("currentApprovalCandidate", {})
candidate = candidates.get(unit, {})
if unit not in pending:
raise ValueError(f"{unit}: not listed in pendingApproval")
if unit in approved:
raise ValueError(f"{unit}: already listed in approvedBaseline")
if expected_version and candidate.get("version") != expected_version:
raise ValueError(
f"{unit}: expected candidate version {expected_version}, got {candidate.get('version')}"
)
manifest["pendingApproval"] = [item for item in pending if item != unit]
manifest["approvedBaseline"] = sorted([*approved, unit])
if isinstance(candidates, dict):
candidates.pop(unit, None)
manifest["currentApprovalCandidate"] = candidates
return manifest
def main() -> int:
parser = argparse.ArgumentParser(
description="Promote a user-approved pending unit sprite sample into approvedBaseline."
)
parser.add_argument("--unit", required=True, help="Unit texture key, for example unit-shu-infantry.")
parser.add_argument("--expected-version", help="Optional candidate version guard, for example v3.")
parser.add_argument(
"--dry-run",
action="store_true",
help="Validate and print the resulting manifest status without writing the file.",
)
args = parser.parse_args()
try:
manifest = load_json(MANIFEST_PATH)
audit = load_json(AUDIT_PATH)
require_ok_sample(args.unit, audit)
next_manifest = promote(manifest, args.unit, args.expected_version)
except Exception as exc:
print(f"Unit sprite approval promotion: FAILED\n- {exc}")
return 1
approved_count = len(next_manifest.get("approvedBaseline", []))
pending_count = len(next_manifest.get("pendingApproval", []))
if args.dry_run:
print("Unit sprite approval promotion: DRY RUN OK")
else:
write_manifest(next_manifest)
print("Unit sprite approval promotion: OK")
print(f"- promoted: {args.unit}")
print(f"- approved baseline: {approved_count}")
print(f"- pending approval: {pending_count}")
return 0
if __name__ == "__main__":
sys.exit(main())