Build legal manifest / build-manifest (push) Failing after 3m37s
scripts/build-manifest.py: minden projekt-almappa .md dokumentumát HTML+PDF-re renderel (pandoc → HTML, WeasyPrint → PDF, full_fonts=True — lásd HRF-34, ugyanaz a determinizmus-óvintézkedés), és dist/legal-manifest.json-t ír soronként: version (utolsó, a fájlt ténylegesen érintő commit rövid hashe, NEM repo HEAD — HT-15), effective_from (a dokumentum saját "Hatálybalépés napja" sorából parse-olva, null ha még [dátum] placeholder), content_hash (a forrás git blob hashe, átmeneti megoldás, lásd a script docstringjét). .gitea/workflows/legal-manifest.yml: push/manual trigger, teljes (nem sekély) checkout, pandoc+WeasyPrint telepítve explicit lépésként (nem egyedi runner-image — így a CI-környezet a workflow fájlból látható és reprodukálható). Helyileg tesztelve: effective_from-parse, git_blob_hash, last_commit_hash, pandoc HTML-render mind helyesen működik. A WeasyPrint-lépés Windows-on nem tesztelhető (natív libek, lásd HRF-34) — a CI-futtatás bizonyítja.
131 lines
5.0 KiB
Python
131 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Builds dist/legal-manifest.json (HT-14): renders each project's markdown legal documents
|
|
to HTML+PDF and computes a content_hash, producing the single canonical manifest every
|
|
consumer backend imports at deploy time (HT-15) instead of hashing independently.
|
|
|
|
content_hash source (interim, per the HT-3 "Design döntés" comment): the source markdown's
|
|
git blob hash (`git hash-object`), NOT the rendered artifact's hash yet — that requires a
|
|
proven byte-for-byte-deterministic render, which this pandoc+WeasyPrint combination hasn't
|
|
been tested for (ResidentFirst's HRF-34 proved a *different* render pipeline deterministic,
|
|
not this one). Switching later doesn't invalidate old receipts, since a receipt is bound to
|
|
document_version, not content_hash alone.
|
|
|
|
effective_from source: parsed from each document's own "Hatálybalépés napja: <dátum>" line —
|
|
the one human-controlled field carrying the legally relevant date. A document whose date is
|
|
still the unfilled "[dátum]" placeholder gets effective_from: null (not yet effective)
|
|
rather than an invented date.
|
|
|
|
version: the short hash of the last commit that actually touched this file (NOT repo HEAD —
|
|
see HT-15's belépő ellenőrzés) — same convention as the two consumer sync scripts already
|
|
use for document_version.
|
|
|
|
Usage: python3 scripts/build-manifest.py
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
OUTPUT_DIR = REPO_ROOT / "dist"
|
|
|
|
HUNGARIAN_MONTHS = {
|
|
"január": 1, "február": 2, "március": 3, "április": 4, "május": 5, "június": 6,
|
|
"július": 7, "augusztus": 8, "szeptember": 9, "október": 10, "november": 11, "december": 12,
|
|
}
|
|
|
|
# e.g. "*Hatálybalépés napja: 2026. július 17. · Verzió: 1.0*"
|
|
DATE_LINE_RE = re.compile(r"Hatálybalépés napja:\s*(.+?)\s*·\s*Verzió:")
|
|
HU_DATE_RE = re.compile(r"(\d{4})\.\s*([a-záéíóöőúüű]+)\s*(\d{1,2})\.")
|
|
|
|
|
|
def parse_effective_from(markdown: str) -> str | None:
|
|
m = DATE_LINE_RE.search(markdown)
|
|
if not m:
|
|
return None
|
|
hu = HU_DATE_RE.match(m.group(1).strip())
|
|
if not hu:
|
|
return None # still the "[dátum]" placeholder, or an unrecognized format
|
|
year, month_name, day = hu.groups()
|
|
month = HUNGARIAN_MONTHS.get(month_name.lower())
|
|
if month is None:
|
|
return None
|
|
return f"{int(year):04d}-{month:02d}-{int(day):02d}"
|
|
|
|
|
|
def _git(*args: str) -> str:
|
|
return subprocess.run(
|
|
["git", *args], cwd=REPO_ROOT, check=True, capture_output=True, text=True
|
|
).stdout.strip()
|
|
|
|
|
|
def git_blob_hash(path: Path) -> str:
|
|
return _git("hash-object", str(path))
|
|
|
|
|
|
def last_commit_hash(path: Path) -> str:
|
|
relative = path.relative_to(REPO_ROOT)
|
|
# NOTE: requires full history (fetch-depth: 0 in CI) — a shallow clone would silently
|
|
# resolve every file to the same single available commit. See HT-15.
|
|
return _git("log", "-1", "--format=%h", "--", str(relative))
|
|
|
|
|
|
def render_html(markdown_path: Path, html_path: Path) -> None:
|
|
subprocess.run(
|
|
["pandoc", str(markdown_path), "-o", str(html_path), "--standalone", "--metadata", "lang=hu"],
|
|
check=True,
|
|
)
|
|
|
|
|
|
def render_pdf(html_path: Path, pdf_path: Path) -> None:
|
|
from weasyprint import HTML
|
|
|
|
# full_fonts=True: WeasyPrint's default font subsetting is not byte-for-byte
|
|
# deterministic (confirmed empirically while building HRF-34) — full embedding is.
|
|
HTML(filename=str(html_path)).write_pdf(str(pdf_path), full_fonts=True)
|
|
|
|
|
|
def build_manifest_for_project(project_dir: Path) -> dict:
|
|
manifest = {}
|
|
for md_path in sorted(project_dir.glob("*.md")):
|
|
doc_type = md_path.stem
|
|
markdown = md_path.read_text(encoding="utf-8")
|
|
|
|
out_dir = OUTPUT_DIR / project_dir.name
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
html_path = out_dir / f"{doc_type}.html"
|
|
pdf_path = out_dir / f"{doc_type}.pdf"
|
|
|
|
render_html(md_path, html_path)
|
|
render_pdf(html_path, pdf_path)
|
|
|
|
manifest[doc_type] = {
|
|
"version": last_commit_hash(md_path),
|
|
"effective_from": parse_effective_from(markdown),
|
|
"content_hash": git_blob_hash(md_path),
|
|
"html": f"{project_dir.name}/{doc_type}.html",
|
|
"pdf": f"{project_dir.name}/{doc_type}.pdf",
|
|
}
|
|
return manifest
|
|
|
|
|
|
def main() -> None:
|
|
OUTPUT_DIR.mkdir(exist_ok=True)
|
|
full_manifest = {}
|
|
for project_dir in sorted(REPO_ROOT.iterdir()):
|
|
if not project_dir.is_dir() or project_dir.name.startswith(".") or project_dir.name in ("dist", "scripts"):
|
|
continue
|
|
if not list(project_dir.glob("*.md")):
|
|
continue
|
|
print(f"Renderelés: {project_dir.name}/")
|
|
full_manifest[project_dir.name] = build_manifest_for_project(project_dir)
|
|
|
|
manifest_path = OUTPUT_DIR / "legal-manifest.json"
|
|
manifest_path.write_text(json.dumps(full_manifest, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
print(f"Manifest írva: {manifest_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|