fix: rewrite migrate.mjs to bypass missing drizzle-orm migrator module
Some checks failed
Deploy to VPS / deploy (push) Has been cancelled

drizzle-orm v0.45.2 declares a migrator export but does not ship the
actual .js file, causing all migrations to silently fail on every deploy.
This was the root cause of the missing "documents" table (AIIA-62).

The new script reads the drizzle journal and executes SQL files directly
via pg, with its own tracking table for idempotent re-runs.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
CTO (LegalAI)
2026-04-10 10:12:01 +00:00
parent 8172872329
commit fe838d5916

View File

@@ -1,17 +1,73 @@
// Runtime migration script — runs drizzle SQL migrations against the database.
// Used by the Docker entrypoint before starting the app.
// Runtime migration script — reads the drizzle journal and executes pending
// SQL migrations directly via pg, bypassing the drizzle-orm migrator module
// which may not be shipped in all drizzle-orm builds.
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import pg from 'pg';
import { readFileSync } from 'node:fs';
const MIGRATIONS_DIR = './drizzle';
const MIGRATION_TABLE = '__drizzle_migrations';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
try {
const db = drizzle(pool);
console.log('Running database migrations...');
await migrate(db, { migrationsFolder: './drizzle' });
console.log('Migrations complete.');
const client = await pool.connect();
// Ensure migration tracking table exists
await client.query(`
CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (
id SERIAL PRIMARY KEY,
tag TEXT NOT NULL UNIQUE,
created_at BIGINT NOT NULL
)
`);
// Load journal
const journal = JSON.parse(readFileSync(`${MIGRATIONS_DIR}/meta/_journal.json`, 'utf8'));
// Find already-applied migrations
const { rows: applied } = await client.query(`SELECT tag FROM ${MIGRATION_TABLE}`);
const appliedTags = new Set(applied.map(r => r.tag));
let count = 0;
for (const entry of journal.entries) {
if (appliedTags.has(entry.tag)) continue;
const sqlFile = `${MIGRATIONS_DIR}/${entry.tag}.sql`;
let sql;
try {
sql = readFileSync(sqlFile, 'utf8');
} catch {
console.warn(`Migration file not found: ${sqlFile}, skipping.`);
continue;
}
// Split on drizzle statement breakpoints and execute each statement
const statements = sql.split('--> statement-breakpoint')
.map(s => s.trim())
.filter(Boolean);
console.log(`Applying migration: ${entry.tag} (${statements.length} statements)`);
await client.query('BEGIN');
try {
for (const stmt of statements) {
await client.query(stmt);
}
await client.query(
`INSERT INTO ${MIGRATION_TABLE} (tag, created_at) VALUES ($1, $2)`,
[entry.tag, entry.when],
);
await client.query('COMMIT');
count++;
} catch (err) {
await client.query('ROLLBACK');
throw new Error(`Migration ${entry.tag} failed: ${err.message}`);
}
}
client.release();
console.log(count > 0 ? `Applied ${count} migration(s).` : 'No pending migrations.');
} catch (err) {
console.error('Migration failed:', err);
process.exit(1);