gitignore
This commit is contained in:
183
src/index.ts
Normal file
183
src/index.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/node
|
||||
|
||||
import { existsSync } from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { Pool } from 'pg';
|
||||
import { env } from 'process';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
const db = new Pool({
|
||||
host: env.POSTGRES_HOST,
|
||||
user: env.POSTGRES_USER,
|
||||
port: Number(env.POSTGRES_PORT),
|
||||
password: env.POSTGRES_PASSWORD
|
||||
})
|
||||
|
||||
type MigrationFile = {
|
||||
version: string;
|
||||
fileName: string;
|
||||
fullPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga de variables de entorno manual (para evitar dependencias como dotenv)
|
||||
* Busca el .env en el directorio actual (CWD)
|
||||
*/
|
||||
async function loadEnv(envpath?: string) {
|
||||
const envPath = envpath ?? path.join(process.cwd(), '.env');
|
||||
if (existsSync(envPath)) {
|
||||
const content = await fs.readFile(envPath, 'utf-8');
|
||||
content.split('\n').forEach(line => {
|
||||
const [key, value] = line.split('=');
|
||||
if (key && value) process.env[key.trim()] = value.trim();
|
||||
});
|
||||
console.log('ℹ️ Archivo .env cargado desde el directorio actual.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parseo de argumentos manual: --target <version>
|
||||
*/
|
||||
function getArgs() {
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option("target", {
|
||||
alias: "t",
|
||||
type: 'string',
|
||||
description: "Versión objetivo de la migracion",
|
||||
requiresArg: false
|
||||
})
|
||||
.parse()
|
||||
|
||||
console.log("args", argv)
|
||||
/* Antiguo */
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const targetIdx = args.indexOf('--target') !== -1 ? args.indexOf('--target') : args.indexOf('-t');
|
||||
const envIdx = args.indexOf('--env') !== -1 ? args.indexOf('--env') : args.indexOf('-e');
|
||||
const migrtionsIdx = args.indexOf("")
|
||||
|
||||
if (targetIdx === -1 || !args[targetIdx + 1]) {
|
||||
console.error('❌ Error: Debes especificar una versión objetivo con --target o -t');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
target: args[targetIdx + 1],
|
||||
env: args[envIdx + 1]
|
||||
};
|
||||
}
|
||||
|
||||
function versionToValue(version: string) {
|
||||
return version.split("_")[0]!
|
||||
.split(".")
|
||||
.slice(0, 3)
|
||||
.map(e => parseInt(e))
|
||||
}
|
||||
|
||||
/**
|
||||
* Para poder ordenar las verisones con 1 - 3 valores
|
||||
*/
|
||||
function compareVersions(va: string, vb: string) {
|
||||
const { max } = Math
|
||||
|
||||
const partesa = versionToValue(va)
|
||||
const partesb = versionToValue(vb)
|
||||
|
||||
const maxLen = max(partesa.length, partesb.length)
|
||||
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
const partea = partesa[i] ?? 0
|
||||
const parteb = partesb[i] ?? 0
|
||||
|
||||
if (partea == parteb) continue;
|
||||
else if (partea > parteb) return 1;
|
||||
else return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Lógica principal de ejecución de migraciones
|
||||
* @param targetVersion Versión objetivo pasada por el usuario
|
||||
* @param currentVersion Versión actual obtenida de la DB
|
||||
*/
|
||||
async function runMigrations(targetVersion: string, currentVersion: string, migrationDir: string) {
|
||||
const dbClient = await db.connect()
|
||||
try {
|
||||
const files = await fs.readdir(migrationDir);
|
||||
|
||||
const pendingMigrations: MigrationFile[] = files
|
||||
.map(f => (<MigrationFile>{
|
||||
version: path.parse(f).name,
|
||||
fileName: f,
|
||||
fullPath: path.join(migrationDir, f)
|
||||
}))
|
||||
// 1. Filtrar las migraciones > que la actual
|
||||
.filter(v => compareVersions(v.version, currentVersion) == -1)
|
||||
// 2. Filtra las migraciones <= que la objetivo
|
||||
.filter(v => compareVersions(v.version, targetVersion) >= 0)
|
||||
.sort((a, b) => compareVersions(a.version, b.version));
|
||||
|
||||
if (pendingMigrations.length === 0) {
|
||||
console.log("✅ La base de datos ya está actualizada.");
|
||||
return;
|
||||
}
|
||||
console.log("Migraciones pendietes", pendingMigrations)
|
||||
console.log(`🚀 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');
|
||||
console.log(` -> Aplicando: ${migration.fileName}`);
|
||||
|
||||
// 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'
|
||||
);
|
||||
`)
|
||||
*/
|
||||
}
|
||||
|
||||
// await db.query('COMMIT');
|
||||
await db.query("COMMIT")
|
||||
console.log("✨ Migraciones completadas con éxito.");
|
||||
} catch (error) {
|
||||
// Si algo falla, el rollback es vital en DevOps
|
||||
// await db.query('ROLLBACK');
|
||||
console.error("❌ Error durante la migración. Se ha realizado un rollback automático.");
|
||||
console.error(error);
|
||||
await db.query("ROLLBACK")
|
||||
process.exit(1);
|
||||
} finally {
|
||||
dbClient.release()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* *******************************************************************
|
||||
* *******************************************************************
|
||||
*/
|
||||
|
||||
async function main() {
|
||||
const { target: targetVersion, env: envPath } = getArgs();
|
||||
await loadEnv(envPath)
|
||||
}
|
||||
|
||||
main().then(e => console.log(e)).catch(console.error)
|
||||
Reference in New Issue
Block a user