feat: serve docs flat at /<repo>/ with no redirects

- collect.py copies each repo's built site contents directly to a
  top-level <repo>/ dir (was libs|apis|wiki/<repo>/site nesting)
- nginx uses `index index.html lib/index.html api/index.html` so
  /dagpipe/ -> lib/index.html, /auth-server/ -> api/index.html are
  served internally with no redirects
- _index regenerates links against the flat layout
  (/dagpipe/lib/, /auth-server/api/, /mongo-ops/, /blog/...)
- config.yml static entries point at vendored blog/ + media-manager/
- Dockerfile copies per-repo dirs flat into the nginx html root
- removed stale libs/, apis/, wiki/, tutorials/ category trees
This commit is contained in:
2026-09-11 21:32:30 +05:30
parent 0c25cbb19f
commit a32eeaf2b4
882 changed files with 85 additions and 1982 deletions

View File

@@ -17,19 +17,10 @@ import yaml
ROOT = Path(__file__).resolve().parent
CONFIG_FILE = ROOT / "config.yml"
KIND_DIR = {"lib": "libs", "api": "apis", "wiki": "wiki"}
SITE_DIR = "site"
MCP_DIR = "mcp"
TITLE_CSS = "font-size:1.6rem;margin-bottom:1.2rem;color:#2dd4bf;"
CARD_CSS = (
"background:#161b22;border:1px solid #30363d;border-radius:12px;"
"padding:1.8rem;text-align:left;"
"transition:transform 0.15s ease,box-shadow 0.15s ease;"
)
INDENT = " "
OLD_DIRS = ("libs", "apis", "wiki", "tutorials")
# ── helpers ──────────────────────────────────────────────────────────────────
@@ -58,6 +49,18 @@ def _safe_description(entry: dict) -> str:
return entry.get("description") or f"Documentation for {_safe_title(entry)}"
def _find_home(dest: Path) -> str:
"""After copying a site into *dest*, return the relative URL path
where index.html lives. Checks root, lib/, api/ in order."""
if (dest / "index.html").exists():
return ""
if (dest / "lib" / "index.html").exists():
return "lib/"
if (dest / "api" / "index.html").exists():
return "api/"
return ""
# ── index.html generator ────────────────────────────────────────────────────
INDEX_TEMPLATE = r"""<!DOCTYPE html>
@@ -137,7 +140,6 @@ INDEX_TEMPLATE = r"""<!DOCTYPE html>
def _render_section(title: str, cards: list[tuple[str, str, str, str]]) -> str:
"""cards = [(safe_title, description, href, label), ...]"""
if not cards:
return ""
lines = [f'<section class="section">', f"<h2>{title}</h2>", '<div class="grid">']
@@ -152,7 +154,7 @@ def _render_section(title: str, cards: list[tuple[str, str, str, str]]) -> str:
return "\n".join(lines)
def build_index_html(config: dict) -> str:
def build_index_html(config: dict, home_urls: dict[str, str]) -> str:
lib_cards: list[tuple[str, str, str, str]] = []
api_cards: list[tuple[str, str, str, str]] = []
wiki_cards: list[tuple[str, str, str, str]] = []
@@ -163,22 +165,24 @@ def build_index_html(config: dict) -> str:
name = entry["name"]
title = _safe_title(entry)
desc = _safe_description(entry)
url = home_urls.get(name, f"/{name}/")
if docs.get("lib"):
lib_cards.append((title, desc, f"/libs/{name}/", "View Documentation"))
if docs.get("api"):
api_cards.append((title, desc, f"/apis/{name}/", "View Documentation"))
if docs.get("wiki"):
wiki_cards.append((title, desc, f"/wiki/{name}/", "View Documentation"))
lib_cards.append((title, desc, url, "View Documentation"))
elif docs.get("api"):
api_cards.append((title, desc, url, "View Documentation"))
elif docs.get("wiki"):
wiki_cards.append((title, desc, url, "View Documentation"))
for entry in config.get("static", []):
kind = entry.get("kind")
name = entry["repo"]
title = _safe_title(entry)
desc = _safe_description(entry)
url = home_urls.get(name, f"/{name}/")
if kind == "api":
api_cards.append((title, desc, f"/apis/{name}/", "View Documentation"))
api_cards.append((title, desc, url, "View Documentation"))
elif kind == "tutorial":
tutorial_cards.append((title, desc, f"/tutorials/{name}/", "View Tutorial"))
tutorial_cards.append((title, desc, url, "View Tutorial"))
body = "\n\n".join(filter(None, [
_render_section("Libraries", lib_cards),
@@ -196,7 +200,7 @@ NGINX_TEMPLATE = r"""server {{
server_name _;
root /usr/share/nginx/html;
index index.html api/index.html;
index index.html lib/index.html api/index.html;
location / {{
try_files $uri $uri/ /index.html;
@@ -219,6 +223,7 @@ def collect(config: dict, dry_run: bool = False) -> list[str]:
service = config["service"]
errors: list[str] = []
copied: list[str] = []
home_urls: dict[str, str] = {}
for entry in config.get("repos", []):
name = entry["name"]
@@ -237,8 +242,8 @@ def collect(config: dict, dry_run: bool = False) -> list[str]:
if not src.is_dir():
errors.append(f"{name}: {kind} site missing ({src})")
continue
dst = ROOT / KIND_DIR[kind] / name / SITE_DIR
copied.append(f"{kind:>4} {name} {dst.relative_to(ROOT)}")
dst = ROOT / name
copied.append(f"{kind:>4} {name} {dst.relative_to(ROOT)}/")
if not dry_run:
_copy_dir(src, dst)
@@ -249,28 +254,51 @@ def collect(config: dict, dry_run: bool = False) -> list[str]:
errors.append(f"{name}: mcp bundle missing ({src})")
continue
dst = ROOT / MCP_DIR / name
copied.append(f" mcp {name} {dst.relative_to(ROOT)}")
copied.append(f" mcp {name} {dst.relative_to(ROOT)}/")
if not dry_run:
_copy_dir(src, dst)
for entry in config.get("static", []):
name = entry["repo"]
p = ROOT / entry["path"]
dst = ROOT / name
if not p.is_dir():
errors.append(f"static {entry['repo']}: path missing ({p})")
errors.append(f"static {name}: path missing ({p})")
elif p == dst:
copied.append(f"static {name} already vendored at {dst.relative_to(ROOT)}/")
else:
copied.append(f"static {entry['repo']} {entry['path']}")
copied.append(f"static {name} {dst.relative_to(ROOT)}/")
if not dry_run:
_copy_dir(p, dst)
# ── compute home URLs ───────────────────────────────────────────────────
if not dry_run:
index_html = build_index_html(config)
for entry in config.get("repos", []):
home_urls[entry["name"]] = f"/{entry['name']}/{_find_home(ROOT / entry['name'])}"
for entry in config.get("static", []):
home_urls[entry["repo"]] = f"/{entry['repo']}/{_find_home(ROOT / entry['repo'])}"
# ── regenerate outputs ──────────────────────────────────────────────
index_html = build_index_html(config, home_urls)
(ROOT / "_index" / "index.html").write_text(index_html, encoding="utf-8")
nginx_conf = build_nginx_conf(service["html_port"])
(ROOT / "nginx.conf").write_text(nginx_conf, encoding="utf-8")
print(f"[OK] _index/index.html regenerated")
print(f"[OK] nginx.conf regenerated (listen {service['html_port']})")
for name, url in sorted(home_urls.items()):
print(f"[URL] {name:20s} -> {url}")
for line in copied:
print(f"[COP]{line}")
# ── clean stale category dirs ───────────────────────────────────────────
if not dry_run:
for d in OLD_DIRS:
p = ROOT / d
if p.exists():
shutil.rmtree(p, ignore_errors=True)
print(f"[DEL] {d}/ (stale category dir removed)")
if errors:
print("\nERRORS:", file=sys.stderr)
for e in errors: