Implemented a standardized folder structure for domain documentation to improve clarity and maintainability. Migrated existing files to the new structure, updated links in related documentation, and added README files for navigation and guidance.
137 lines
3.7 KiB
Bash
Executable File
137 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# validate-links.sh - Link-Validierung für Projektdokumentation (`docs/**`).
|
|
# Zweck: Guardrail für die "Docs-as-Code"-Strategie.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
QUICK_MODE=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--quick)
|
|
QUICK_MODE=true
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
cat << 'EOF'
|
|
Docs Link-Validierung
|
|
|
|
USAGE:
|
|
./.junie/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 os
|
|
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, da sie zu wartungsintensiv waren.
|
|
# Das Skript konzentriert sich nun auf die Validierung der 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:
|
|
# remove fragment and query parts
|
|
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
|
|
|
|
# drop angle brackets <...> used in markdown for urls with spaces
|
|
if target.startswith("<") and target.endswith(">"):
|
|
target = target[1:-1]
|
|
|
|
target = unquote(strip_fragment_and_query(target))
|
|
|
|
# ignore absolute paths in the repo (we treat them as doc-style links; validate only if relative)
|
|
if target.startswith("/"):
|
|
continue
|
|
|
|
# ignore non-file targets (e.g. empty or protocol-less anchors)
|
|
if ":" in target.split("/", 1)[0]:
|
|
# things like "vscode:..." etc.
|
|
continue
|
|
|
|
# treat as file path relative to markdown file
|
|
resolved = (f.parent / target).resolve()
|
|
|
|
# keep validation within repo
|
|
try:
|
|
resolved.relative_to(root.resolve())
|
|
except ValueError:
|
|
print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}")
|
|
errors += 1
|
|
continue
|
|
|
|
# allow directories if they contain README.md
|
|
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
|