Some checks are pending
offline / test (push) Waiting to run
Central fleet.json sets min/max copies. Tree-node keepFloor respawns until three healthy unpaused replicas remain. CLI and loopback UI pause, resume, stop, and restart instances that fail health checks.
324 lines
13 KiB
Python
Executable file
324 lines
13 KiB
Python
Executable file
#!/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"),
|
||
],
|
||
),
|
||
(
|
||
"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"<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",
|
||
"verae-fleet",
|
||
"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"),
|
||
("verae-fleet", "Service catalog, replica floors, monitor"),
|
||
]
|
||
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/<name>.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()
|