mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Docs split Phase 1: docs.roboco.tech becomes canonical — redirect stubs, user tree removed, MkDocs retired (#299)
* docs: replace MkDocs deploy with static redirect stubs docs.roboco.tech (roboco-website) is now the canonical user-facing docs site (spec: docs/internal/specs/2026-07-03-docs-site-split.md). Every URL this repo's Pages site published needs to keep resolving, so scripts/gen_docs_redirects.py generates one meta-refresh + rel=canonical stub per page — derived from mkdocs.yml's nav while it still lists every page — into the committed docs-redirects/ directory. All 58 stub targets verify against the website repo's nav.ts (0 unmapped); the only rename is how-to/* -> tour/* per the spec's slug map. Rewrite .github/workflows/docs.yml to deploy docs-redirects/ directly instead of running `mkdocs build`. The user-facing docs/ tree and mkdocs.yml itself are untouched here — deleting them is the next step, gated on these stubs resolving correctly. * docs: delete the MkDocs user-facing tree, docs.roboco.tech is canonical Per docs/internal/specs/2026-07-03-docs-site-split.md decision (1): Material->MDX is not verbatim-portable, so repo A's user-facing docs are deleted rather than kept as a permanently-drifting mirror. Deletes index/get-started/company/how-to/panel/models/operations/optional/deploy/ api/troubleshooting plus images/videos/assets. KEEPS docs/rag/ (indexed agent corpus), docs/map/, docs/internal/, and the team buckets (backend/frontend/ux_ui) — none of these were ever in mkdocs.yml's nav. mkdocs.yml's entire nav mapped 1:1 onto the deleted tree, so pruning it "accordingly" leaves nothing — remove it outright, along with the now-dead `docs` optional-dependency group (mkdocs/mkdocs-material/mkdocstrings/ pymarkdownlnt — mkdocstrings was already unused, not wired into any mkdocs plugin), the matching deptry DEP002 ignore entries, .pymarkdown.json, and the serve-docs/build-docs/lint-docs/fix-docs Makefile targets (all scoped only to the deleted paths). Add regen-docs-redirects as the one remaining docs Makefile target. Repointed everything that linked into the deleted tree or the old Pages URL: README's hero video/gif and walkthrough links now hit the docs.roboco.tech-hosted copies (already duplicated there per the spec's ground truth), usage.md / deployment.md's jump-links, pyproject's Documentation URL, and CLAUDE.md's Blueprint Reference paragraph. * chore: sync uv.lock after removing the docs optional-dependency group Follow-up to the mkdocs.yml / docs extra removal — mkdocs, mkdocs-material, mkdocstrings, pymarkdownlnt, and their transitive-only dependencies drop out of the lockfile now that nothing in pyproject.toml declares them. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate GitHub Pages redirect stubs for every page MkDocs used to publish.
|
||||
|
||||
Docs-site split Phase 1 (docs/internal/specs/2026-07-03-docs-site-split.md):
|
||||
docs.roboco.tech (the `roboco-website` repo) is now the canonical user-facing
|
||||
docs site; this repo's MkDocs build retires. Every URL MkDocs published under
|
||||
https://rennf93.github.io/roboco/ must keep resolving — old links, bookmarks,
|
||||
and search results don't get to 404 — so instead of real content each gets a
|
||||
tiny static stub: a meta-refresh + `rel=canonical` pointing at the
|
||||
docs.roboco.tech equivalent.
|
||||
|
||||
The stub set is generated once, while `mkdocs.yml`'s nav still lists every
|
||||
published page, and the output is committed under `docs-redirects/` (NOT
|
||||
gitignored, unlike `site/`) because `mkdocs.yml`'s nav is deleted later in
|
||||
this same phase — nothing durable would be left for a future run to read.
|
||||
`.github/workflows/docs.yml` deploys that committed directory as-is; it does
|
||||
not re-run this script.
|
||||
|
||||
Section rename: the mirror's "how-to" tour section is called "tour" on
|
||||
docs.roboco.tech (per the spec's slug map). Every other section slug is
|
||||
identical between the two sites — verified with --verify-nav-ts against the
|
||||
website repo's nav.ts before this stub set was committed.
|
||||
|
||||
Usage:
|
||||
uv run python scripts/gen_docs_redirects.py
|
||||
uv run python scripts/gen_docs_redirects.py \\
|
||||
--verify-nav-ts ../roboco-website/src/content/docs/nav.ts
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
MKDOCS_YML = Path("mkdocs.yml")
|
||||
OUT_DIR = Path("docs-redirects")
|
||||
DEST_BASE = "https://docs.roboco.tech/docs"
|
||||
|
||||
SECTION_RENAME = {"how-to": "tour"}
|
||||
|
||||
STUB_TEMPLATE = """<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{title}</title>
|
||||
<meta http-equiv="refresh" content="0; url={dest}">
|
||||
<link rel="canonical" href="{dest}">
|
||||
</head>
|
||||
<body>
|
||||
<p>This page has moved to <a href="{dest}">{dest}</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Page:
|
||||
title: str
|
||||
md_path: str # as written in mkdocs.yml nav, e.g. "optional/http-security.md"
|
||||
|
||||
|
||||
class _TolerantLoader(yaml.SafeLoader):
|
||||
"""mkdocs.yml's `markdown_extensions` block carries `!!python/name:...`
|
||||
tags (pymdownx wiring). We only read `nav:`, so tolerate them instead of
|
||||
failing to parse the file."""
|
||||
|
||||
|
||||
_TolerantLoader.add_multi_constructor(
|
||||
"tag:yaml.org,2002:python/name:", lambda _loader, suffix, _node: suffix
|
||||
)
|
||||
|
||||
|
||||
def load_nav_pages(mkdocs_yml: Path) -> list[Page]:
|
||||
config = yaml.load(mkdocs_yml.read_text(), Loader=_TolerantLoader)
|
||||
return list(_walk_nav(config["nav"]))
|
||||
|
||||
|
||||
def _walk_nav(items: list[Any], section_title: str | None = None) -> Any:
|
||||
for item in items:
|
||||
if isinstance(item, str):
|
||||
yield Page(title=section_title or "RoboCo Docs", md_path=item)
|
||||
elif isinstance(item, dict):
|
||||
for title, value in item.items():
|
||||
if isinstance(value, str):
|
||||
yield Page(title=title, md_path=value)
|
||||
else:
|
||||
yield from _walk_nav(value, section_title=title)
|
||||
|
||||
|
||||
def _slug_parts(md_path: str) -> tuple[str, ...]:
|
||||
"""Directory/stem parts of an md path with a trailing index/README
|
||||
collapsed onto the enclosing directory — mirrors MkDocs'
|
||||
`use_directory_urls` scheme (the default, and what this site used)."""
|
||||
path = Path(md_path)
|
||||
parts = path.parts[:-1]
|
||||
if path.stem not in ("index", "README"):
|
||||
parts = (*parts, path.stem)
|
||||
return parts
|
||||
|
||||
|
||||
def source_dir(md_path: str) -> Path:
|
||||
"""Output directory (relative to OUT_DIR) holding this page's stub —
|
||||
the exact directory GitHub Pages already serves it from today."""
|
||||
return Path(*_slug_parts(md_path))
|
||||
|
||||
|
||||
def dest_url(md_path: str) -> str:
|
||||
parts = _slug_parts(md_path)
|
||||
if not parts:
|
||||
return DEST_BASE
|
||||
renamed = (SECTION_RENAME.get(parts[0], parts[0]), *parts[1:])
|
||||
return f"{DEST_BASE}/{'/'.join(renamed)}"
|
||||
|
||||
|
||||
def render_stub(title: str, dest: str) -> str:
|
||||
return STUB_TEMPLATE.format(title=title, dest=dest)
|
||||
|
||||
|
||||
def generate(pages: list[Page], out_dir: Path) -> list[tuple[str, str]]:
|
||||
"""Write one stub per page; return [(source_url_path, dest_url), ...]."""
|
||||
mapping = []
|
||||
for page in pages:
|
||||
rel_dir = source_dir(page.md_path)
|
||||
dest = dest_url(page.md_path)
|
||||
out_path = out_dir / rel_dir / "index.html"
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text(render_stub(page.title, dest))
|
||||
source_url_path = "/" if str(rel_dir) == "." else f"/{rel_dir.as_posix()}/"
|
||||
mapping.append((source_url_path, dest))
|
||||
return mapping
|
||||
|
||||
|
||||
def verify_against_nav_ts(mapping: list[tuple[str, str]], nav_ts: Path) -> list[str]:
|
||||
"""Every stub's destination must resolve to a real docs.roboco.tech page.
|
||||
Returns the unmapped destinations (empty = every stub target exists)."""
|
||||
nav_slugs = set(re.findall(r'slug:\s*"([^"]+)"', nav_ts.read_text()))
|
||||
unmapped = []
|
||||
for _source, dest in mapping:
|
||||
slug = dest.removeprefix(f"{DEST_BASE}/").removeprefix(DEST_BASE)
|
||||
if slug and slug not in nav_slugs:
|
||||
unmapped.append(dest)
|
||||
return unmapped
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = sys.argv[1:]
|
||||
verify_path = (
|
||||
Path(args[args.index("--verify-nav-ts") + 1])
|
||||
if "--verify-nav-ts" in args
|
||||
else None
|
||||
)
|
||||
|
||||
pages = load_nav_pages(MKDOCS_YML)
|
||||
mapping = generate(pages, OUT_DIR)
|
||||
|
||||
print(f"Generated {len(mapping)} redirect stub(s) under {OUT_DIR}/:")
|
||||
for source, dest in mapping:
|
||||
print(f" {source:55s} -> {dest}")
|
||||
|
||||
if verify_path is not None:
|
||||
unmapped = verify_against_nav_ts(mapping, verify_path)
|
||||
if unmapped:
|
||||
print(f"\n{len(unmapped)} unmapped target(s) (no matching nav.ts entry):")
|
||||
for target in unmapped:
|
||||
print(f" {target}")
|
||||
return 1
|
||||
print(f"\nAll {len(mapping)} stub targets verified against {verify_path}.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user