#!/usr/bin/env node // up -> откат ВСЕХ миграций по одной -> up -> повторный up (идемпотентность). // Запускается ролью мигратора (DATABASE_MIGRATION_URL), без SUPERUSER. import { spawnSync } from 'node:child_process'; function run(direction) { const result = spawnSync(process.execPath, ['scripts/migrate.mjs', direction], { env: { ...process.env, ALLOW_MIGRATION_DOWN: direction === 'down' ? 'true' : 'false' }, encoding: 'utf8', }); process.stdout.write(result.stdout); process.stderr.write(result.stderr); if (result.status !== 0) { console.error(`migrate ${direction} failed with exit ${result.status}`); process.exit(result.status ?? 1); } return result.stdout; } const mode = process.argv[2] ?? 'roundtrip'; console.log('== migrate up (1)'); const first = run('up'); if (mode === 'up') process.exit(0); const applied = (first.match(/^applied /gm) ?? []).length; console.log(`== rollback all (applied now: ${applied})`); let rolledBack = 0; for (let i = 0; i < 1000; i += 1) { const out = run('down'); if (/no migration to roll back/.test(out)) break; rolledBack += 1; } console.log(`== rolled back ${rolledBack} migrations`); console.log('== migrate up (2)'); const second = run('up'); const reapplied = (second.match(/^applied /gm) ?? []).length; console.log('== migrate up (3, must be no-op)'); const third = run('up'); if ((third.match(/^applied /gm) ?? []).length !== 0) { console.error('third up applied migrations again: not idempotent'); process.exit(1); } if (rolledBack === 0 || reapplied !== rolledBack) { console.error(`rollback/reapply mismatch: rolledBack=${rolledBack} reapplied=${reapplied}`); process.exit(1); } console.log(`== roundtrip OK: rolledBack=${rolledBack} reapplied=${reapplied}`);