338 lines
11 KiB
Bash
Executable File
338 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# validate-links.sh - Automatisierte Link-Validierung für Meldestelle Guidelines
|
||
# Version: 1.0.0
|
||
# Autor: Junie AI-Assistant
|
||
# Datum: 2025-09-15
|
||
|
||
set -euo pipefail
|
||
|
||
# Bestimme das Projekt-Root-Verzeichnis
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
|
||
# Wechsle ins Projekt-Root für korrekte relative Pfade
|
||
cd "$PROJECT_ROOT"
|
||
|
||
GUIDELINES_DIR=".junie/guidelines"
|
||
META_DIR="$GUIDELINES_DIR/_meta"
|
||
CROSS_REFS_FILE="$META_DIR/cross-refs.json"
|
||
SCRIPTS_DIR=".junie/scripts"
|
||
|
||
# Farben für Output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Logging-Funktionen
|
||
log_info() {
|
||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}✅ $1${NC}"
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}❌ $1${NC}"
|
||
}
|
||
|
||
# Hauptvariablen
|
||
ERRORS=0
|
||
WARNINGS=0
|
||
QUICK_MODE=false
|
||
|
||
# Command-line Parameter
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
--quick)
|
||
QUICK_MODE=true
|
||
shift
|
||
;;
|
||
--help|-h)
|
||
cat << 'EOF'
|
||
Meldestelle Guidelines Link-Validierung
|
||
|
||
USAGE:
|
||
./validate-links.sh [OPTIONS]
|
||
|
||
OPTIONS:
|
||
--quick Schnelle Validierung (nur kritische Checks)
|
||
--help Diese Hilfe anzeigen
|
||
|
||
BESCHREIBUNG:
|
||
Validiert alle Links und Cross-Referenzen in den Guidelines basierend auf
|
||
der cross-refs.json Matrix. Prüft:
|
||
|
||
- Cross-Referenzen zwischen Guidelines
|
||
- Markdown-Links in Guidelines
|
||
- YAML-Metadaten-Konsistenz
|
||
- Template-Struktur-Konsistenz
|
||
|
||
EXIT-CODES:
|
||
0 = Alle Validierungen erfolgreich
|
||
1 = Fehler gefunden
|
||
2 = Warnings gefunden (nur bei --strict)
|
||
|
||
EOF
|
||
exit 0
|
||
;;
|
||
*)
|
||
log_error "Unbekannter Parameter: $1"
|
||
echo "Nutze --help für Hilfe"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
echo "🔍 Meldestelle Guidelines Link-Validierung"
|
||
echo "=================================================="
|
||
echo "Datum: $(date '+%Y-%m-%d %H:%M:%S')"
|
||
echo "Modus: $([ "$QUICK_MODE" = true ] && echo "Quick" || echo "Vollständig")"
|
||
echo ""
|
||
|
||
# Prüfe ob erforderliche Dateien existieren
|
||
if [[ ! -f "$CROSS_REFS_FILE" ]]; then
|
||
log_error "Cross-Referenz-Datei nicht gefunden: $CROSS_REFS_FILE"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ ! -d "$GUIDELINES_DIR" ]]; then
|
||
log_error "Guidelines-Verzeichnis nicht gefunden: $GUIDELINES_DIR"
|
||
exit 1
|
||
fi
|
||
|
||
# 1. Cross-Referenz-Validierung
|
||
validate_cross_references() {
|
||
log_info "Validiere Cross-Referenzen aus cross-refs.json..."
|
||
|
||
local temp_guidelines=$(mktemp)
|
||
local temp_refs=$(mktemp)
|
||
|
||
# Alle Guidelines aus cross-refs.json extrahieren
|
||
jq -r '.cross_references | keys[]' "$CROSS_REFS_FILE" > "$temp_guidelines" 2>/dev/null || {
|
||
log_error "Fehler beim Lesen der cross-refs.json"
|
||
((ERRORS++))
|
||
return
|
||
}
|
||
|
||
while IFS= read -r guideline; do
|
||
guideline_file="$GUIDELINES_DIR/$guideline"
|
||
|
||
# Prüfe ob Guideline-Datei existiert
|
||
if [[ ! -f "$guideline_file" ]]; then
|
||
log_error "Guideline '$guideline' in cross-refs.json aber Datei fehlt: $guideline_file"
|
||
((ERRORS++))
|
||
continue
|
||
fi
|
||
|
||
# Hole referenzierte Guidelines aus JSON
|
||
jq -r ".cross_references[\"$guideline\"].references_to[]? // empty" "$CROSS_REFS_FILE" > "$temp_refs" 2>/dev/null
|
||
|
||
while IFS= read -r ref; do
|
||
# Skip leere Zeilen
|
||
[[ -z "$ref" ]] && continue
|
||
|
||
# Relativer Pfad zu absolut konvertieren
|
||
if [[ "$ref" == /* ]]; then
|
||
ref_file="$GUIDELINES_DIR$ref"
|
||
elif [[ "$ref" == *"/" ]]; then
|
||
# Directory-Referenz (z.B. technology-guides/docker/)
|
||
ref_dir="$GUIDELINES_DIR/$ref"
|
||
if [[ ! -d "$ref_dir" ]]; then
|
||
log_error "Referenziertes Verzeichnis '$ref' existiert nicht: $ref_dir"
|
||
((ERRORS++))
|
||
fi
|
||
continue
|
||
else
|
||
# Alle Referenzen sind relativ zum Guidelines-Root-Verzeichnis
|
||
ref_file="$GUIDELINES_DIR/$ref"
|
||
fi
|
||
|
||
# Normalisiere Pfad
|
||
ref_file=$(realpath -m "$ref_file" 2>/dev/null || echo "$ref_file")
|
||
|
||
# Prüfe ob referenzierte Datei existiert
|
||
if [[ ! -f "$ref_file" ]]; then
|
||
log_error "'$guideline' referenziert '$ref', aber Datei existiert nicht: $ref_file"
|
||
((ERRORS++))
|
||
continue
|
||
fi
|
||
|
||
# Prüfe ob der Link tatsächlich im Markdown existiert (nur im vollständigen Modus)
|
||
if [[ "$QUICK_MODE" = false ]]; then
|
||
ref_basename=$(basename "$ref" .md)
|
||
if ! grep -q "\[$ref_basename\]" "$guideline_file" && ! grep -q "($ref)" "$guideline_file"; then
|
||
log_warning "'$guideline' sollte '$ref' referenzieren, aber Link fehlt im Markdown"
|
||
((WARNINGS++))
|
||
fi
|
||
fi
|
||
|
||
done < "$temp_refs"
|
||
|
||
log_success "'$guideline' - Cross-Referenzen validiert"
|
||
|
||
done < "$temp_guidelines"
|
||
|
||
rm -f "$temp_guidelines" "$temp_refs"
|
||
}
|
||
|
||
# 2. Markdown-Links Validierung
|
||
validate_markdown_links() {
|
||
if [[ "$QUICK_MODE" = true ]]; then
|
||
log_info "Überspringe Markdown-Link-Validierung (Quick-Modus)"
|
||
return
|
||
fi
|
||
|
||
log_info "Validiere Markdown-Links in Guidelines..."
|
||
|
||
find "$GUIDELINES_DIR" -name "*.md" -not -path "*/_archived/*" | while read file; do
|
||
# Relative Links extrahieren ([Text](./path/file.md))
|
||
grep -o "\[.*\](\./[^)]*\.md)" "$file" 2>/dev/null | sed 's/.*(\.\///' | sed 's/).*//' | while read link; do
|
||
[[ -z "$link" ]] && continue
|
||
|
||
# Absoluten Pfad konstruieren
|
||
dir=$(dirname "$file")
|
||
target_file="$dir/$link"
|
||
target_file=$(realpath -m "$target_file" 2>/dev/null || echo "$target_file")
|
||
|
||
if [[ ! -f "$target_file" ]]; then
|
||
log_error "$(basename "$file") verlinkt auf '$link', aber Ziel existiert nicht: $target_file"
|
||
((ERRORS++))
|
||
fi
|
||
done
|
||
|
||
# Relative Links mit ../ extrahieren
|
||
grep -o "\[.*\](\.\./[^)]*\.md)" "$file" 2>/dev/null | sed 's/.*(\.\.\///' | sed 's/).*//' | while read link; do
|
||
[[ -z "$link" ]] && continue
|
||
|
||
dir=$(dirname "$file")
|
||
target_file="$dir/../$link"
|
||
target_file=$(realpath -m "$target_file" 2>/dev/null || echo "$target_file")
|
||
|
||
if [[ ! -f "$target_file" ]]; then
|
||
log_error "$(basename "$file") verlinkt auf '../$link', aber Ziel existiert nicht: $target_file"
|
||
((ERRORS++))
|
||
fi
|
||
done
|
||
|
||
log_success "$(basename "$file") - Markdown-Links validiert"
|
||
done
|
||
}
|
||
|
||
# 3. YAML-Metadaten Validierung
|
||
validate_yaml_metadata() {
|
||
log_info "Validiere YAML-Metadaten-Konsistenz..."
|
||
|
||
find "$GUIDELINES_DIR" -name "*.md" -not -path "*/_archived/*" -not -name "README.md" -not -name "master-guideline.md" | while read file; do
|
||
# YAML-Header extrahieren (nur zwischen den ersten beiden --- Zeilen)
|
||
yaml_content=$(awk '/^---$/{if(++count==2) exit} count==1 && !/^---$/{print}' "$file" 2>/dev/null || echo "")
|
||
|
||
if [[ -z "$yaml_content" ]]; then
|
||
log_warning "'$(basename "$file")' hat keinen YAML-Header"
|
||
((WARNINGS++))
|
||
continue
|
||
fi
|
||
|
||
# YAML-Syntax prüfen (falls python verfügbar)
|
||
if command -v python3 &> /dev/null; then
|
||
echo "$yaml_content" | python3 -c "import yaml,sys; yaml.safe_load(sys.stdin)" 2>/dev/null || {
|
||
log_error "'$(basename "$file")' hat ungültige YAML-Syntax im Header"
|
||
((ERRORS++))
|
||
continue
|
||
}
|
||
fi
|
||
|
||
# Erforderliche Felder prüfen
|
||
required_fields=("guideline_type" "scope" "audience" "last_updated" "ai_context")
|
||
for field in "${required_fields[@]}"; do
|
||
if ! echo "$yaml_content" | grep -q "^$field:" 2>/dev/null; then
|
||
log_error "'$(basename "$file")' fehlt erforderliches YAML-Feld: $field"
|
||
((ERRORS++))
|
||
fi
|
||
done
|
||
|
||
# Dependencies validieren (nur im vollständigen Modus)
|
||
if [[ "$QUICK_MODE" = false ]]; then
|
||
deps=$(echo "$yaml_content" | grep "dependencies:" 2>/dev/null | sed 's/.*\[//' | sed 's/\].*//' | tr ',' '\n' | sed 's/[" ]//g' || echo "")
|
||
for dep in $deps; do
|
||
[[ -z "$dep" ]] && continue
|
||
dep_file="$GUIDELINES_DIR/$dep"
|
||
if [[ ! -f "$dep_file" ]]; then
|
||
log_error "'$(basename "$file")' dependency '$dep' existiert nicht"
|
||
((ERRORS++))
|
||
fi
|
||
done
|
||
fi
|
||
|
||
log_success "$(basename "$file") - Metadaten validiert"
|
||
done
|
||
}
|
||
|
||
# 4. Template-Struktur Validierung
|
||
validate_template_structure() {
|
||
if [[ "$QUICK_MODE" = true ]]; then
|
||
log_info "Überspringe Template-Validierung (Quick-Modus)"
|
||
return
|
||
fi
|
||
|
||
log_info "Validiere Template-Struktur-Konsistenz..."
|
||
|
||
local template_dir="$GUIDELINES_DIR/_templates"
|
||
if [[ ! -d "$template_dir" ]]; then
|
||
log_warning "Template-Verzeichnis nicht gefunden: $template_dir"
|
||
((WARNINGS++))
|
||
return
|
||
fi
|
||
|
||
# Prüfe ob neue Guidelines Template-Struktur folgen
|
||
find "$GUIDELINES_DIR" -name "*.md" -not -path "*/_archived/*" -not -path "*/_templates/*" -not -name "README.md" | while read file; do
|
||
# Prüfe grundlegende Template-Struktur
|
||
if ! grep -q "^guideline_type:" "$file" 2>/dev/null; then
|
||
log_warning "'$(basename "$file")' folgt möglicherweise nicht der Template-Struktur (fehlt guideline_type)"
|
||
((WARNINGS++))
|
||
fi
|
||
|
||
if ! grep -q "^ai_context:" "$file" 2>/dev/null; then
|
||
log_warning "'$(basename "$file")' folgt möglicherweise nicht der Template-Struktur (fehlt ai_context)"
|
||
((WARNINGS++))
|
||
fi
|
||
done
|
||
|
||
log_success "Template-Struktur validiert"
|
||
}
|
||
|
||
# Hauptvalidierung ausführen
|
||
main() {
|
||
validate_cross_references
|
||
validate_markdown_links
|
||
validate_yaml_metadata
|
||
validate_template_structure
|
||
|
||
echo ""
|
||
echo "=================================================="
|
||
echo "📊 Validierungs-Ergebnisse:"
|
||
echo " Fehler: $ERRORS"
|
||
echo " Warnungen: $WARNINGS"
|
||
|
||
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
|
||
log_success "Alle Validierungen erfolgreich! 🎉"
|
||
exit 0
|
||
elif [[ $ERRORS -eq 0 ]]; then
|
||
log_warning "Validierung abgeschlossen mit $WARNINGS Warnungen"
|
||
exit 0
|
||
else
|
||
log_error "Validierung fehlgeschlagen mit $ERRORS Fehlern und $WARNINGS Warnungen"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Script ausführen
|
||
main "$@"
|