"""docs/diagrams/manifest.json 을 산출물 실제 바이트에서 다시 쓴다. 수신증의 단일 작성자다. PowerShell 과 Python 이 각자 JSON 을 쓰면 들여쓰기·키 순서가 갈려 매니페스트가 왔다 갔다 하므로, rebuild-diagrams.ps1 도 이 스크립트를 부른다. 단독 실행도 된다: python scripts/update_diagram_manifest.py """ from __future__ import annotations import hashlib import io import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] DIAGRAM_DIR = ROOT / "docs" / "diagrams" # (타입, 파일 basename). 다이어그램을 추가하면 여기와 tests/test_diagram_sync.py 의 # NODE_COLLECTION 을 함께 고친다. DIAGRAMS = [ ("architecture", "system-architecture"), ("dataflow", "evidence-dataflow"), ("workflow", "operator-review-workflow"), ("workflow", "review-guide"), ("lifecycle", "submission-lifecycle"), ] NOTE = ( "archify deliver 는 바이트 결정적이다. 이 수신증은 tests/test_diagram_sync.py 가 " "강제한다 - HTML 을 직접 고치거나 spec 만 고치고 재생성을 잊으면 커밋 게이트가 막는다. " "갱신은 손으로 하지 말고 scripts/rebuild-diagrams.ps1 로 한다." ) def _sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def _generator_stamp(html: Path) -> str: match = re.search( r' int: entries = [] for diagram_type, base in DIAGRAMS: spec = DIAGRAM_DIR / f"{base}.{diagram_type}.json" html = DIAGRAM_DIR / f"{base}.html" for path in (spec, html): if not path.exists(): raise SystemExit(f"없는 파일: {path}") entries.append( { "type": diagram_type, "spec": spec.name, "html": html.name, "specSha256": _sha256(spec), "htmlSha256": _sha256(html), "htmlBytes": html.stat().st_size, } ) generators = {_generator_stamp(DIAGRAM_DIR / e["html"]) for e in entries} if len(generators) != 1: raise SystemExit( f"산출물마다 generator 가 다르다: {sorted(generators)} - 전부 재생성해야 한다." ) manifest = { "note": NOTE, "generator": generators.pop(), "diagrams": entries, } target = DIAGRAM_DIR / "manifest.json" with io.open(target, "w", encoding="utf-8", newline="\n") as handle: json.dump(manifest, handle, ensure_ascii=False, indent=2) handle.write("\n") # 비ASCII 는 stdout 에 넣지 않는다 (콘솔이 cp949 라 UnicodeEncodeError 로 죽은 이력). print("manifest updated: %d diagrams, generator %s" % (len(entries), manifest["generator"])) for entry in entries: print(" %-30s %s.. %d B" % (entry["html"], entry["htmlSha256"][:8], entry["htmlBytes"])) return 0 if __name__ == "__main__": sys.exit(main())