102 lines
3.7 KiB
Python
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())
|