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:
80
collect.py
80
collect.py
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user