/** * Database Migration Script — destructive fresh sync. * * Drops every table and recreates the schema from Sequelize model definitions * in `src/database/models/`. After the fresh schema is in place, every * versioned migration file under `scripts/migrations/` is automatically * stamped into the `migrations` table as "already applied" so subsequent * `npm run migrate:up` runs on this DB will be no-ops until a newer * migration is added. * * For incremental schema changes on environments that already hold data, * use `npm run migrate:up` instead. * * Run: npx tsx scripts/migrate.ts * Flags: * --no-baseline Skip stamping migration files as applied (advanced). */ import 'dotenv/config'; import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { createHash } from 'crypto'; import db from '../src/database/models/index.js'; const MIGRATIONS_DIR = path.resolve( path.dirname(fileURLToPath(import.meta.url)), 'migrations' ); async function discoverMigrations(): Promise { try { const entries = await fs.readdir(MIGRATIONS_DIR); return entries .filter((name) => name.endsWith('.ts') && !name.startsWith('_')) .sort(); } catch { return []; } } async function fileChecksum(file: string): Promise { const buf = await fs.readFile(path.join(MIGRATIONS_DIR, file)); return createHash('sha256').update(buf).digest('hex'); } async function baselineMigrationsTable(): Promise { const files = await discoverMigrations(); if (files.length === 0) { console.log('No versioned migrations to baseline.'); return; } console.log(`šŸ“Œ Stamping ${files.length} migration(s) as already-applied:`); for (const file of files) { const name = file.replace(/\.ts$/, ''); const checksum = await fileChecksum(file); await db.Migration.create({ name, checksum }); console.log(` + ${name}`); } } async function runMigrations() { console.log('šŸ”„ Starting database synchronization (Fresh Startup)...\n'); console.log('āš ļø WARNING: This will drop all existing tables in the database.\n'); const skipBaseline = process.argv.includes('--no-baseline'); try { await db.sequelize.authenticate(); console.log('šŸ“” Connected to the database successfully.'); // force: true drops existing tables — schema is rebuilt exactly from // Sequelize models, so every enum / column / index matches code. await db.sequelize.sync({ force: true }); console.log('\nāœ… All tables created and synchronized successfully!'); console.log('----------------------------------------------------'); const modelNames = Object.keys(db).filter((k) => k !== 'sequelize' && k !== 'Sequelize'); console.log(`Available Models (${modelNames.length}): ${modelNames.join(', ')}`); console.log('----------------------------------------------------\n'); if (!skipBaseline) { await baselineMigrationsTable(); } else { console.log('Skipping migration baseline (--no-baseline).'); } process.exit(0); } catch (error: any) { console.error('\nāŒ Migration failed:', error.message); if (error.stack) { console.error('\nStack Trace:\n', error.stack); } process.exit(1); } } runMigrations();