272 lines
8.4 KiB
Bash
272 lines
8.4 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
|
||
|
|
# Content Migration Script - Category-Based Structure
|
||
|
|
# Migrates existing flat content structure to category-based organization
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
|
|
CONTENT_DIR="${PROJECT_ROOT}/content"
|
||
|
|
BACKUP_DIR="${PROJECT_ROOT}/content-backup-$(date +%Y%m%d_%H%M%S)"
|
||
|
|
|
||
|
|
# Colors for output
|
||
|
|
GREEN='\033[0;32m'
|
||
|
|
BLUE='\033[0;34m'
|
||
|
|
YELLOW='\033[1;33m'
|
||
|
|
RED='\033[0;31m'
|
||
|
|
NC='\033[0m'
|
||
|
|
|
||
|
|
echo -e "${BLUE}🔄 Content Migration to Category Structure${NC}"
|
||
|
|
echo -e "${BLUE}=========================================${NC}"
|
||
|
|
|
||
|
|
# Create backup
|
||
|
|
echo -e "${YELLOW}📋 Creating backup at: $BACKUP_DIR${NC}"
|
||
|
|
cp -r "$CONTENT_DIR" "$BACKUP_DIR"
|
||
|
|
echo -e "${GREEN}✅ Backup created${NC}"
|
||
|
|
|
||
|
|
# Get available content types from content-kinds.toml
|
||
|
|
get_content_types() {
|
||
|
|
local config_file="$CONTENT_DIR/content-kinds.toml"
|
||
|
|
if [ -f "$config_file" ]; then
|
||
|
|
# Extract enabled content types
|
||
|
|
grep -A 5 '\[\[content_kinds\]\]' "$config_file" | grep -E '^(name|enabled)' | \
|
||
|
|
awk '/name =/ {name=$3; gsub(/["]/, "", name)} /enabled = true/ {print name}' | \
|
||
|
|
grep -v '^$'
|
||
|
|
else
|
||
|
|
echo "blog recipes" # Fallback
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get available languages by checking locales directory
|
||
|
|
get_languages() {
|
||
|
|
if [ -d "$CONTENT_DIR/locales" ]; then
|
||
|
|
ls "$CONTENT_DIR/locales" | grep -E '^[a-z]{2}$' | tr '\n' ' '
|
||
|
|
else
|
||
|
|
echo "en es" # Fallback
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Extract frontmatter from markdown file
|
||
|
|
extract_frontmatter() {
|
||
|
|
local file="$1"
|
||
|
|
if head -1 "$file" | grep -q "^---$"; then
|
||
|
|
awk '/^---$/{flag++; next} flag==1 && /^---$/{flag++} flag==1{print}' "$file"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get category from frontmatter
|
||
|
|
get_category() {
|
||
|
|
local file="$1"
|
||
|
|
local frontmatter=$(extract_frontmatter "$file")
|
||
|
|
echo "$frontmatter" | grep -E '^category:' | sed 's/category:[[:space:]]*//' | tr -d '"' | xargs
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get post ID from frontmatter
|
||
|
|
get_post_id() {
|
||
|
|
local file="$1"
|
||
|
|
local frontmatter=$(extract_frontmatter "$file")
|
||
|
|
local id=$(echo "$frontmatter" | grep -E '^id:' | sed 's/id:[[:space:]]*//' | tr -d '"' | xargs)
|
||
|
|
|
||
|
|
# If no ID found, use filename without extension
|
||
|
|
if [ -z "$id" ]; then
|
||
|
|
basename "$file" .md
|
||
|
|
else
|
||
|
|
echo "$id"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get emoji for category from root meta.toml
|
||
|
|
get_category_emoji() {
|
||
|
|
local content_type="$1"
|
||
|
|
local lang="$2"
|
||
|
|
local category="$3"
|
||
|
|
local root_meta="$CONTENT_DIR/$content_type/$lang/meta.toml"
|
||
|
|
|
||
|
|
if [ -f "$root_meta" ]; then
|
||
|
|
# Extract emoji from [categories_emojis] section
|
||
|
|
local emoji=$(awk -v cat="$category" '
|
||
|
|
/^\[categories_emojis\]/ { in_section = 1; next }
|
||
|
|
/^\[/ && !/^\[categories_emojis\]/ { in_section = 0; next }
|
||
|
|
in_section && $0 ~ "^" cat " *= *\".*\"$" {
|
||
|
|
gsub(/^[^"]*"/, "", $0);
|
||
|
|
gsub(/".*$/, "", $0);
|
||
|
|
print $0;
|
||
|
|
exit
|
||
|
|
}
|
||
|
|
' "$root_meta")
|
||
|
|
|
||
|
|
# Return emoji or default
|
||
|
|
if [ -n "$emoji" ]; then
|
||
|
|
echo "$emoji"
|
||
|
|
else
|
||
|
|
echo "📝" # Default fallback
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
echo "📝" # Default fallback
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create category meta.toml if it doesn't exist
|
||
|
|
create_category_meta() {
|
||
|
|
local content_type="$1"
|
||
|
|
local lang="$2"
|
||
|
|
local category="$3"
|
||
|
|
local category_dir="$CONTENT_DIR/$content_type/$lang/$category"
|
||
|
|
local meta_file="$category_dir/meta.toml"
|
||
|
|
|
||
|
|
if [ ! -f "$meta_file" ]; then
|
||
|
|
mkdir -p "$category_dir"
|
||
|
|
|
||
|
|
# Generate title from category slug
|
||
|
|
local title=$(echo "$category" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')
|
||
|
|
|
||
|
|
# Get emoji from root meta.toml
|
||
|
|
local emoji=$(get_category_emoji "$content_type" "$lang" "$category")
|
||
|
|
|
||
|
|
cat > "$meta_file" << EOF
|
||
|
|
slug = "$category"
|
||
|
|
title = "$title"
|
||
|
|
emoji = "$emoji"
|
||
|
|
published = true
|
||
|
|
sort_order = 1
|
||
|
|
description = "$title content"
|
||
|
|
css_class = "category-$category"
|
||
|
|
css_style = ""
|
||
|
|
|
||
|
|
[translations]
|
||
|
|
# Add translations here when available
|
||
|
|
EOF
|
||
|
|
echo -e "${GREEN}📝 Created meta.toml for category: $category ($lang) with emoji: $emoji${NC}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Migrate posts for a content type and language
|
||
|
|
migrate_content_type() {
|
||
|
|
local content_type="$1"
|
||
|
|
local lang="$2"
|
||
|
|
local source_dir="$CONTENT_DIR/$content_type/$lang"
|
||
|
|
|
||
|
|
echo -e "${BLUE}🔄 Migrating $content_type ($lang)...${NC}"
|
||
|
|
|
||
|
|
if [ ! -d "$source_dir" ]; then
|
||
|
|
echo -e "${YELLOW}⚠️ Directory not found: $source_dir${NC}"
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Track categories and post counts
|
||
|
|
local categories=""
|
||
|
|
local posts_migrated=0
|
||
|
|
local posts_uncategorized=0
|
||
|
|
|
||
|
|
# Process all markdown files in the source directory
|
||
|
|
for file in "$source_dir"/*.md; do
|
||
|
|
[ ! -f "$file" ] && continue
|
||
|
|
|
||
|
|
local filename=$(basename "$file")
|
||
|
|
local post_id=$(get_post_id "$file")
|
||
|
|
local category=$(get_category "$file")
|
||
|
|
|
||
|
|
# Use 'uncategorized' if no category found
|
||
|
|
if [ -z "$category" ]; then
|
||
|
|
category="uncategorized"
|
||
|
|
((posts_uncategorized++))
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Add category to list if not already present
|
||
|
|
if ! echo "$categories" | grep -q "$category"; then
|
||
|
|
categories="$categories $category"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create category directory and meta.toml
|
||
|
|
create_category_meta "$content_type" "$lang" "$category"
|
||
|
|
|
||
|
|
# Determine target file path
|
||
|
|
local target_dir="$CONTENT_DIR/$content_type/$lang/$category"
|
||
|
|
local target_file="$target_dir/${post_id}.md"
|
||
|
|
|
||
|
|
# Move file if it's not already in the right place
|
||
|
|
if [ "$file" != "$target_file" ]; then
|
||
|
|
mv "$file" "$target_file"
|
||
|
|
echo " 📄 Moved: $filename → $category/${post_id}.md"
|
||
|
|
((posts_migrated++))
|
||
|
|
else
|
||
|
|
echo " ✅ Already in place: $category/${post_id}.md"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# Summary for this content type/language
|
||
|
|
if [ $posts_migrated -gt 0 ]; then
|
||
|
|
echo -e "${GREEN}✅ Migrated $posts_migrated posts for $content_type ($lang)${NC}"
|
||
|
|
echo -e " 📁 Categories:$categories"
|
||
|
|
[ $posts_uncategorized -gt 0 ] && echo -e "${YELLOW} ⚠️ $posts_uncategorized posts moved to 'uncategorized'${NC}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Main migration process
|
||
|
|
main() {
|
||
|
|
echo -e "${BLUE}📋 Scanning content structure...${NC}"
|
||
|
|
|
||
|
|
# Get content types and languages
|
||
|
|
CONTENT_TYPES=($(get_content_types))
|
||
|
|
LANGUAGES=($(get_languages))
|
||
|
|
|
||
|
|
echo -e "${BLUE}Content types: ${CONTENT_TYPES[*]}${NC}"
|
||
|
|
echo -e "${BLUE}Languages: ${LANGUAGES[*]}${NC}"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Migrate each content type for each language
|
||
|
|
for content_type in "${CONTENT_TYPES[@]}"; do
|
||
|
|
for lang in "${LANGUAGES[@]}"; do
|
||
|
|
migrate_content_type "$content_type" "$lang"
|
||
|
|
done
|
||
|
|
echo ""
|
||
|
|
done
|
||
|
|
|
||
|
|
echo -e "${GREEN}🎉 Migration completed!${NC}"
|
||
|
|
echo -e "${BLUE}📁 Backup saved at: $BACKUP_DIR${NC}"
|
||
|
|
echo -e "${BLUE}💡 Next steps:${NC}"
|
||
|
|
echo " 1. Run 'just content-build' to rebuild with new structure"
|
||
|
|
echo " 2. Test content discovery and routing"
|
||
|
|
echo " 3. Update any hardcoded content paths in code"
|
||
|
|
echo " 4. Verify translation links work correctly"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check if running in correct directory
|
||
|
|
if [ ! -f "$CONTENT_DIR/content-kinds.toml" ]; then
|
||
|
|
echo -e "${RED}❌ Error: Not in project root or content-kinds.toml not found${NC}"
|
||
|
|
echo " Run this script from the project root directory"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Run migration
|
||
|
|
main
|
||
|
|
|
||
|
|
echo -e "${BLUE}🔍 Migration summary saved to info/summaries/${NC}"
|
||
|
|
mkdir -p info/summaries
|
||
|
|
cat > info/summaries/content-migration-$(date +%Y%m%d_%H%M%S).md << EOF
|
||
|
|
# Content Migration Summary
|
||
|
|
|
||
|
|
**Date:** $(date)
|
||
|
|
**Script:** migrate-to-category-structure.sh
|
||
|
|
**Backup:** $BACKUP_DIR
|
||
|
|
|
||
|
|
## Changes Made
|
||
|
|
- Reorganized content into category-based directories
|
||
|
|
- Created category meta.toml files for styling and metadata
|
||
|
|
- Ensured filenames match frontmatter ID fields
|
||
|
|
- Moved uncategorized posts to 'uncategorized' directories
|
||
|
|
|
||
|
|
## Content Types Processed
|
||
|
|
$(printf "- %s\\n" "${CONTENT_TYPES[@]}")
|
||
|
|
|
||
|
|
## Languages Processed
|
||
|
|
$(printf "- %s\\n" "${LANGUAGES[@]}")
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
1. Run \`just content-build\` to rebuild content indexes
|
||
|
|
2. Test routing with new URL patterns
|
||
|
|
3. Verify translation discovery works
|
||
|
|
4. Update any hardcoded content references
|
||
|
|
EOF
|