#!/usr/bin/env python3 """Build static documentation site into ./site for zapier.georgelambert.org.""" from __future__ import annotations import shutil import subprocess from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SITE = ROOT / "site" SECTIONS = [ ( "Start here", [ ("README.md", "Workspace README"), ("docs/04-activate/SETUP-ZAPIER-DEVELOPER.html", "Zapier developer setup (HTML)"), ("docs/04-activate/SETUP-ZAPIER-DEVELOPER.pdf", "Zapier developer setup (PDF)"), ("docs/OPEN.md", "Still open"), ("TODO.md", "Implementation TODO / gates"), ], ), ( "Architecture", [ ("docs/02-architecture/composition.md", "Zappier + middleware composition"), ("docs/02-architecture/overview.md", "Middleware overview"), ("docs/02-architecture/nats-gateway.md", "NATS on NS1"), ("docs/02-architecture/nats-subjects.md", "NATS subjects"), ("docs/02-architecture/archive-nats.md", "Archive NATS, bloom, multi-receipt"), ("docs/02-architecture/modules-and-nats.md", "Module catalog and NATS addresses"), ], ), ( "Product", [ ("docs/01-product/features-a-m.md", "Features a–m"), ("docs/01-product/api-gap-analysis.md", "Verae OpenAPI gaps"), ("docs/01-product/billing-and-keys.md", "Billing and API keys"), ("docs/00-sources/workspace-brief.md", "Original brief"), ("docs/00-sources/provenance.md", "Provenance"), ], ), ( "API specs", [ ("docs/api/middleware-openapi.yaml", "Middleware OpenAPI"), ("packages/zappier/openapi.yaml", "Zappier OpenAPI"), ("docs/00-sources/veraetime-openapi.yaml", "Verae Timestamping OpenAPI snapshot"), ], ), ] def rel(p: Path) -> str: return p.relative_to(ROOT).as_posix() def copy_tree(src: Path, dest: Path, ignore=None) -> None: if not src.exists(): return dest.parent.mkdir(parents=True, exist_ok=True) if src.is_file(): shutil.copy2(src, dest) return shutil.copytree(src, dest, dirs_exist_ok=True, ignore=ignore) def pandoc_md(src: Path, dest: Path, title: str) -> None: dest.parent.mkdir(parents=True, exist_ok=True) css = """ body{font:16px/1.5 Georgia,serif;max-width:48rem;margin:2rem auto;padding:0 1rem;color:#122} a{color:#0b4f8a} code,pre{font-family:ui-monospace,Menlo,monospace;font-size:0.88rem} pre{background:#1b2833;color:#eef;padding:0.8rem;overflow:auto} table{border-collapse:collapse} td,th{border:1px solid #ccc;padding:0.35rem 0.5rem} nav{font:14px system-ui;margin-bottom:1.5rem} """ header = dest.with_suffix(".hdr.html") header.write_text( f"\n\n", encoding="utf-8", ) subprocess.run( [ "pandoc", str(src), "-o", str(dest), "--standalone", f"--metadata=title={title}", f"--include-in-header={header}", ], check=False, capture_output=True, ) header.unlink(missing_ok=True) def walk_md_pdf(prefix: Path) -> list[tuple[str, str]]: items = [] if not prefix.exists(): return items for p in sorted(prefix.rglob("*")): if p.suffix.lower() in {".md", ".pdf", ".html", ".yaml", ".yml", ".svg"} and p.is_file(): items.append((rel(p), p.name)) return items def main() -> None: if SITE.exists(): shutil.rmtree(SITE) SITE.mkdir() copy_tree(ROOT / "docs", SITE / "docs", ignore=shutil.ignore_patterns("_build", ".DS_Store")) sphinx = ROOT / "docs" / "sphinx" / "_build" / "html" if sphinx.exists(): copy_tree(sphinx, SITE / "sphinx") for pkg in ("verae-activate", "verae-zapier", "verae-zapier-middleware", "zappier"): readme = ROOT / "packages" / pkg / "README.md" if readme.exists(): copy_tree(readme, SITE / "packages" / pkg / "README.md") zdocs = ROOT / "packages" / "zappier" / "docs" if zdocs.exists(): copy_tree(zdocs, SITE / "packages" / "zappier" / "docs", ignore=shutil.ignore_patterns("screenshots", "walkthrough", "superpowers")) for name in ("README.md", "TODO.md", "OPEN.md"): src = ROOT / name if not src.exists() and name == "OPEN.md": src = ROOT / "docs" / "OPEN.md" if src.exists(): copy_tree(src, SITE / src.name if src.parent == ROOT else SITE / "docs" / "OPEN.md") copy_tree(ROOT / "docs" / "WORK-LOG.md", SITE / "docs" / "WORK-LOG.md") copy_tree(ROOT / "packages" / "docs-master", SITE / "docs-master") # research markdown + diagrams only r = ROOT / "research" / "zapier" if r.exists(): for fname in ( "README.md", "getting-started.md", "PLATFORM-REFERENCE.md", "FUNCTIONS-REFERENCE.md", "MCP-REFERENCE.md", "LOGIN.md", "docs/zapier-billing.md", ): p = r / fname if p.exists(): copy_tree(p, SITE / "research" / fname) diagrams = r / "docs" / "diagrams" if diagrams.exists(): copy_tree(diagrams, SITE / "research" / "docs" / "diagrams") # HTML versions of markdown for the catalog entries html_pairs = [] for _title, links in SECTIONS: for path, label in links: src = ROOT / path if src.suffix == ".md" and src.exists(): dest = SITE / Path(path).with_suffix(".html") pandoc_md(src, dest, label) html_pairs.append((path, dest.relative_to(SITE).as_posix(), label)) now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") cards = [] for title, links in SECTIONS: lis = [] for path, label in links: src = ROOT / path if not src.exists() and path == "OPEN.md": src = ROOT / "docs" / "OPEN.md" path = "docs/OPEN.md" if not src.exists(): continue href = path html = Path(path).with_suffix(".html").as_posix() if src.suffix == ".md" and (SITE / html).exists(): href = html lis.append(f'
  • {label} {path}
  • ') cards.append(f"

    {title}

    ") extra = [ ("docs/modules/README.md", "Module API sheets"), ("docs/models/README.md", "Data models"), ("sphinx/index.html", "Sphinx HTML"), ("docs/04-activate/SETUP-ZAPIER-DEVELOPER.html", "Setup HTML"), ] extra_lis = "".join( f'
  • {lab}
  • ' for h, lab in extra if (SITE / h).exists() or h.startswith("sphinx") ) index = f""" Verae Time × Zapier documentation
    zapier.georgelambert.org

    Verae Time × Zapier documentation

    Project reference: setup checklist, architecture (including NATS job wait and WORM archive fan-out), module API sheets, models, OpenAPI, and research notes. Generated {now}.

    {''.join(cards)}

    Catalogs

    """ (SITE / "index.html").write_text(index, encoding="utf-8") print(f"site built at {SITE} ({sum(1 for _ in SITE.rglob('*') if _.is_file())} files)") if __name__ == "__main__": main()