name: Docs # Checks that the documentation the project actually ships stays consistent: # - every relative link / image path in docs/*.md and README.md resolves # inside the repo (external URLs are NOT fetched: localhost examples, # rate-limited hosts and the Tailscale-era links made that gate permanently # red, and a 200 on a curl --head proves nothing about a doc anyway) # - the FastAPI app imports and its OpenAPI schema is exportable (that is # the reference at https://water.buildfor.life/docs) # The previous Sphinx/apidoc jobs produced artifacts nobody read and were # removed. Reference docs live in docs/*.md; the public overview is at # https://buildfor.life/docs/tooling/ping-river-monitor/. on: push: branches: [master, develop] paths: - "docs/**" - "README.md" - "CONTRIBUTING.md" - "src/web_api.py" - "src/schemas.py" - ".gitea/workflows/docs.yml" pull_request: paths: - "docs/**" - "README.md" - "CONTRIBUTING.md" workflow_dispatch: env: PYTHON_VERSION: "3.11" 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 / "CONTRIBUTING.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 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip cache-dependency-path: requirements.txt - name: Install dependencies run: | python -m pip install --upgrade pip --root-user-action=ignore pip install --root-user-action=ignore -r requirements.txt - name: OpenAPI schema exports env: DB_TYPE: sqlite run: | python - <<'PY' import json from src.web_api import app spec = app.openapi() paths = sorted(spec["paths"]) required = {"/forecast", "/measurements/latest", "/measurements/history/{station_code}", "/stations", "/api/stats", "/health"} missing = required - set(paths) assert not missing, f"documented endpoints missing from the app: {missing}" json.dump(spec, open("openapi.json", "w"), indent=1) print(f"{len(paths)} paths; schema written to openapi.json") PY - uses: actions/upload-artifact@v3 with: name: openapi-${{ github.run_number }} path: openapi.json