From 3c4c8b2037004da67d088c880af22d0bb7d9d68d Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Thu, 19 Feb 2026 12:36:11 +0100 Subject: [PATCH] la version ya no es PK --- src/index.ts | 139 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 32 deletions(-) diff --git a/src/index.ts b/src/index.ts index 52e5167..a51d2af 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ #!/usr/bin/node -import { constants } from 'buffer'; import { existsSync } from 'fs'; import fs from 'fs/promises'; import path from 'path'; @@ -60,19 +59,25 @@ function getArgs() { type: "string", description: "Path del directorio de migrations" }) + .option("versionTable", { + alias: "v", + type: "string", + description: "Nombre de la tabla donde se almacenan las versiones de la BDD, por defecto 'db_versions'", + default: "db_versions" + }) .parse() as { target: string, env: string, - migrations: string + migrations: string, + versionTable: "db_versions" | string } - console.log("args", argv) - /* Antiguo */ - + //console.log("args", argv) return { target: argv.target, env: argv.env, - migrations: argv.migrations + migrations: argv.migrations, + versionTable: argv.versionTable }; } @@ -106,17 +111,89 @@ function compareVersions(va: string, vb: string) { return 0 } -async function getCurrentVersion(db: Pool) { +async function getCurrentVersion(db: Pool, versionTable: string) { try { const lastVersion = await db.query(` - SELECT * FROM db_versions + SELECT * FROM ${versionTable} ORDER BY creation_date DESC LIMIT 1 `) return lastVersion.rows[0] } catch (e) { + console.error("Error leyendo la tabla de versiones ", versionTable, e) + } +} + +/** + * Si se estuviese lanzando el script sin que exista una tabla con el regstro + * de versiones se crea una nueva con 0.0.0 como version inicial + */ +async function initVersionTable(db: Pool, versionTable: string) { + const client = await db.connect() + + const ddlCreateVersionTable = ` + CREATE TABLE IF NOT EXISTS ${versionTable} ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + version TEXT PRIMARY KEY, -- version semantica x.x.x, + notes TEXT, + creation_date TIMESTAMP NOT NULL DEFAULT (now() at time zone 'utc'), + stable BOOLEAN DEFAULT FALSE -- Si la version ha sido testada y se puede desplegar + ); + ` + + const insertFistValue = ` + INSERT INTO ${versionTable} ( + version, + notes + ) + VALUES ( + '0.0.0', + 'Versión base' + ); + ` + + try { + await client.query("BEGIN") + + await client.query(ddlCreateVersionTable) + await client.query(insertFistValue) + + await client.query("COMMIT") + return 0 + } catch (e) { + client.query("ROLLBACK") + console.error("[x] No se ha podido crear la tabla de versiones de la BDD") console.error(e) } finally { + client.release() + } +} + + +async function writeAppliedVersion(db: Pool, versionTable: string, version: MigrationFile) { + const client = await db.connect() + const insertVersion = ` + INSERT INTO ${versionTable} ( + version, + notes + ) + VALUES ( + $1, + $2 + ); + ` + const parts = version.version.split("_") + try { + await client.query("BEGIN") + await client.query(insertVersion, parts) + await client.query("COMMIT") + return 0 + } catch (e) { + client.query("ROLLBACK") + console.error("[x] No se ha podido insertar la nueva version de la BDD") + console.error(e) + } finally { + client.release() } } @@ -128,7 +205,8 @@ async function getCurrentVersion(db: Pool) { async function runMigrations(args: { targetVersion: string, currentVersion?: string, - migrationDir: string + migrationDir: string, + versionTable: string }) { let db; try { @@ -147,16 +225,19 @@ async function runMigrations(args: { const dbClient = await db.connect() try { + const versionBdd = (await getCurrentVersion(db, args.versionTable))?.version + + if (versionBdd == undefined) { + await initVersionTable(db, args.versionTable) + } + // 1º La version explicita 2º La versión almacenada en BDD 3º 0.0.0 como version base - const versionBdd = (await getCurrentVersion(db))?.version if (versionBdd == undefined) { console.log("[x] Error buscando la ultima version de la base de datos") } const currentVersion = args.currentVersion ?? versionBdd ?? "0.0.0" console.log("[i] Migrando desde la version " + currentVersion + " a la version " + args.targetVersion) const files = await fs.readdir(args.migrationDir); - console.log("Directorio de migraciones", args.migrationDir) - console.log("Archivos de migraciones", files) const pendingMigrations: MigrationFile[] = files .map(f => ({ version: path.parse(f).name, @@ -173,11 +254,10 @@ async function runMigrations(args: { console.log("[o] La base de datos ya está actualizada."); return; } - console.log("[i] Migraciones pendietes", pendingMigrations) + console.log("[i] Migraciones pendietes", pendingMigrations.map(e => e.version)) console.log(`[i] Aplicando ${pendingMigrations.length} migraciones...`); // Iniciamos Transacción (Ejemplo conceptual con un cliente genérico) - // await db.query('BEGIN'); await dbClient.query("BEGIN") for (const migration of pendingMigrations) { const sql = await fs.readFile(migration.fullPath, 'utf8'); @@ -185,29 +265,19 @@ async function runMigrations(args: { // Ejecutar SQL await dbClient.query(sql); - - // Actualizar tabla de versión - // En principio esto se hace en el archivo pero estoy pensando en meterlo en el script para - // evitar olvidos - /* await dbClient.query(` - INSERT INTO db_versions ( - version, - notes - ) - VALUES ( - $1, - 'Fechas modificadas para que todas sean en base a UTC' - ); - `) - */ + console.log(` -> Aplicado correctamente: ${migration.fileName}`); } - await db.query("COMMIT") + await dbClient.query("COMMIT") + + const ultimaVersion = pendingMigrations[pendingMigrations.length - 1] + await writeAppliedVersion(db, args.versionTable, ultimaVersion) console.log("[o] Migraciones completadas con éxito."); + console.log("[o] Última version aplicada: ", ultimaVersion.version); } catch (error) { console.error("[x] Error durante la migración. Se ha realizado un rollback automático."); console.error(error); - await db.query("ROLLBACK") + await dbClient.query("ROLLBACK") process.exit(1); } finally { dbClient.release() @@ -216,13 +286,18 @@ async function runMigrations(args: { /** * ******************************************************************* + * MAIN * ******************************************************************* */ async function main() { const args = getArgs(); await loadEnv(args.env); - runMigrations({ targetVersion: args.target, migrationDir: args.migrations }) + await runMigrations({ + targetVersion: args.target, + migrationDir: args.migrations, + versionTable: args.versionTable + }) } main().then(e => console.log(e)).catch(console.error)