website-htmx-rustelo-code/scripts/sh/content/validate-id-consistency.sh
2026-07-10 03:44:13 +01:00

370 lines
No EOL
11 KiB
Bash
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"