#!/bin/bash # Content ID Consistency Validator # This script validates that content IDs are consistent between language versions # It should be run during content generation to catch ID mismatches early set -e # Configuration CONTENT_DIR="content" CONTENT_KINDS_FILE="content/content-kinds.toml" # Supported languages LANGUAGES=("en" "es") # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Get enabled content types from content-kinds.toml get_content_types() { if [ ! -f "$CONTENT_KINDS_FILE" ]; then echo "blog recipes" # fallback return fi # Extract content type names from TOML where enabled = true grep -A 10 "^\[\[content_kinds\]\]" "$CONTENT_KINDS_FILE" | \ awk ' /^\[\[content_kinds\]\]/ { in_section = 1; name = ""; enabled = ""; next } /^name = / { if (in_section) { name = $3; gsub(/["'"'"']/, "", name) } } /^enabled = / { if (in_section) { enabled = $3; gsub(/["'"'"']/, "", enabled) } } /^$/ || /^\[/ { if (in_section && name && enabled == "true") print name in_section = 0; name = ""; enabled = "" } END { if (in_section && name && enabled == "true") print name } ' | tr '\n' ' ' } # Helper functions log_error() { echo -e "${RED}❌ ERROR: $1${NC}" } log_warning() { echo -e "${YELLOW}⚠️ WARNING: $1${NC}" } log_success() { echo -e "${GREEN}✅ $1${NC}" } log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } log_step() { echo -e "${CYAN}🔧 $1${NC}" } # Extract ID from markdown frontmatter extract_id() { local file="$1" if [ ! -f "$file" ]; then echo "" return fi # Extract ID from YAML frontmatter if head -1 "$file" | grep -q "^---$"; then awk '/^---$/{flag++; next} flag==1 && /^---$/{flag++} flag==1 && /^id:/{print $2; exit}' "$file" | sed 's/["\047]//g' fi } # Extract filename without extension get_filename() { local file="$1" basename "$file" .md } # Validate ID consistency for a content type validate_content_type() { local content_type="$1" local errors=0 local warnings=0 log_step "Validating $content_type ID consistency..." # Get the primary language directory (English) local primary_lang="en" local primary_dir="${CONTENT_DIR}/${content_type}/${primary_lang}" if [ ! -d "$primary_dir" ]; then log_error "Primary language directory not found: $primary_dir" return 1 fi # Create temporary files for tracking local temp_dir="/tmp/id_validation_$$" mkdir -p "$temp_dir" # Process each markdown file in primary language for primary_file in "$primary_dir"/*.md; do if [ ! -f "$primary_file" ]; then continue fi local filename=$(get_filename "$primary_file") local primary_id=$(extract_id "$primary_file") if [ -z "$primary_id" ]; then log_warning "No ID found in primary file: $primary_file" warnings=$((warnings + 1)) continue fi log_info "Checking $content_type: $filename (ID: $primary_id)" # Check corresponding files in other languages for lang in "${LANGUAGES[@]}"; do if [ "$lang" = "$primary_lang" ]; then continue fi local lang_dir="${CONTENT_DIR}/${content_type}/${lang}" local lang_file="${lang_dir}/${filename}.md" if [ ! -f "$lang_file" ]; then log_warning "Missing $lang translation for $filename" warnings=$((warnings + 1)) continue fi local lang_id=$(extract_id "$lang_file") if [ -z "$lang_id" ]; then log_error "No ID found in $lang file: $lang_file" errors=$((errors + 1)) continue fi if [ "$primary_id" != "$lang_id" ]; then log_error "ID mismatch for $filename:" log_error " $primary_lang: '$primary_id'" log_error " $lang: '$lang_id'" log_error " Files: $primary_file <-> $lang_file" errors=$((errors + 1)) # Store mismatch for summary echo "$content_type,$filename,$primary_lang,$primary_id,$lang,$lang_id" >> "$temp_dir/mismatches.csv" else log_success " ✓ $lang: $lang_id" fi done done # Generate summary if [ -f "$temp_dir/mismatches.csv" ]; then echo "" log_error "ID MISMATCHES FOUND:" echo -e "${RED}Content Type | Filename | ${primary_lang} ID | Other Lang | Other ID${NC}" echo -e "${RED}-------------|----------|--------|-----------|--------${NC}" while IFS=',' read -r content_type filename primary_lang primary_id other_lang other_id; do echo -e "${RED}$content_type | $filename | $primary_id | $other_lang | $other_id${NC}" done < "$temp_dir/mismatches.csv" echo "" fi # Cleanup rm -rf "$temp_dir" if [ $errors -gt 0 ]; then log_error "$content_type validation failed with $errors errors and $warnings warnings" return 1 elif [ $warnings -gt 0 ]; then log_warning "$content_type validation completed with $warnings warnings" return 0 else log_success "$content_type validation passed - all IDs are consistent" return 0 fi } # Generate fix suggestions generate_fix_suggestions() { local content_type="$1" local primary_lang="en" local primary_dir="${CONTENT_DIR}/${content_type}/${primary_lang}" echo "" log_step "Generating fix suggestions for $content_type..." for primary_file in "$primary_dir"/*.md; do if [ ! -f "$primary_file" ]; then continue fi local filename=$(get_filename "$primary_file") local primary_id=$(extract_id "$primary_file") if [ -z "$primary_id" ]; then continue fi for lang in "${LANGUAGES[@]}"; do if [ "$lang" = "$primary_lang" ]; then continue fi local lang_file="${CONTENT_DIR}/${content_type}/${lang}/${filename}.md" if [ ! -f "$lang_file" ]; then continue fi local lang_id=$(extract_id "$lang_file") if [ -n "$lang_id" ] && [ "$primary_id" != "$lang_id" ]; then echo "" log_info "Fix command for $filename ($lang):" echo " sed -i 's/^id: \"$lang_id\"/id: \"$primary_id\"/' \"$lang_file\"" echo " # OR use your editor to change ID from '$lang_id' to '$primary_id'" fi done done } # Check for duplicate IDs within a language check_duplicate_ids() { local content_type="$1" local lang="$2" local content_dir="${CONTENT_DIR}/${content_type}/${lang}" if [ ! -d "$content_dir" ]; then return 0 fi local temp_file="/tmp/ids_${content_type}_${lang}_$$" # Extract all IDs for md_file in "$content_dir"/*.md; do if [ -f "$md_file" ]; then local id=$(extract_id "$md_file") if [ -n "$id" ]; then echo "$id|$(basename "$md_file")" >> "$temp_file" fi fi done if [ ! -f "$temp_file" ]; then return 0 fi # Check for duplicates local duplicates=$(cut -d'|' -f1 "$temp_file" | sort | uniq -d) if [ -n "$duplicates" ]; then log_error "Duplicate IDs found in $content_type ($lang):" echo "$duplicates" | while read -r duplicate_id; do log_error " ID '$duplicate_id' used in:" grep "^$duplicate_id|" "$temp_file" | cut -d'|' -f2 | while read -r filename; do log_error " - $filename" done done rm -f "$temp_file" return 1 fi rm -f "$temp_file" return 0 } # Main validation function main() { echo "🔍 Content ID Consistency Validator" echo "====================================" echo "" # Get enabled content types dynamically local content_types=($(get_content_types)) log_info "Enabled content types: ${content_types[*]}" echo "" local total_errors=0 local has_suggestions=false # Check for duplicate IDs within each language log_step "Checking for duplicate IDs within languages..." for content_type in "${content_types[@]}"; do for lang in "${LANGUAGES[@]}"; do if ! check_duplicate_ids "$content_type" "$lang"; then total_errors=$((total_errors + 1)) fi done done # Validate cross-language ID consistency for content_type in "${content_types[@]}"; do if ! validate_content_type "$content_type"; then total_errors=$((total_errors + 1)) has_suggestions=true fi done # Generate fix suggestions if there were errors if [ "$has_suggestions" = true ]; then for content_type in "${content_types[@]}"; do generate_fix_suggestions "$content_type" done fi echo "" echo "======================================" if [ $total_errors -eq 0 ]; then log_success "🎉 All content IDs are consistent across languages!" echo "" log_info "✅ Content integrity validated" log_info "✅ No duplicate IDs found" log_info "✅ Cross-language ID consistency confirmed" exit 0 else log_error "❌ Content validation failed with $total_errors issue(s)" echo "" log_error "REQUIRED ACTIONS:" log_error "1. Fix ID mismatches using the suggested commands above" log_error "2. Ensure all content has consistent IDs across languages" log_error "3. Re-run this validator to confirm fixes" log_error "4. Update your content generation process to prevent future mismatches" echo "" log_info "💡 TIP: Use 'id' for cross-language lookup and 'slug' for URLs" log_info "💡 TIP: Add this script to your CI/CD pipeline to catch issues early" exit 1 fi } # Show usage if no arguments if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then echo "Content ID Consistency Validator" echo "" echo "Usage: $0 [OPTIONS]" echo "" echo "This script validates that content IDs are consistent between language versions." echo "It checks all enabled content types in all supported languages." echo "" echo "Options:" echo " --help, -h Show this help message" echo "" echo "What it checks:" echo " ✓ ID consistency across language versions" echo " ✓ Duplicate IDs within the same language" echo " ✓ Missing translations" echo " ✓ Frontmatter format validation" echo "" echo "Integration:" echo " Add to justfile: just validate-content-ids" echo " Add to CI/CD pipeline for automated validation" echo " Run before content deployment" echo "" exit 0 fi # Run main validation main "$@"