/** * Ejecuta la migración en una transacción y la deshace. * * `drizzle-kit migrate` se queda colgado sin decir por qué, así que hay que * preguntarle directamente a Postgres. * * El progreso se escribe a disco de forma SÍNCRONA. La salida normal de Node * se acumula en un buffer cuando va a un pipe o a un archivo, y si el proceso * se mata por tiempo ese buffer se pierde entero: es justo lo que hacía que * pareciera que no llegaba a arrancar. */ import fs from 'node:fs'; import pg from 'pg'; const ARCHIVO = 'drizzle/0002_catalogo_en_la_base.sql'; const DIARIO = '.diario-migracion.txt'; const APLICAR = process.argv.includes('--aplicar'); fs.writeFileSync(DIARIO, ''); const apuntar = (linea) => fs.appendFileSync(DIARIO, linea + '\n'); const sql = fs.readFileSync(ARCHIVO, 'utf8'); const sentencias = sql .split('--> statement-breakpoint') .map((s) => s.trim()) // Sin expresión regular. `(--[^\n]*\n?)+` tarda catorce segundos en un // comentario de quinientos caracteres: cuando no encaja, puede repartir el // mismo texto de exponencialmente muchas formas antes de rendirse. Línea a // línea es lineal, y además se lee. .filter((s) => s && s.split('\n').some((l) => l.trim() && !l.trim().startsWith('--'))); apuntar(`${sentencias.length} sentencias`); const cliente = new pg.Client({ connectionString: process.env.DATABASE_URL, // Que no se quede esperando eternamente si el servidor no contesta. connectionTimeoutMillis: 10_000, query_timeout: 30_000 }); apuntar('conectando...'); await cliente.connect(); apuntar('conectado'); await cliente.query('BEGIN'); // Que falle rápido en vez de colgarse esperando un bloqueo que no llega. await cliente.query("SET LOCAL lock_timeout = '5s'"); await cliente.query("SET LOCAL statement_timeout = '30s'"); apuntar('transacción abierta'); let hechas = 0; let rota = null; for (const [i, sentencia] of sentencias.entries()) { const resumen = sentencia.split('\n').find((l) => l.trim() && !l.trim().startsWith('--')) ?? ''; apuntar(`[${i + 1}/${sentencias.length}] ${resumen.trim().slice(0, 80)}`); try { await cliente.query(sentencia); hechas++; } catch (fallo) { rota = { i, sentencia, fallo }; apuntar(`\n FALLA: ${fallo.message}`); if (fallo.hint) apuntar(` pista: ${fallo.hint}`); if (fallo.position) apuntar(` posición: ${fallo.position}`); apuntar('\n' + sentencia.slice(0, 600)); break; } } const completa = !rota && hechas === sentencias.length; await cliente.query(APLICAR && completa ? 'COMMIT' : 'ROLLBACK'); apuntar(completa ? `\nLas ${hechas} sentencias pasan.` : `\nParó en la ${hechas + 1}.`); apuntar(APLICAR && completa ? 'APLICADA.' : '(deshecha)'); await cliente.end();