#!/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"), ("docs/02-architecture/tree-nodes.md", "Tree nodes and bulk Merkle summaries"), ("docs/02-architecture/fleet.md", "Fleet replica floors and monitor"), ("packages/overview/README.md", "System overview (TOC)"), ("packages/overview/INDEX.md", "Documentation index"), ], ), ( "User guide (signup → lookup)", [ ("packages/zapier-user-docs/README.md", "User guide index"), ("packages/zapier-user-docs/02-signup-zappier-portal.md", "Sign up"), ("packages/zapier-user-docs/04-register-a-hash.md", "Register a SHA-256"), ("packages/zapier-user-docs/06-lookup-central-chain.md", "Central chain lookup"), ("packages/zapier-user-docs/09-lookup-tree-nodes.md", "Tree-node lookup"), ], ), ( "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", "verae-request-splitter", "verae-archive-worm", "verae-archive-aggregator", "verae-tree-node", "verae-zapier-simulator", "zapier-user-docs", "verae-fleet", "overview", "verae-nats-process", "docs-master", ): 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") copy_tree(ROOT / "packages" / "overview", SITE / "overview") copy_tree(ROOT / "packages" / "zapier-user-docs", SITE / "user-docs", ignore=shutil.ignore_patterns("node_modules")) sim_pub = ROOT / "packages" / "verae-zapier-simulator" / "public" / "index.html" if sim_pub.exists(): copy_tree(sim_pub, SITE / "simulator" / "index.html") # 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"), ("docs-master/README.md", "Master summaries (docs-master)"), ("docs-master/MESSAGE-FLOWS.md", "Numbered message flows"), ("docs-master/modules-and-nats.md", "NATS address table"), ("user-docs/README.md", "User guide index"), ("packages/verae-zapier-simulator/README.md", "Simulator README"), ] git_repos = [ ("master-zapier-plan-draft", "Monorepo (this workspace)"), ("zappier-edge", "Metered HTTPS edge"), ("verae-middleware", "Zapier-facing HTTP + NATS workers"), ("verae-zapier-app", "Full Zapier Platform app"), ("verae-activate", "Activate-now Zapier app"), ("verae-request-splitter", "Hash vs attachment splitter"), ("verae-archive-worm", "Bloom-filtered WORM archive node"), ("verae-archive-aggregator", "Archive reply aggregator"), ("zapier-docs-master", "Master summaries and NATS contracts"), ("verae-tree-node", "Merkle leaf proofs (bulk summaries)"), ("verae-zapier-simulator", "Zapier interface + trace console"), ("zapier-user-docs", "Signup-to-usage user guide"), ("verae-fleet", "Service catalog, replica floors, monitor"), ("overview", "High-level system overview"), ("verae-nats-process", "Template for a new NATS address"), ] git_lis = "".join( f'
  • {name} ' f'{desc}
  • ' for name, desc in git_repos ) 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)}

    Git repositories (Forgejo)

    Clone: ssh://git@git.georgelambert.org:2223/marchon/<name>.git (SSH port 2223). Branches main and master.

    Master module docs

    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()