Major changes: - Replace Payload CMS with Tina CMS (self-hosted) - Add Astro DB for consent logging (PDPA compliant) - Update Tailwind v3 to v4 (@tailwindcss/vite plugin) - Add astro-tina-starter template - Rewrite consent template for Astro (ConsentBanner.astro, Astro DB, Nano Stores) - Add install-tina-backend.sh for self-hosted Tina per customer - Rename convert-astro.sh to migrate-tina.sh - Add AGENTS.md template for generated websites - Delete all Payload/Next.js files Technical updates: - Astro DB using defineDb with eq operators for queries - Tailwind v4 with @theme block - Tina CMS local development mode - Proper Astro API routes for consent Research-verified with official documentation (April 2026)
443 lines
11 KiB
Bash
Executable File
443 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#===============================================================================
|
|
# migrate-tina.sh - Migrate existing websites to Astro + Tina CMS
|
|
#
|
|
# Usage: ./migrate-tina.sh [source-path] [target-path]
|
|
#
|
|
# This script migrates websites to Astro + Tina CMS:
|
|
# - Converts content to Tina CMS format
|
|
# - Sets up Astro DB for consent logging
|
|
# - Adds PDPA-compliant consent system
|
|
# - Preserves content and structure
|
|
#
|
|
# Requirements:
|
|
# - node.js 20+
|
|
# - npm
|
|
# - git
|
|
#
|
|
#===============================================================================
|
|
|
|
set -e
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
SOURCE_PATH="${1:-}"
|
|
TARGET_PATH="${2:-.}"
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
|
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
print_usage() {
|
|
cat << EOF
|
|
Usage: $(basename "$0") [source-path] [target-path]
|
|
|
|
Migrate existing website to Astro + Tina CMS
|
|
|
|
Arguments:
|
|
source-path Path to existing website project
|
|
target-path Path for the migrated Astro + Tina project
|
|
|
|
Examples:
|
|
$(basename "$0") /path/to/existing-site /path/to/migrated-site
|
|
|
|
Features:
|
|
- Detects source website technology (Astro, Next.js, etc.)
|
|
- Converts content to Tina CMS format
|
|
- Sets up Astro DB for consent logging (PDPA compliant)
|
|
- Adds cookie consent banner with Thai law compliance
|
|
- Preserves SEO metadata and content structure
|
|
|
|
EOF
|
|
}
|
|
|
|
detect_source_type() {
|
|
log_info "Detecting source website type..."
|
|
|
|
cd "$SOURCE_PATH"
|
|
|
|
if [ -f "astro.config.mjs" ] || [ -f "astro.config.ts" ]; then
|
|
SOURCE_TYPE="astro"
|
|
log_success "Detected: Astro"
|
|
elif [ -f "next.config.js" ] || [ -f "next.config.mjs" ] || [ -f "package.json" ] && grep -q "next" package.json 2>/dev/null; then
|
|
SOURCE_TYPE="nextjs"
|
|
log_success "Detected: Next.js"
|
|
elif [ -f "package.json" ] && grep -q "remix" package.json 2>/dev/null; then
|
|
SOURCE_TYPE="remix"
|
|
log_success "Detected: Remix"
|
|
elif [ -d "src/content" ] || [ -d "content/posts" ]; then
|
|
SOURCE_TYPE="generic"
|
|
log_success "Detected: Generic static site"
|
|
else
|
|
log_warning "Could not detect source type, assuming generic"
|
|
SOURCE_TYPE="generic"
|
|
fi
|
|
}
|
|
|
|
analyze_source_content() {
|
|
log_info "Analyzing source content..."
|
|
|
|
cd "$SOURCE_PATH"
|
|
|
|
local md_count=$(find . -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | grep -v node_modules | wc -l)
|
|
local astro_count=$(find . -type f -name "*.astro" 2>/dev/null | grep -v node_modules | wc -l)
|
|
local pages_count=$(find . -type f \( -name "*.tsx" -o -name "*.jsx" \) 2>/dev/null | grep -v node_modules | grep -E "pages/|app/" | wc -l)
|
|
|
|
echo ""
|
|
echo " Analysis Results:"
|
|
echo " ─────────────────"
|
|
echo " Markdown/MDX files: $md_count"
|
|
echo " Astro components: $astro_count"
|
|
echo " Pages (tsx/jsx): $pages_count"
|
|
echo ""
|
|
|
|
# List sample content files
|
|
if [ $md_count -gt 0 ]; then
|
|
echo " Sample content files:"
|
|
find . -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | grep -v node_modules | head -5 | while read -r f; do
|
|
echo " - $f"
|
|
done
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
copy_template() {
|
|
log_info "Copying Astro+Tina template..."
|
|
|
|
local template_dir="$(dirname "$(dirname "$(readlink -f "$0")")")/templates/astro-tina-starter"
|
|
|
|
if [ ! -d "$template_dir" ]; then
|
|
log_error "Template not found: $template_dir"
|
|
exit 1
|
|
fi
|
|
|
|
cp -r "$template_dir"/* "$TARGET_PATH/"
|
|
cp -r "$template_dir"/.* "$TARGET_PATH/" 2>/dev/null || true
|
|
|
|
log_success "Template copied to: $TARGET_PATH"
|
|
}
|
|
|
|
migrate_content() {
|
|
log_info "Migrating content to Tina format..."
|
|
|
|
cd "$SOURCE_PATH"
|
|
|
|
# Detect content directory
|
|
local content_dir=""
|
|
if [ -d "src/content" ]; then
|
|
content_dir="src/content"
|
|
elif [ -d "content" ]; then
|
|
content_dir="content"
|
|
elif [ -d "content/posts" ]; then
|
|
content_dir="content/posts"
|
|
fi
|
|
|
|
if [ -z "$content_dir" ]; then
|
|
log_warning "No content directory found, creating default structure"
|
|
mkdir -p "$TARGET_PATH/src/content"
|
|
return
|
|
fi
|
|
|
|
# Create Tina content directory
|
|
mkdir -p "$TARGET_PATH/src/content"
|
|
|
|
# Copy markdown/mdx files
|
|
find "$content_dir" -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | while read -r file; do
|
|
local relative_path="${file#$SOURCE_PATH/$content_dir/}"
|
|
local target_file="$TARGET_PATH/src/content/$relative_path"
|
|
|
|
mkdir -p "$(dirname "$target_file")"
|
|
cp "$file" "$target_file"
|
|
|
|
echo " Migrated: $relative_path"
|
|
done
|
|
|
|
log_success "Content migration complete"
|
|
}
|
|
|
|
add_consent_system() {
|
|
log_info "Adding PDPA-compliant consent system..."
|
|
|
|
local consent_template="$(dirname "$(dirname "$(readlink -f "$0")")")/templates/consent"
|
|
|
|
if [ ! -d "$consent_template" ]; then
|
|
log_warning "Consent template not found, skipping"
|
|
return
|
|
fi
|
|
|
|
# Copy consent files
|
|
cp -r "$consent_template"/* "$TARGET_PATH/src/components/consent/" 2>/dev/null || true
|
|
|
|
log_success "Consent system added"
|
|
}
|
|
|
|
create_tina_schema() {
|
|
log_info "Creating Tina CMS schema..."
|
|
|
|
cd "$TARGET_PATH"
|
|
|
|
# Ensure .tina directory exists
|
|
mkdir -p .tina
|
|
|
|
# Create or update schema
|
|
cat > .tina/schema.ts << 'EOF'
|
|
import { defineSchema, config } from 'tinacms'
|
|
|
|
// Your content collections
|
|
const schema = defineSchema({
|
|
collections: [
|
|
{
|
|
name: 'post',
|
|
label: 'Posts',
|
|
path: 'src/content/posts',
|
|
fields: [
|
|
{
|
|
type: 'string',
|
|
name: 'title',
|
|
label: 'Title',
|
|
required: true,
|
|
},
|
|
{
|
|
type: 'string',
|
|
name: 'slug',
|
|
label: 'Slug',
|
|
required: true,
|
|
},
|
|
{
|
|
type: 'datetime',
|
|
name: 'date',
|
|
label: 'Date',
|
|
},
|
|
{
|
|
type: 'string',
|
|
name: 'author',
|
|
label: 'Author',
|
|
},
|
|
{
|
|
type: 'string',
|
|
name: 'image',
|
|
label: 'Featured Image',
|
|
},
|
|
{
|
|
type: 'string',
|
|
name: 'description',
|
|
label: 'Description',
|
|
},
|
|
{
|
|
type: 'rich-text',
|
|
name: 'body',
|
|
label: 'Body',
|
|
isBody: true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'page',
|
|
label: 'Pages',
|
|
path: 'src/content/pages',
|
|
fields: [
|
|
{
|
|
type: 'string',
|
|
name: 'title',
|
|
label: 'Title',
|
|
required: true,
|
|
},
|
|
{
|
|
type: 'string',
|
|
name: 'slug',
|
|
label: 'Slug',
|
|
required: true,
|
|
},
|
|
{
|
|
type: 'rich-text',
|
|
name: 'body',
|
|
label: 'Body',
|
|
isBody: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
|
|
export default config({
|
|
schema,
|
|
// Other config options
|
|
})
|
|
EOF
|
|
|
|
log_success "Tina schema created"
|
|
}
|
|
|
|
create_migration_report() {
|
|
log_info "Creating migration report..."
|
|
|
|
cd "$SOURCE_PATH"
|
|
|
|
local md_count=$(find . -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | grep -v node_modules | wc -l)
|
|
|
|
cat > "$TARGET_PATH/MIGRATION_REPORT.md" << EOF
|
|
# Migration Report: → Astro + Tina CMS
|
|
|
|
## Source
|
|
- **Original Type:** $SOURCE_TYPE
|
|
- **Path:** $SOURCE_PATH
|
|
- **Date:** $(date)
|
|
|
|
## Statistics
|
|
- **Content Files Migrated:** $md_count
|
|
|
|
## What's Included
|
|
|
|
### ✅ Astro 6.1.7
|
|
Modern static site framework with excellent performance.
|
|
|
|
### ✅ Tina CMS
|
|
Self-hosted Git-based CMS for visual content editing.
|
|
|
|
### ✅ Tailwind CSS 4.x
|
|
Latest Tailwind with @tailwindcss/vite plugin.
|
|
|
|
### ✅ Astro DB
|
|
Built-in database for consent logging and dynamic content.
|
|
|
|
### ✅ PDPA Consent System
|
|
Thai Personal Data Protection Act compliant cookie consent:
|
|
- Cookie banner with Accept/Reject/Preferences
|
|
- Consent logging in Astro DB
|
|
- API endpoint for consent management
|
|
|
|
### ✅ Nano Stores
|
|
Lightweight client-side state management.
|
|
|
|
## Project Structure
|
|
|
|
\`\`\`
|
|
$TARGET_PATH/
|
|
├── src/
|
|
│ ├── components/
|
|
│ │ └── consent/ # PDPA consent system
|
|
│ ├── content/
|
|
│ │ ├── posts/ # Blog posts (Tina managed)
|
|
│ │ └── pages/ # Static pages (Tina managed)
|
|
│ ├── layouts/
|
|
│ │ └── Layout.astro
|
|
│ ├── pages/
|
|
│ │ └── index.astro
|
|
│ └── styles/
|
|
│ └── global.css
|
|
├── .tina/
|
|
│ └── schema.ts # Tina content schema
|
|
├── db/
|
|
│ └── config.ts # Astro DB config
|
|
├── Dockerfile
|
|
└── AGENTS.md # AI agent instructions
|
|
\`\`\`
|
|
|
|
## Next Steps
|
|
|
|
1. **Install dependencies:**
|
|
\`\`\`bash
|
|
cd $TARGET_PATH
|
|
npm install
|
|
\`\`\`
|
|
|
|
2. **Set up environment:**
|
|
\`\`\`bash
|
|
cp .env.example .env
|
|
# Edit .env with your settings
|
|
\`\`\`
|
|
|
|
3. **Start development:**
|
|
\`\`\`bash
|
|
npm run dev
|
|
\`\`\`
|
|
|
|
4. **Access Tina Admin:**
|
|
- Visit \`http://localhost:4321/admin\` (when in dev mode)
|
|
- Or \`http://localhost:4321/___tina\` for direct access
|
|
|
|
5. **Configure Tina Backend** (for production):
|
|
\`\`\`bash
|
|
./scripts/install-tina-backend.sh
|
|
\`\`\`
|
|
|
|
## Tina CMS Setup
|
|
|
|
For production, you'll need to set up the Tina backend:
|
|
\`\`\`bash
|
|
./scripts/install-tina-backend.sh
|
|
\`\`\`
|
|
|
|
This will install:
|
|
- Auth.js for authentication
|
|
- Database adapter for content storage
|
|
- Git provider for content management
|
|
|
|
## PDPA Compliance
|
|
|
|
The consent system logs:
|
|
- User consent choices (accept/reject)
|
|
- Cookie categories (analytics, marketing, functional)
|
|
- Timestamp and user agent
|
|
- IP address (for compliance auditing)
|
|
|
|
Logs are stored in Astro DB and can be exported for compliance reporting.
|
|
EOF
|
|
|
|
log_success "Migration report: $TARGET_PATH/MIGRATION_REPORT.md"
|
|
}
|
|
|
|
main() {
|
|
echo "=============================================="
|
|
echo " Website → Astro + Tina CMS Migration Tool"
|
|
echo "=============================================="
|
|
echo ""
|
|
|
|
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
|
print_usage
|
|
exit 0
|
|
fi
|
|
|
|
if [ -z "$SOURCE_PATH" ]; then
|
|
print_usage
|
|
echo ""
|
|
log_error "Please specify source path"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$SOURCE_PATH" ]; then
|
|
log_error "Source path not found: $SOURCE_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$TARGET_PATH" ]; then
|
|
mkdir -p "$TARGET_PATH"
|
|
fi
|
|
|
|
detect_source_type
|
|
analyze_source_content
|
|
copy_template
|
|
migrate_content
|
|
add_consent_system
|
|
create_tina_schema
|
|
create_migration_report
|
|
|
|
echo ""
|
|
echo "=============================================="
|
|
log_success "Migration complete!"
|
|
echo "=============================================="
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. cd $TARGET_PATH"
|
|
echo " 2. npm install"
|
|
echo " 3. npm run dev"
|
|
echo " 4. See MIGRATION_REPORT.md for details"
|
|
echo ""
|
|
}
|
|
|
|
main "$@" |