Files
gh-greyhaven-ai-claude-code…/skills/deployment-cloudflare/scripts/migrate.py
2025-11-29 18:29:12 +08:00

199 lines
5.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Run database migrations for Grey Haven applications with Doppler.
Supports both Drizzle (TypeScript) and Alembic (Python) migrations
across multiple environments.
Usage:
# Run migrations for development
python scripts/migrate.py --env dev
# Run migrations for staging
python scripts/migrate.py --env staging
# Run migrations for production (with confirmation)
python scripts/migrate.py --env production
# Rollback last migration
python scripts/migrate.py --env dev --rollback
# Rollback to specific migration
python scripts/migrate.py --env dev --rollback --to 20250115_add_users
# Dry run (show what would happen)
python scripts/migrate.py --env production --dry-run
# Use Alembic instead of Drizzle
python scripts/migrate.py --env dev --backend alembic
Always run with --help first to see all options.
"""
import argparse
import subprocess
import sys
from typing import Optional
def run_command(cmd: str, description: str, dry_run: bool = False) -> bool:
"""Run a shell command and return success status."""
print(f"\n{'[DRY RUN] ' if dry_run else ''}{description}")
print(f" Command: {cmd}")
if dry_run:
return True
result = subprocess.run(cmd, shell=True, capture_output=False)
return result.returncode == 0
def confirm_production_migration() -> bool:
"""Ask for confirmation before production migration."""
print("\nWARNING: WARNING: You are about to run migrations on PRODUCTION database")
print("This operation is IRREVERSIBLE and will affect live data.")
response = input("Type 'run production migrations' to confirm: ")
return response.strip().lower() == "run production migrations"
def main():
parser = argparse.ArgumentParser(
description="Run database migrations with Doppler environment variables",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Run Drizzle migrations for development
python scripts/migrate.py --env dev
# Rollback last Drizzle migration
python scripts/migrate.py --env dev --rollback
# Run Alembic migrations for staging
python scripts/migrate.py --env staging --backend alembic
# Rollback to specific Alembic migration
python scripts/migrate.py --env staging --backend alembic --rollback --to abc123
Environments:
dev - Local development database
test - CI/CD test database
staging - Staging database
production - Production database (requires confirmation)
Backends:
drizzle - Drizzle Kit (TypeScript/TanStack Start)
alembic - Alembic (Python/FastAPI)
Doppler Configuration:
Requires doppler CLI configured with appropriate access.
Uses DATABASE_URL_ADMIN from Doppler config.
"""
)
parser.add_argument(
"--env",
required=True,
choices=["dev", "test", "staging", "production"],
help="Environment to run migrations against"
)
parser.add_argument(
"--backend",
default="drizzle",
choices=["drizzle", "alembic"],
help="Migration backend to use (default: drizzle)"
)
parser.add_argument(
"--rollback",
action="store_true",
help="Rollback migrations instead of applying"
)
parser.add_argument(
"--to",
type=str,
help="Rollback to specific migration (use with --rollback)"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would happen without executing"
)
args = parser.parse_args()
# Production confirmation
if args.env == "production" and not args.dry_run and not args.rollback:
if not confirm_production_migration():
print("\nERROR: Production migration cancelled")
sys.exit(1)
env = args.env
backend = args.backend
print(f"\n{'=' * 70}")
print(f" Database Migration - {env.upper()} ({backend.upper()})")
print(f"{'=' * 70}")
# Check Doppler configuration
if not args.dry_run:
result = subprocess.run(
f"doppler secrets get DATABASE_URL_ADMIN --config {env}",
shell=True,
capture_output=True
)
if result.returncode != 0:
print(f"\nERROR: Failed to get DATABASE_URL_ADMIN from Doppler config '{env}'")
print(" Make sure Doppler is configured: doppler setup")
sys.exit(1)
# Construct migration command
if backend == "drizzle":
if args.rollback:
if args.to:
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback --to {args.to}"
else:
cmd = f"doppler run --config {env} -- drizzle-kit migrate:rollback"
description = "Rolling back Drizzle migration"
else:
cmd = f"doppler run --config {env} -- drizzle-kit push:pg"
description = "Applying Drizzle migrations"
elif backend == "alembic":
if args.rollback:
if args.to:
cmd = f"doppler run --config {env} -- alembic downgrade {args.to}"
else:
cmd = f"doppler run --config {env} -- alembic downgrade -1"
description = "Rolling back Alembic migration"
else:
cmd = f"doppler run --config {env} -- alembic upgrade head"
description = "Applying Alembic migrations"
# Run migration
success = run_command(cmd, description, args.dry_run)
if not success:
print(f"\nERROR: Migration failed for {env}")
sys.exit(1)
# Success!
print(f"\n{'=' * 70}")
if args.rollback:
print(f" SUCCESS: Rollback successful for {env.upper()}")
else:
print(f" SUCCESS: Migration successful for {env.upper()}")
print(f"{'=' * 70}")
if not args.rollback:
print("\nNext steps:")
print(" • Verify schema changes in database")
print(" • Run tests: doppler run --config test -- npm run test")
print(" • Deploy application if migrations succeeded")
else:
print("\nNext steps:")
print(" • Verify rollback was successful")
print(" • Re-deploy previous application version if needed")
if __name__ == "__main__":
main()