Skip to main content
Glama
migrate.ts12.4 kB
#!/usr/bin/env npx tsx /** * ThoughtMCP Database Migration CLI * * Production-ready migration tool for managing database schema changes. * * Usage: * npx tsx scripts/migrate.ts <command> [options] * * Commands: * status Show current migration status * up Apply all pending migrations * down <ver> Rollback to specific version * validate Validate all migration files * dry-run Show what migrations would be applied * reset Reset database (development only) * * Environment Variables: * DB_HOST Database host (default: localhost) * DB_PORT Database port (default: 5432) * DB_NAME Database name (required) * DB_USER Database user (required) * DB_PASSWORD Database password (required) */ import { dirname, join } from "path"; import { fileURLToPath } from "url"; import { DatabaseConnectionManager } from "../src/database/connection-manager.js"; import { MigrationValidator } from "../src/database/migration-validator.js"; import { SchemaMigrationSystem } from "../src/database/schema-migration.js"; // ES module __dirname equivalent const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Colors for terminal output const colors = { reset: "\x1b[0m", red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m", cyan: "\x1b[36m", }; function log(message: string, color?: keyof typeof colors): void { if (color) { console.log(`${colors[color]}${message}${colors.reset}`); } else { console.log(message); } } function logError(message: string): void { console.error(`${colors.red}[ERROR]${colors.reset} ${message}`); } function logSuccess(message: string): void { console.log(`${colors.green}[SUCCESS]${colors.reset} ${message}`); } function logWarning(message: string): void { console.log(`${colors.yellow}[WARNING]${colors.reset} ${message}`); } function logInfo(message: string): void { console.log(`${colors.blue}[INFO]${colors.reset} ${message}`); } /** * Get database configuration from environment */ function getDatabaseConfig(): { host: string; port: number; database: string; user: string; password: string; } { const host = process.env.DB_HOST || "localhost"; const port = parseInt(process.env.DB_PORT || "5432", 10); const database = process.env.DB_NAME || process.env.DATABASE_NAME; const user = process.env.DB_USER || process.env.DATABASE_USER; const password = process.env.DB_PASSWORD || process.env.DATABASE_PASSWORD; if (!database) { throw new Error("DB_NAME environment variable is required"); } if (!user) { throw new Error("DB_USER environment variable is required"); } if (!password) { throw new Error("DB_PASSWORD environment variable is required"); } return { host, port, database, user, password }; } /** * Show migration status */ async function showStatus(): Promise<void> { const config = getDatabaseConfig(); const dbManager = new DatabaseConnectionManager(config); try { await dbManager.connect(); const migrationSystem = new SchemaMigrationSystem(dbManager); const currentVersion = await migrationSystem.getCurrentVersion(); log("\n📊 Database Migration Status", "cyan"); log("═".repeat(40)); log(`Database: ${config.database}`); log(`Host: ${config.host}:${config.port}`); log(`Current Version: ${currentVersion}`); // Get migration info const migrationsDir = join(__dirname, "../src/database/migrations"); const validator = new MigrationValidator(migrationsDir); const files = validator.getMigrationFiles(); log("\n📁 Migration Files:"); for (const file of files) { const status = file.version <= currentVersion ? "✅" : "⏳"; log(` ${status} ${file.version}. ${file.name}`); } const pendingCount = files.filter((f) => f.version > currentVersion).length; if (pendingCount > 0) { logWarning(`\n${pendingCount} pending migration(s)`); } else { logSuccess("\nDatabase is up to date"); } } finally { await dbManager.disconnect(); } } /** * Apply pending migrations */ async function migrateUp(): Promise<void> { const config = getDatabaseConfig(); const dbManager = new DatabaseConnectionManager(config); try { await dbManager.connect(); const migrationSystem = new SchemaMigrationSystem(dbManager); const versionBefore = await migrationSystem.getCurrentVersion(); logInfo(`Current version: ${versionBefore}`); logInfo("Applying pending migrations...\n"); await migrationSystem.runMigrations(); const versionAfter = await migrationSystem.getCurrentVersion(); if (versionAfter > versionBefore) { logSuccess(`\nMigrated from version ${versionBefore} to ${versionAfter}`); } else { logSuccess("\nDatabase is already up to date"); } } finally { await dbManager.disconnect(); } } /** * Rollback to specific version */ async function migrateDown(targetVersion: number): Promise<void> { const config = getDatabaseConfig(); const dbManager = new DatabaseConnectionManager(config); try { await dbManager.connect(); const migrationSystem = new SchemaMigrationSystem(dbManager); const currentVersion = await migrationSystem.getCurrentVersion(); if (targetVersion >= currentVersion) { logWarning( `Target version ${targetVersion} is not less than current version ${currentVersion}` ); return; } logWarning(`⚠️ Rolling back from version ${currentVersion} to ${targetVersion}`); logWarning("This operation may result in data loss!\n"); // In production, we'd want confirmation here // For now, proceed with rollback await migrationSystem.rollbackMigration(targetVersion); const newVersion = await migrationSystem.getCurrentVersion(); logSuccess(`\nRolled back to version ${newVersion}`); } finally { await dbManager.disconnect(); } } /** * Validate migration files */ async function validateMigrations(): Promise<void> { const migrationsDir = join(__dirname, "../src/database/migrations"); const validator = new MigrationValidator(migrationsDir); log("\n🔍 Validating Migration Files", "cyan"); log("═".repeat(40)); // Get migrations from SchemaMigrationSystem const migrations = [ { version: 1, name: "initial_schema", upFile: "001_initial_schema.sql", downFile: "001_initial_schema_down.sql", }, { version: 2, name: "create_indexes", upFile: "002_create_indexes.sql", downFile: "002_create_indexes_down.sql", }, { version: 3, name: "reinforcement_history", upFile: "003_reinforcement_history.sql", downFile: "003_reinforcement_history_down.sql", }, { version: 4, name: "full_text_search", upFile: "004_full_text_search.sql", downFile: "004_full_text_search_down.sql", }, ]; const report = validator.validateAll(migrations); for (const result of report.results) { const status = result.isValid ? "✅" : "❌"; log(`\n${status} Migration ${result.migration.version}: ${result.migration.name}`); if (result.errors.length > 0) { for (const error of result.errors) { logError(` ${error}`); } } if (result.warnings.length > 0) { for (const warning of result.warnings) { logWarning(` ${warning}`); } } } // Check sequence const sequenceCheck = validator.checkSequence(migrations); if (sequenceCheck.hasGaps) { logWarning(`\n⚠️ Migration sequence has gaps: ${sequenceCheck.gaps.join(", ")}`); } log("\n" + "═".repeat(40)); if (report.isValid) { logSuccess(report.summary); } else { logError(report.summary); process.exit(1); } } /** * Show dry-run of pending migrations */ async function dryRun(): Promise<void> { const config = getDatabaseConfig(); const dbManager = new DatabaseConnectionManager(config); try { await dbManager.connect(); const migrationSystem = new SchemaMigrationSystem(dbManager); const currentVersion = await migrationSystem.getCurrentVersion(); const migrationsDir = join(__dirname, "../src/database/migrations"); const validator = new MigrationValidator(migrationsDir); // Get all migrations const allMigrations = [ { version: 1, name: "initial_schema", upFile: "001_initial_schema.sql", downFile: "001_initial_schema_down.sql", }, { version: 2, name: "create_indexes", upFile: "002_create_indexes.sql", downFile: "002_create_indexes_down.sql", }, { version: 3, name: "reinforcement_history", upFile: "003_reinforcement_history.sql", downFile: "003_reinforcement_history_down.sql", }, { version: 4, name: "full_text_search", upFile: "004_full_text_search.sql", downFile: "004_full_text_search_down.sql", }, ]; const pendingMigrations = allMigrations.filter((m) => m.version > currentVersion); log("\n🔮 Dry Run - Migration Preview", "cyan"); log("═".repeat(40)); log(validator.generateDryRunReport(pendingMigrations, currentVersion)); } finally { await dbManager.disconnect(); } } /** * Reset database (development only) */ async function resetDatabase(): Promise<void> { if (process.env.NODE_ENV === "production") { logError("Database reset is not allowed in production!"); process.exit(1); } const config = getDatabaseConfig(); const dbManager = new DatabaseConnectionManager(config); try { await dbManager.connect(); const migrationSystem = new SchemaMigrationSystem(dbManager); logWarning("⚠️ Resetting database - ALL DATA WILL BE LOST!"); logInfo("Rolling back all migrations...\n"); await migrationSystem.resetDatabase(); logSuccess("\nDatabase reset complete"); } finally { await dbManager.disconnect(); } } /** * Show help */ function showHelp(): void { log("\n📚 ThoughtMCP Database Migration Tool", "cyan"); log("═".repeat(40)); log("\nUsage: npx tsx scripts/migrate.ts <command> [options]\n"); log("Commands:"); log(" status Show current migration status"); log(" up Apply all pending migrations"); log(" down <ver> Rollback to specific version"); log(" validate Validate all migration files"); log(" dry-run Show what migrations would be applied"); log(" reset Reset database (development only)"); log("\nEnvironment Variables:"); log(" DB_HOST Database host (default: localhost)"); log(" DB_PORT Database port (default: 5432)"); log(" DB_NAME Database name (required)"); log(" DB_USER Database user (required)"); log(" DB_PASSWORD Database password (required)"); log("\nExamples:"); log(" npx tsx scripts/migrate.ts status"); log(" npx tsx scripts/migrate.ts up"); log(" npx tsx scripts/migrate.ts down 2"); log(" npx tsx scripts/migrate.ts validate"); log(" npx tsx scripts/migrate.ts dry-run"); } /** * Main entry point */ async function main(): Promise<void> { const args = process.argv.slice(2); const command = args[0]; try { switch (command) { case "status": await showStatus(); break; case "up": case "migrate": await migrateUp(); break; case "down": case "rollback": const targetVersion = parseInt(args[1], 10); if (isNaN(targetVersion)) { logError("Target version is required for rollback"); logInfo("Usage: npx tsx scripts/migrate.ts down <version>"); process.exit(1); } await migrateDown(targetVersion); break; case "validate": await validateMigrations(); break; case "dry-run": await dryRun(); break; case "reset": await resetDatabase(); break; case "help": case "--help": case "-h": showHelp(); break; default: if (command) { logError(`Unknown command: ${command}`); } showHelp(); process.exit(command ? 1 : 0); } } catch (error) { logError(error instanceof Error ? error.message : String(error)); process.exit(1); } } main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server