'use strict'; const fs = require('fs'); const path = require('path'); const db = require('./db'); const MIGRATIONS_DIR = path.join(__dirname, 'migrations'); function init() { db.exec(` CREATE TABLE IF NOT EXISTS _migrations ( filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (datetime('now')) ); `); } function listFiles() { if (!fs.existsSync(MIGRATIONS_DIR)) return []; return fs.readdirSync(MIGRATIONS_DIR) .filter(f => f.endsWith('.sql')) .sort(); } function applied() { return new Set(db.prepare('SELECT filename FROM _migrations').all().map(r => r.filename)); } function run() { init(); const done = applied(); const files = listFiles(); const pending = files.filter(f => !done.has(f)); if (pending.length === 0) { console.log('[migrate] Nothing to apply — schema is up to date'); return; } for (const file of pending) { const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), 'utf8'); console.log(`[migrate] Applying ${file}...`); try { db.exec('BEGIN'); db.exec(sql); db.prepare('INSERT INTO _migrations (filename) VALUES (?)').run(file); db.exec('COMMIT'); console.log(`[migrate] OK: ${file}`); } catch (e) { db.exec('ROLLBACK'); console.error(`[migrate] FAILED at ${file}: ${e.message}`); process.exit(1); } } console.log(`[migrate] Applied ${pending.length} migration(s)`); } /* Mark 000_baseline.sql as applied on existing DBs without running it. Run once per environment after first deploy of this system. */ function markBaseline() { init(); const hasUsers = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='users'").get(); if (hasUsers) { const done = applied(); if (!done.has('000_baseline.sql')) { db.prepare('INSERT INTO _migrations (filename) VALUES (?)').run('000_baseline.sql'); console.log('[migrate] Marked 000_baseline.sql as applied (existing DB — not re-run)'); } else { console.log('[migrate] 000_baseline.sql already marked as applied'); } } else { console.log('[migrate] No users table found — run `npm run migrate` first to initialize schema'); process.exit(1); } } if (require.main === module) { const cmd = process.argv[2]; if (cmd === 'bootstrap') markBaseline(); else run(); } module.exports = { run, markBaseline };