/** * Migrate Status Command * * Show the current migration status of a database. * * @module cli/commands/migrate-status */ import { resolve } from 'node:path' import { existsSync } from 'node:fs' import type { MigrateCLIOptions } from '../types.js' import { formatSuccess, formatError, formatInfo, formatWarning, formatTable } from '../formatting.js' import { loadMigrationsFromDirectory, createQueryExecutor } from '../utils.js' /** * Show migration status command */ export async function runMigrateStatus(options: MigrateCLIOptions): Promise { const url = options.url || process.env['DATABASE_URL'] if (!url) { console.error(formatError('Database URL is required')) console.error(formatInfo('Provide --url or set DATABASE_URL environment variable')) process.exit(1) } // Load migrations from directory const migrationsDir = resolve(process.cwd(), options.migrationsDir || './migrations') if (!existsSync(migrationsDir)) { console.error(formatError(`Migrations directory not found: ${migrationsDir}`)) console.error(formatInfo('Create a migrations directory or specify one with --migrations-dir')) process.exit(1) } try { const migrations = loadMigrationsFromDirectory(migrationsDir) // Import migration system dynamically const { createMigrationRegistry, createMigrationRunner } = await import('@dotdo/postgres/migrations') // Create registry - cast MigrationDefinition to Migration (structurally compatible) const registry = createMigrationRegistry({ migrations: migrations as import('@dotdo/postgres/migrations').Migration[], validateOnCreate: false, // Allow empty migrations allowVersionGaps: true, }) // Connect to database console.log(formatInfo('Connecting to database...')) const executor = await createQueryExecutor(url) // Create runner - use conditional spreading for optional properties const runner = createMigrationRunner(executor, registry, { ...(options.verbose ? { debug: options.verbose } : {}), }) // Get schema version const schemaVersion = await runner.getSchemaVersion() const comparison = await runner.compareVersion() // Output result if (options.json) { console.log(JSON.stringify({ currentVersion: schemaVersion.currentVersion, appliedCount: schemaVersion.appliedCount, lastMigrationAt: schemaVersion.lastMigrationAt, pendingCount: comparison.pendingCount, needsMigration: comparison.needsMigration, registeredMigrations: migrations.length, migrations: schemaVersion.migrations, pendingVersions: comparison.missingVersions, }, null, 2)) } else { console.log('') console.log(formatInfo('Migration Status')) console.log('================') console.log('') console.log(`Current Version: ${schemaVersion.currentVersion}`) console.log(`Applied Migrations: ${schemaVersion.appliedCount}`) console.log(`Pending Migrations: ${comparison.pendingCount}`) console.log(`Last Migration: ${schemaVersion.lastMigrationAt || 'Never'}`) console.log('') if (comparison.needsMigration) { console.log(formatWarning(`${comparison.pendingCount} pending migration(s) to apply:`)) for (const version of comparison.missingVersions) { const migration = migrations.find((m) => m.version === version) console.log(` - [${version}] ${migration?.name || 'Unknown'}`) } console.log('') console.log(formatInfo('Run `npx postgres.do migrate` to apply pending migrations')) } else { console.log(formatSuccess('Database is up to date')) } // Show applied migrations table if verbose if (options.verbose && schemaVersion.migrations.length > 0) { console.log('') console.log(formatInfo('Applied Migrations:')) const headers = ['Version', 'ID', 'Status', 'Applied At'] const rows = schemaVersion.migrations.map((m) => [ m.version.toString(), m.id, m.status, m.applied_at, ]) console.log(formatTable(headers, rows)) } // Show registered migrations if verbose if (options.verbose && migrations.length > 0) { console.log('') console.log(formatInfo('Registered Migrations:')) const headers = ['Version', 'ID', 'Name', 'Reversible'] const rows = migrations.map((m) => [ m.version.toString(), m.id, m.name, m.down ? 'Yes' : 'No', ]) console.log(formatTable(headers, rows)) } } // Cleanup if ('end' in executor && typeof executor.end === 'function') { await executor.end() } } catch (error) { console.error(formatError('Status check error:'), error instanceof Error ? error.message : error) if (options.verbose && error instanceof Error && error.stack) { console.error(error.stack) } process.exit(1) } }