Release v1.9.0: Add video-comparer skill and enhance transcript-fixer
## New Skill: video-comparer v1.0.0 - Compare original and compressed videos with interactive HTML reports - Calculate quality metrics (PSNR, SSIM) for compression analysis - Generate frame-by-frame visual comparisons (slider, side-by-side, grid) - Extract video metadata (codec, resolution, bitrate, duration) - Multi-platform FFmpeg support with security features ## transcript-fixer Enhancements - Add async AI processor for parallel processing - Add connection pool management for database operations - Add concurrency manager and rate limiter - Add audit log retention and database migrations - Add health check and metrics monitoring - Add comprehensive test suite (8 new test files) - Enhance security with domain and path validators ## Marketplace Updates - Update marketplace version from 1.8.0 to 1.9.0 - Update skills count from 15 to 16 - Update documentation (README.md, CLAUDE.md, CHANGELOG.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
385
transcript-fixer/scripts/utils/db_migrations_cli.py
Normal file
385
transcript-fixer/scripts/utils/db_migrations_cli.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database Migration CLI - Migration Management Commands
|
||||
|
||||
CRITICAL FIX (P1-6): Production database migration CLI commands
|
||||
|
||||
Features:
|
||||
- Run migrations with dry-run support
|
||||
- Migration status and history
|
||||
- Rollback capability
|
||||
- Migration validation
|
||||
- Migration planning
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
from dataclasses import asdict
|
||||
|
||||
from .database_migration import DatabaseMigrationManager, MigrationRecord, MigrationStatus
|
||||
from .migrations import MIGRATION_REGISTRY, LATEST_VERSION, get_migration, get_migrations_up_to
|
||||
from .config import get_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatabaseMigrationCLI:
|
||||
"""CLI interface for database migrations"""
|
||||
|
||||
def __init__(self, db_path: Path = None):
|
||||
"""
|
||||
Initialize migration CLI
|
||||
|
||||
Args:
|
||||
db_path: Database path (uses config if not provided)
|
||||
"""
|
||||
if db_path is None:
|
||||
config = get_config()
|
||||
db_path = config.database.path
|
||||
|
||||
self.db_path = Path(db_path)
|
||||
self.migration_manager = DatabaseMigrationManager(self.db_path)
|
||||
|
||||
# Register all migrations
|
||||
for migration in MIGRATION_REGISTRY.values():
|
||||
self.migration_manager.register_migration(migration)
|
||||
|
||||
def cmd_status(self, args) -> None:
|
||||
"""
|
||||
Show migration status
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
current_version = self.migration_manager.get_current_version()
|
||||
history = self.migration_manager.get_migration_history()
|
||||
pending = self.migration_manager.get_pending_migrations()
|
||||
|
||||
print("Database Migration Status")
|
||||
print("=" * 40)
|
||||
print(f"Database Path: {self.db_path}")
|
||||
print(f"Current Version: {current_version}")
|
||||
print(f"Latest Version: {LATEST_VERSION}")
|
||||
print(f"Pending Migrations: {len(pending)}")
|
||||
print(f"Total Migrations Applied: {len([h for h in history if h.status == MigrationStatus.COMPLETED])}")
|
||||
|
||||
if pending:
|
||||
print("\nPending Migrations:")
|
||||
for migration in pending:
|
||||
print(f" - {migration.version}: {migration.name}")
|
||||
|
||||
if history:
|
||||
print("\nRecent Migration History:")
|
||||
for i, record in enumerate(history[:5]):
|
||||
status_icon = "✅" if record.status == MigrationStatus.COMPLETED else "❌"
|
||||
print(f" {status_icon} {record.version}: {record.name} ({record.status.value})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting status: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_history(self, args) -> None:
|
||||
"""
|
||||
Show migration history
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
history = self.migration_manager.get_migration_history()
|
||||
|
||||
if not history:
|
||||
print("No migration history found")
|
||||
return
|
||||
|
||||
if args.format == 'json':
|
||||
records = [record.to_dict() for record in history]
|
||||
print(json.dumps(records, indent=2, default=str))
|
||||
else:
|
||||
print("Migration History")
|
||||
print("=" * 40)
|
||||
for record in history:
|
||||
status_icon = {
|
||||
MigrationStatus.COMPLETED: "✅",
|
||||
MigrationStatus.FAILED: "❌",
|
||||
MigrationStatus.ROLLED_BACK: "↩️",
|
||||
MigrationStatus.RUNNING: "⏳",
|
||||
}.get(record.status, "❓")
|
||||
|
||||
print(f"{status_icon} {record.version} ({record.direction.value})")
|
||||
print(f" Name: {record.name}")
|
||||
print(f" Status: {record.status.value}")
|
||||
print(f" Executed: {record.executed_at}")
|
||||
print(f" Duration: {record.execution_time_ms}ms")
|
||||
|
||||
if record.error_message:
|
||||
print(f" Error: {record.error_message}")
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting history: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_migrate(self, args) -> None:
|
||||
"""
|
||||
Run migrations
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
target_version = args.version if args.version else LATEST_VERSION
|
||||
dry_run = args.dry_run
|
||||
force = args.force
|
||||
|
||||
print(f"Running migrations to version: {target_version}")
|
||||
if dry_run:
|
||||
print("🚨 DRY RUN MODE - No changes will be applied")
|
||||
if force:
|
||||
print("🚨 FORCE MODE - Safety checks bypassed")
|
||||
|
||||
# Get migration plan
|
||||
plan = self.migration_manager.get_migration_plan(target_version)
|
||||
|
||||
if not plan:
|
||||
print("✅ No migrations to apply")
|
||||
return
|
||||
|
||||
print(f"\nMigration Plan:")
|
||||
print("=" * 40)
|
||||
for i, step in enumerate(plan, 1):
|
||||
breaking_icon = "🔴" if step.get('is_breaking') else "🟢"
|
||||
print(f"{i}. {breaking_icon} {step['version']}: {step['name']}")
|
||||
print(f" Description: {step['description']}")
|
||||
if step.get('dependencies'):
|
||||
print(f" Dependencies: {', '.join(step['dependencies'])}")
|
||||
if step.get('is_breaking'):
|
||||
print(" ⚠️ Breaking change - may require data migration")
|
||||
print()
|
||||
|
||||
if not args.yes and not dry_run:
|
||||
response = input("Continue with migration? (y/N): ")
|
||||
if response.lower() != 'y':
|
||||
print("Migration cancelled")
|
||||
return
|
||||
|
||||
# Run migration
|
||||
self.migration_manager.migrate_to_version(target_version, dry_run, force)
|
||||
|
||||
if dry_run:
|
||||
print("✅ Dry run completed successfully")
|
||||
else:
|
||||
print("✅ Migration completed successfully")
|
||||
|
||||
# Show new status
|
||||
new_version = self.migration_manager.get_current_version()
|
||||
print(f"Database is now at version: {new_version}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Migration failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_rollback(self, args) -> None:
|
||||
"""
|
||||
Rollback migration
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
target_version = args.version
|
||||
dry_run = args.dry_run
|
||||
force = args.force
|
||||
|
||||
if not target_version:
|
||||
print("❌ Target version is required for rollback")
|
||||
sys.exit(1)
|
||||
|
||||
current_version = self.migration_manager.get_current_version()
|
||||
|
||||
print(f"Rolling back from version {current_version} to {target_version}")
|
||||
if dry_run:
|
||||
print("🚨 DRY RUN MODE - No changes will be applied")
|
||||
if force:
|
||||
print("🚨 FORCE MODE - Safety checks bypassed")
|
||||
|
||||
# Warn about potential data loss
|
||||
if not args.yes and not dry_run:
|
||||
response = input("⚠️ WARNING: Rollback may cause data loss. Continue? (y/N): ")
|
||||
if response.lower() != 'y':
|
||||
print("Rollback cancelled")
|
||||
return
|
||||
|
||||
# Run rollback
|
||||
self.migration_manager.migrate_to_version(target_version, dry_run, force)
|
||||
|
||||
if dry_run:
|
||||
print("✅ Dry run completed successfully")
|
||||
else:
|
||||
print("✅ Rollback completed successfully")
|
||||
|
||||
# Show new status
|
||||
new_version = self.migration_manager.get_current_version()
|
||||
print(f"Database is now at version: {new_version}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Rollback failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_plan(self, args) -> None:
|
||||
"""
|
||||
Show migration plan
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
target_version = args.version if args.version else LATEST_VERSION
|
||||
plan = self.migration_manager.get_migration_plan(target_version)
|
||||
|
||||
if not plan:
|
||||
print("✅ No migrations to apply")
|
||||
return
|
||||
|
||||
print(f"Migration Plan (to version {target_version})")
|
||||
print("=" * 50)
|
||||
|
||||
current_version = self.migration_manager.get_current_version()
|
||||
print(f"Current Version: {current_version}")
|
||||
print(f"Target Version: {target_version}")
|
||||
print()
|
||||
|
||||
for i, step in enumerate(plan, 1):
|
||||
breaking_icon = "🔴" if step.get('is_breaking') else "🟢"
|
||||
rollback_icon = "✅" if step.get('has_rollback') else "❌"
|
||||
|
||||
print(f"{i}. {breaking_icon} {step['version']}: {step['name']}")
|
||||
print(f" Description: {step['description']}")
|
||||
print(f" Rollback: {rollback_icon}")
|
||||
|
||||
if step.get('dependencies'):
|
||||
print(f" Dependencies: {', '.join(step['dependencies'])}")
|
||||
|
||||
print()
|
||||
|
||||
# Safety validation
|
||||
is_safe, issues = self.migration_manager.validate_migration_safety(target_version)
|
||||
if is_safe:
|
||||
print("✅ Migration plan is safe")
|
||||
else:
|
||||
print("⚠️ Safety issues detected:")
|
||||
for issue in issues:
|
||||
print(f" - {issue}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting migration plan: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_validate(self, args) -> None:
|
||||
"""
|
||||
Validate migration safety
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
target_version = args.version if args.version else LATEST_VERSION
|
||||
|
||||
is_safe, issues = self.migration_manager.validate_migration_safety(target_version)
|
||||
|
||||
if is_safe:
|
||||
print("✅ Migration plan is safe")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ Migration safety issues found:")
|
||||
for issue in issues:
|
||||
print(f" - {issue}")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Validation failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_create_migration(self, args) -> None:
|
||||
"""
|
||||
Create a new migration template
|
||||
|
||||
Args:
|
||||
args: Command line arguments
|
||||
"""
|
||||
try:
|
||||
version = args.version
|
||||
name = args.name
|
||||
description = args.description
|
||||
|
||||
if not version or not name:
|
||||
print("❌ Version and name are required")
|
||||
sys.exit(1)
|
||||
|
||||
# Check if migration already exists
|
||||
if version in MIGRATION_REGISTRY:
|
||||
print(f"❌ Migration {version} already exists")
|
||||
sys.exit(1)
|
||||
|
||||
# Create migration template
|
||||
template = f'''
|
||||
# Migration {version}: {name}
|
||||
# Description: {description}
|
||||
|
||||
from __future__ import annotations
|
||||
import sqlite3
|
||||
from typing import Tuple
|
||||
from .database_migration import Migration
|
||||
from utils.migrations import get_migration
|
||||
|
||||
|
||||
def _validate_migration(conn: sqlite3.Connection, migration: Migration) -> Tuple[bool, str]:
|
||||
"""Validate migration"""
|
||||
# Add custom validation logic here
|
||||
return True, "Migration validation passed"
|
||||
|
||||
|
||||
MIGRATION_{version.replace(".", "_")} = Migration(
|
||||
version="{version}",
|
||||
name="{name}",
|
||||
description="{description}",
|
||||
forward_sql=\"\"\"
|
||||
-- Add your forward migration SQL here
|
||||
\"\"\",
|
||||
backward_sql=\"\"\"
|
||||
-- Add your backward migration SQL here (optional)
|
||||
\"\"\",
|
||||
dependencies=["2.2"], # List required migrations
|
||||
check_function=_validate_migration,
|
||||
is_breaking=False # Set to True for breaking changes
|
||||
)
|
||||
|
||||
# Add to MIGRATION_REGISTRY in migrations.py
|
||||
# ALL_MIGRATIONS.append(MIGRATION_{version.replace(".", "_")})
|
||||
# MIGRATION_REGISTRY["{version}"] = MIGRATION_{version.replace(".", "_")}
|
||||
# LATEST_VERSION = "{version}" # Update if this is the latest
|
||||
'''.strip()
|
||||
|
||||
print("Migration Template:")
|
||||
print("=" * 50)
|
||||
print(template)
|
||||
print("\n⚠️ Remember to:")
|
||||
print("1. Add the migration to ALL_MIGRATIONS list in migrations.py")
|
||||
print("2. Update MIGRATION_REGISTRY and LATEST_VERSION")
|
||||
print("3. Test the migration before deploying")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error creating template: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_migration_cli(db_path: Path = None) -> DatabaseMigrationCLI:
|
||||
"""Create migration CLI instance"""
|
||||
return DatabaseMigrationCLI(db_path)
|
||||
Reference in New Issue
Block a user