name: Docs # Every relative link / image path in the shipped Markdown (README, CLAUDE, # CONTRIBUTING, rust/README, docs/) resolves inside the repo. External URLs # are NOT fetched: localhost examples and rate-limited hosts made that gate # permanently red, and a 200 proves nothing about a doc anyway. The API # reference (/docs, /openapi.json) is generated by ping-collector from its # route table and tested in rust/collector/src/web/tests.rs. on: push: branches: [master, develop] paths: - "**.md" - ".gitea/workflows/docs.yml" pull_request: paths: - "**.md" workflow_dispatch: jobs: docs: name: Validate documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Relative links and images resolve run: | python3 - <<'PY' import re, sys, pathlib root = pathlib.Path(".") files = [root / "README.md", root / "CLAUDE.md", root / "CONTRIBUTING.md", root / "rust" / "README.md", *root.glob("docs/**/*.md")] link = re.compile(r"!?\[[^\]]*\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)") bad = [] for md in files: if not md.exists(): continue for m in link.finditer(md.read_text(encoding="utf-8")): target = m.group(1) if target.startswith(("http://", "https://", "mailto:", "#")): continue path = target.split("#", 1)[0] if not path: continue resolved = (md.parent / path).resolve() if not resolved.exists(): bad.append(f"{md}: {target}") if bad: print("Broken relative links:") print("\n".join(" " + b for b in bad)) sys.exit(1) print(f"checked {len(files)} files, all relative links resolve") PY