← back to Professional Directory
scripts/migrate.js
54 lines
#!/usr/bin/env node
/**
* Plain SQL migration runner.
* - Tracks applied migrations in schema_migrations table.
* - Applies db/schema.sql first (idempotent), then any db/migrations/*.sql in lexical order.
*/
const fs = require('node:fs');
const path = require('node:path');
const { pool } = require('../agents/shared/db');
const ROOT = path.resolve(__dirname, '..');
const SCHEMA = path.join(ROOT, 'db/schema.sql');
const MIG_DIR = path.join(ROOT, 'db/migrations');
(async () => {
await pool.query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
filename TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
`);
// schema.sql is idempotent — apply every run
console.log('→ applying db/schema.sql');
await pool.query(fs.readFileSync(SCHEMA, 'utf8'));
console.log('✓ schema.sql');
if (fs.existsSync(MIG_DIR)) {
const files = fs.readdirSync(MIG_DIR)
.filter(f => f.endsWith('.sql') && !f.startsWith('0001_init')) // 0001 = pointer to schema.sql
.sort();
for (const f of files) {
const exists = await pool.query('SELECT 1 FROM schema_migrations WHERE filename = $1', [f]);
if (exists.rowCount > 0) { console.log(`✓ already applied ${f}`); continue; }
const sql = fs.readFileSync(path.join(MIG_DIR, f), 'utf8');
console.log(`→ applying ${f}`);
const client = await pool.connect();
try {
await client.query(sql);
await client.query('INSERT INTO schema_migrations(filename) VALUES ($1)', [f]);
console.log(`✓ applied ${f}`);
} finally {
client.release();
}
}
}
await pool.end();
})().catch(err => {
console.error('migrate failed:', err);
process.exit(1);
});