master-zapier-plan-draft/scripts/build-docs-site.py
George Lambert f3dc0e6eee
Some checks are pending
offline / test (push) Waiting to run
Add Zapier interface simulator, tree-node Merkle lookups, and user docs
In-process trace console validates hops, faults, recoveries, and
suggested changes before zapier-platform push. User guide covers
signup through central-chain and bulk-summary tree-node hash lookup.
2026-09-11 12:58:03 -04:00

321 lines
13 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"),
],
),
(
"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 am"),
("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"<nav><a href=\"/\">zapier.georgelambert.org</a></nav>\n<style>{css}</style>\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",
"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" / "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'<li><a href="{href}">{label}</a> <span class="path">{path}</span></li>')
cards.append(f"<section><h2>{title}</h2><ul>{''.join(lis)}</ul></section>")
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"),
]
git_lis = "".join(
f'<li><a href="https://git.georgelambert.org/marchon/{name}">{name}</a> '
f'<span class="path">{desc}</span></li>'
for name, desc in git_repos
)
extra_lis = "".join(
f'<li><a href="{h}">{lab}</a></li>' for h, lab in extra if (SITE / h).exists() or h.startswith("sphinx")
)
index = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Verae Time × Zapier documentation</title>
<style>
:root {{ --ink:#12202c; --muted:#52646f; --line:#d5dee4; --bg:#f6f3ee; --accent:#0f6e56; }}
body {{ margin:0; font:16px/1.5 Georgia, serif; color:var(--ink); background:var(--bg); }}
header {{ background:var(--ink); color:#f6f3ee; padding:2rem 1.5rem; }}
header p {{ max-width:44rem; color:#d9e2e8; }}
.kicker {{ letter-spacing:.12em; text-transform:uppercase; font:700 11px system-ui; opacity:.75; }}
main {{ max-width:52rem; margin:0 auto; padding:1.5rem 1.25rem 3rem; }}
h1 {{ margin:.4rem 0 .6rem; font-size:1.85rem; }}
h2 {{ border-top:1px solid var(--line); padding-top:.8rem; margin-top:1.8rem; }}
ul {{ padding-left:1.2rem; }}
li {{ margin:.35rem 0; }}
a {{ color:#0b4f8a; }}
.path {{ color:var(--muted); font:12px ui-monospace, Menlo, monospace; margin-left:.4rem; }}
input {{ width:100%; padding:.55rem .7rem; font:16px system-ui; border:1px solid var(--line); border-radius:6px; }}
</style>
</head>
<body>
<header>
<div class="kicker">zapier.georgelambert.org</div>
<h1>Verae Time × Zapier documentation</h1>
<p>Project reference: setup checklist, architecture (including NATS job wait and WORM archive fan-out), module API sheets, models, OpenAPI, and research notes. Generated {now}.</p>
</header>
<main>
<p><input id="q" type="search" placeholder="Filter links…" /></p>
{''.join(cards)}
<section>
<h2>Git repositories (Forgejo)</h2>
<p>Clone: <code>ssh://git@git.georgelambert.org:2223/marchon/&lt;name&gt;.git</code> (SSH port 2223). Branches <code>main</code> and <code>master</code>.</p>
<ul>{git_lis}</ul>
</section>
<section>
<h2>Master module docs</h2>
<ul>
<li><a href="docs-master/README.md">docs-master README</a></li>
<li><a href="docs-master/MESSAGE-FLOWS.md">MESSAGE-FLOWS.md</a></li>
<li><a href="docs-master/modules-and-nats.md">modules-and-nats.md</a></li>
<li><a href="docs-master/archive-nats.md">archive-nats.md</a></li>
<li><a href="docs-master/composition.md">composition.md</a></li>
<li><a href="docs-master/modules/">Per-module SUMMARY.md and NATS.md</a></li>
<li><a href="user-docs/README.md">User guide (signup → tree-node lookup)</a></li>
<li><a href="packages/verae-zapier-simulator/README.md">Simulator README</a></li>
</ul>
</section>
<section>
<h2>Catalogs</h2>
<ul>{extra_lis}
<li><a href="docs/modules/">All module markdown</a></li>
<li><a href="docs/models/">All model markdown</a></li>
<li><a href="docs/modules-pdf/">Module PDFs</a></li>
<li><a href="docs/models-pdf/">Model PDFs</a></li>
</ul>
</section>
</main>
<script>
const q = document.getElementById('q');
q.addEventListener('input', () => {{
const v = q.value.toLowerCase();
document.querySelectorAll('li').forEach(li => {{
li.style.display = li.textContent.toLowerCase().includes(v) ? '' : 'none';
}});
}});
</script>
</body>
</html>
"""
(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()