import { readdir, readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { createHash } from 'node:crypto' import { createDatabase } from '@trumpsignal/database' const url = process.env.DATABASE_URL if (!url) throw new Error('DATABASE_URL required') const { pool, lockPool } = createDatabase(url), client = await pool.connect() try { await client.query('SELECT pg_advisory_lock(hashtextextended($1,0))', ['trumpsignal-migration']) await client.query( 'CREATE TABLE IF NOT EXISTS typescript_migrations (name TEXT PRIMARY KEY, sha256 TEXT NOT NULL, applied_at TIMESTAMPTZ NOT NULL DEFAULT now())', ) const folder = fileURLToPath(new URL('../packages/database/migrations/', import.meta.url)), files = (await readdir(folder)).filter((n) => n.endsWith('.sql')).sort() for (const name of files) { const source = await readFile(folder + name, 'utf8'), hash = createHash('sha256').update(source).digest('hex'), old = await client.query<{ sha256: string }>( 'SELECT sha256 FROM typescript_migrations WHERE name=$1', [name], ) if (old.rows[0]) { if (old.rows[0].sha256 !== hash) throw new Error('Previously applied migration changed: ' + name) continue } if (name.startsWith('0001')) { const legacy = await client.query<{ exists: boolean }>( "SELECT to_regclass('public.alembic_version') IS NOT NULL AS exists", ) if (legacy.rows[0].exists) { const head = await client.query<{ version_num: string }>( 'SELECT version_num FROM alembic_version', ) if (!head.rows.some((r) => r.version_num.startsWith('027'))) throw new Error('Existing legacy database must reach Alembic head 027 first') } } await client.query('BEGIN') try { await client.query(source) await client.query('INSERT INTO typescript_migrations(name,sha256) VALUES ($1,$2)', [ name, hash, ]) await client.query('COMMIT') process.stdout.write('Applied ' + name + '\n') } catch (e) { await client.query('ROLLBACK') throw e } } } finally { await client.query('SELECT pg_advisory_unlock(hashtextextended($1,0))', ['trumpsignal-migration']) client.release() await pool.end() await lockPool.end() }