UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

246 lines (202 loc) • 7.48 kB
#!/usr/bin/env node const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); /** * Supabase Pre-flight Checker * Validates Supabase environment before executing database tasks */ class SupabasePreflightChecker { constructor() { this.checks = { cli: false, linked: false, connection: false, localDb: false, migrations: false, environment: false }; this.warnings = []; this.errors = []; } async runChecks() { console.log('šŸ” Supabase Pre-flight Checks'); console.log('═'.repeat(40)); await this.checkCLI(); await this.checkProjectLinked(); await this.checkConnection(); await this.checkEnvironment(); await this.checkMigrations(); this.showResults(); return this.errors.length === 0; } async checkCLI() { process.stdout.write('āœ“ Checking Supabase CLI installation... '); try { const { stdout } = await execAsync('supabase --version'); this.checks.cli = true; console.log('āœ…'); console.log(` Version: ${stdout.trim()}`); } catch (error) { this.checks.cli = false; this.errors.push('Supabase CLI not installed'); console.log('āŒ'); console.log(' Run: brew install supabase/tap/supabase'); } } async checkProjectLinked() { if (!this.checks.cli) return; process.stdout.write('āœ“ Verifying supabase project linked... '); try { const { stdout } = await execAsync('supabase status'); this.checks.linked = true; // Parse status output const lines = stdout.split('\n'); const projectId = lines.find(l => l.includes('API URL'))?.split(' ').pop(); console.log('āœ…'); if (projectId) { console.log(` Project: ${projectId}`); } } catch (error) { this.checks.linked = false; this.errors.push('No Supabase project linked'); console.log('āŒ'); console.log(' Run: supabase link --project-ref <project-id>'); } } async checkConnection() { if (!this.checks.linked) return; process.stdout.write('āœ“ Checking database connection... '); try { const { stdout } = await execAsync('supabase db remote commit --dry-run'); this.checks.connection = true; console.log('āœ…'); } catch (error) { // If it fails because there are no changes, that's OK if (error.message.includes('no changes')) { this.checks.connection = true; console.log('āœ…'); } else { this.checks.connection = false; this.errors.push('Cannot connect to database'); console.log('āŒ'); console.log(' Check your internet connection and credentials'); } } } async checkEnvironment() { process.stdout.write('āœ“ Validating environment variables... '); const requiredEnvVars = [ 'NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY' ]; const missingVars = requiredEnvVars.filter(varName => !process.env[varName]); if (missingVars.length === 0) { this.checks.environment = true; console.log('āœ…'); } else { this.checks.environment = false; this.warnings.push(`Missing environment variables: ${missingVars.join(', ')}`); console.log('āš ļø'); console.log(` Missing: ${missingVars.join(', ')}`); } // Check if using local or remote database const dbUrl = process.env.SUPABASE_DB_URL || process.env.DATABASE_URL || ''; if (dbUrl.includes('localhost') || dbUrl.includes('127.0.0.1')) { this.checks.localDb = true; console.log(' āœ… Using local database (safe for testing)'); } else if (dbUrl) { this.checks.localDb = false; this.warnings.push('Using remote database - be careful with migrations!'); console.log(' āš ļø Using remote database'); } } async checkMigrations() { if (!this.checks.cli) return; process.stdout.write('āœ“ Checking for uncommitted migrations... '); try { const { stdout } = await execAsync('git status supabase/migrations --porcelain'); if (stdout.trim() === '') { this.checks.migrations = true; console.log('āœ…'); } else { this.checks.migrations = false; this.warnings.push('You have uncommitted migrations'); console.log('āš ļø'); console.log(' Uncommitted files in supabase/migrations/'); console.log(' Consider committing before creating new migrations'); } } catch (error) { // Git not available or not a git repo console.log('āš ļø (Git check skipped)'); } } showResults() { console.log('\nšŸ“Š PRE-FLIGHT RESULTS:'); console.log('─'.repeat(30)); if (this.errors.length > 0) { console.log('\nāŒ ERRORS (must fix):'); this.errors.forEach(error => { console.log(` • ${error}`); }); } if (this.warnings.length > 0) { console.log('\nāš ļø WARNINGS (review):'); this.warnings.forEach(warning => { console.log(` • ${warning}`); }); } if (this.errors.length === 0 && this.warnings.length === 0) { console.log('\nāœ… All checks passed! Ready for Supabase operations.'); } console.log('\nšŸ›”ļø SAFETY RECOMMENDATIONS:'); console.log('• Always test migrations locally first'); console.log('• Use "supabase db reset" to test from scratch'); console.log('• Keep migrations small and focused'); console.log('• Include rollback SQL in comments'); } async generateMigrationCommand(taskDetails) { console.log('\nšŸš€ SUPABASE MIGRATION COMMAND:'); console.log('═'.repeat(40)); const migrationName = taskDetails.specifics?.migrationName || 'new_migration'; const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14); console.log(`\n# Generate migration file:`); console.log(`supabase migration new ${migrationName}\n`); console.log(`# This creates: supabase/migrations/${timestamp}_${migrationName}.sql\n`); if (taskDetails.specifics?.sql) { console.log(`# Add this SQL to the migration file:`); console.log('```sql'); console.log(taskDetails.specifics.sql); console.log('```\n'); } console.log(`# Test locally:`); console.log(`supabase db reset # Recreates database and runs all migrations\n`); console.log(`# Apply to remote (after testing):`); console.log(`supabase db push\n`); if (taskDetails.specifics?.rollback) { console.log(`# Rollback SQL (keep in comments):`); console.log('```sql'); console.log(`-- Rollback: ${migrationName}`); console.log(taskDetails.specifics.rollback); console.log('```'); } } } // Run if called directly if (require.main === module) { const checker = new SupabasePreflightChecker(); // Check if task details provided const taskJson = process.argv[2]; checker.runChecks().then(async (passed) => { if (passed && taskJson) { try { const taskDetails = JSON.parse(taskJson); await checker.generateMigrationCommand(taskDetails); } catch (e) { console.error('Invalid task JSON provided'); } } process.exit(passed ? 0 : 1); }); } module.exports = SupabasePreflightChecker;