99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
/**
|
|
* 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<string[]> {
|
|
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<string> {
|
|
const buf = await fs.readFile(path.join(MIGRATIONS_DIR, file));
|
|
return createHash('sha256').update(buf).digest('hex');
|
|
}
|
|
|
|
async function baselineMigrationsTable(): Promise<void> {
|
|
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();
|