cb6e0103e7
Co-authored-by: Junie <junie@jetbrains.com>
128 lines
3.1 KiB
Bash
128 lines
3.1 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
|
# shellcheck disable=SC1091
|
|
source "$SCRIPT_DIR/lib/common.sh"
|
|
|
|
REPO_ROOT="$(resolve_repo_root)"
|
|
cd "$REPO_ROOT"
|
|
|
|
QUICK_MODE=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--quick)
|
|
QUICK_MODE=true
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
cat << 'EOF'
|
|
Docs Link-Validierung
|
|
|
|
USAGE:
|
|
./.ai/scripts/validate-links.sh [--quick]
|
|
|
|
BESCHREIBUNG:
|
|
Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade.
|
|
Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...).
|
|
|
|
OPTIONEN:
|
|
--quick Führt nur eine Teilmenge der Prüfungen durch (aktuell nicht implementiert).
|
|
EOF
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "[ERROR] Unbekannter Parameter: $1" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
python3 - <<'PY'
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from urllib.parse import unquote
|
|
|
|
root = Path.cwd()
|
|
docs_dir = root / "docs"
|
|
|
|
if not docs_dir.is_dir():
|
|
print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
# Veraltete Pfad-Prüfungen wurden entfernt; Fokus auf Link-Integrität.
|
|
FORBIDDEN_SUBSTRINGS = []
|
|
|
|
md_files = sorted(docs_dir.rglob("*.md"))
|
|
|
|
link_pattern = re.compile(r"\]\(([^)]+)\)")
|
|
|
|
errors = 0
|
|
|
|
def is_external(target: str) -> bool:
|
|
t = target.lower()
|
|
return t.startswith("http://") or t.startswith("https://") or t.startswith("mailto:")
|
|
|
|
def strip_fragment_and_query(target: str) -> str:
|
|
target = target.split("#", 1)[0]
|
|
target = target.split("?", 1)[0]
|
|
return target
|
|
|
|
for f in md_files:
|
|
text = f.read_text(encoding="utf-8", errors="replace")
|
|
|
|
for forbidden in FORBIDDEN_SUBSTRINGS:
|
|
if forbidden in text:
|
|
print(f"[ERROR] Veralteter Pfad '{forbidden}' in {f}")
|
|
errors += 1
|
|
|
|
for match in link_pattern.finditer(text):
|
|
target = match.group(1).strip()
|
|
|
|
if not target:
|
|
continue
|
|
if is_external(target):
|
|
continue
|
|
if target.startswith("#"):
|
|
continue
|
|
|
|
if target.startswith("<") and target.endswith(">"):
|
|
target = target[1:-1]
|
|
|
|
target = unquote(strip_fragment_and_query(target))
|
|
|
|
if target.startswith("/"):
|
|
continue
|
|
|
|
if ":" in target.split("/", 1)[0]:
|
|
# z.B. "vscode:..."
|
|
continue
|
|
|
|
resolved = (f.parent / target).resolve()
|
|
|
|
try:
|
|
resolved.relative_to(root.resolve())
|
|
except ValueError:
|
|
print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}")
|
|
errors += 1
|
|
continue
|
|
|
|
if resolved.is_dir():
|
|
if not (resolved / "README.md").is_file():
|
|
print(f"[ERROR] Verlinktes Verzeichnis ohne README.md: {f} -> {target}")
|
|
errors += 1
|
|
continue
|
|
|
|
if not resolved.exists():
|
|
print(f"[ERROR] Broken link: {f} -> {target}")
|
|
errors += 1
|
|
|
|
if errors:
|
|
print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler")
|
|
sys.exit(1)
|
|
|
|
print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft")
|
|
PY
|