@deinossrl/dgp-agent 1.4.59 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/index.mjs +181 -5
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog - DGP Agent
2
2
 
3
+ ## [1.5.1] - 2026-01-12
4
+
5
+ ### Changed
6
+ - Actualizada ayuda (`dgp-agent help`) con documentación completa de backup/restore
7
+ - Agregada sección detallada explicando auto-creación de schemas
8
+ - Mejor descripción de capacidades del agente
9
+
10
+ ## [1.5.0] - 2026-01-12
11
+
12
+ ### Added
13
+ - **Auto-creación de schemas en restore**: Detecta automáticamente qué schemas necesita el backup y los crea antes de restaurar
14
+ - Agregado `psql.exe` a los binarios instalados (pg_dump, pg_restore, psql)
15
+ - Detección robusta de schemas con 3 métodos:
16
+ 1. `pg_restore --list` con múltiples patrones de búsqueda
17
+ 2. Análisis directo del contenido del backup si --list falla
18
+ 3. Fallback a parsing manual del archivo
19
+ - Manejo inteligente de errores al crear schemas (ya existe, sin permisos, etc.)
20
+
21
+ ### Changed
22
+ - Logs más detallados durante detección y creación de schemas
23
+ - Mejor manejo de errores críticos vs warnings
24
+
25
+ ### Fixed
26
+ - Restauraciones personalizadas ahora funcionan correctamente sin necesidad de crear schemas manualmente
27
+ - Resuelve el problema de "schema does not exist" en backups selectivos
28
+
3
29
  ## [1.4.59] - 2026-01-12
4
30
 
5
31
  ### Added
package/index.mjs CHANGED
@@ -1532,16 +1532,21 @@ async function findOrInstallPgDump() {
1532
1532
  // Copiar pg_dump.exe, pg_restore.exe y TODAS las DLLs al BIN_DIR raíz
1533
1533
  const binSrcDir = join(BIN_DIR, 'pgsql', 'bin');
1534
1534
 
1535
- // Copiar pg_dump.exe
1535
+ // Copiar pg_dump.exe, pg_restore.exe y psql.exe
1536
1536
  execSync(`copy "${extractedPgDump}" "${localPgDump}"`, { shell: 'cmd.exe' });
1537
1537
 
1538
- // Copiar pg_restore.exe
1539
1538
  const extractedPgRestore = join(BIN_DIR, 'pgsql', 'bin', 'pg_restore.exe');
1540
1539
  const localPgRestore = join(BIN_DIR, 'pg_restore.exe');
1541
1540
  if (existsSync(extractedPgRestore)) {
1542
1541
  execSync(`copy "${extractedPgRestore}" "${localPgRestore}"`, { shell: 'cmd.exe' });
1543
1542
  }
1544
1543
 
1544
+ const extractedPsql = join(BIN_DIR, 'pgsql', 'bin', 'psql.exe');
1545
+ const localPsql = join(BIN_DIR, 'psql.exe');
1546
+ if (existsSync(extractedPsql)) {
1547
+ execSync(`copy "${extractedPsql}" "${localPsql}"`, { shell: 'cmd.exe' });
1548
+ }
1549
+
1545
1550
  // Copiar TODAS las DLLs (no solo algunas específicas)
1546
1551
  execSync(`copy "${binSrcDir}\\*.dll" "${BIN_DIR}\\"`, { shell: 'cmd.exe' });
1547
1552
 
@@ -1582,16 +1587,21 @@ async function findOrInstallPgDump() {
1582
1587
  if (existsSync(extractedPgDump)) {
1583
1588
  const binSrcDir = join(BIN_DIR, 'pgsql', 'bin');
1584
1589
 
1585
- // Copiar pg_dump.exe
1590
+ // Copiar pg_dump.exe, pg_restore.exe y psql.exe
1586
1591
  execSync(`copy "${extractedPgDump}" "${localPgDump}"`, { shell: 'cmd.exe' });
1587
1592
 
1588
- // Copiar pg_restore.exe
1589
1593
  const extractedPgRestore = join(BIN_DIR, 'pgsql', 'bin', 'pg_restore.exe');
1590
1594
  const localPgRestore = join(BIN_DIR, 'pg_restore.exe');
1591
1595
  if (existsSync(extractedPgRestore)) {
1592
1596
  execSync(`copy "${extractedPgRestore}" "${localPgRestore}"`, { shell: 'cmd.exe' });
1593
1597
  }
1594
1598
 
1599
+ const extractedPsql = join(BIN_DIR, 'pgsql', 'bin', 'psql.exe');
1600
+ const localPsql = join(BIN_DIR, 'psql.exe');
1601
+ if (existsSync(extractedPsql)) {
1602
+ execSync(`copy "${extractedPsql}" "${localPsql}"`, { shell: 'cmd.exe' });
1603
+ }
1604
+
1595
1605
  // Copiar TODAS las DLLs
1596
1606
  execSync(`copy "${binSrcDir}\\*.dll" "${BIN_DIR}\\"`, { shell: 'cmd.exe' });
1597
1607
 
@@ -1958,7 +1968,155 @@ async function executePgRestore(command) {
1958
1968
  .update({ status: 'running', started_at: new Date().toISOString() })
1959
1969
  .eq('id', restore_id);
1960
1970
 
1961
- // Construir argumentos de pg_restore
1971
+ // PASO 1: Detectar y crear schemas necesarios antes de restaurar
1972
+ logInfo('Analizando backup para detectar schemas...');
1973
+ await addCommandLog('info', 'Detectando schemas del backup...');
1974
+
1975
+ const schemas = new Set();
1976
+
1977
+ try {
1978
+ // Usar pg_restore --list para ver el contenido del backup
1979
+ const listResult = spawnSync(pgRestorePath, ['--list', localFilePath], {
1980
+ env: process.env,
1981
+ encoding: 'utf-8',
1982
+ stdio: 'pipe',
1983
+ });
1984
+
1985
+ if (listResult.status === 0 && listResult.stdout) {
1986
+ const listOutput = listResult.stdout;
1987
+
1988
+ // Método 1: Buscar líneas con "SCHEMA - nombre"
1989
+ const schemaLines = listOutput.match(/^\s*\d+;\s*\d+\s+\d+\s+SCHEMA\s+-\s+(\w+)/gm) || [];
1990
+ schemaLines.forEach(line => {
1991
+ const match = line.match(/SCHEMA\s+-\s+(\w+)/);
1992
+ if (match && match[1] && match[1] !== 'public' && match[1] !== '-') {
1993
+ schemas.add(match[1]);
1994
+ }
1995
+ });
1996
+
1997
+ // Método 2: Buscar referencias a schemas en tablas (ej: "TABLE - crm.quotes")
1998
+ const tableLines = listOutput.match(/TABLE\s+-\s+(\w+)\./g) || [];
1999
+ tableLines.forEach(line => {
2000
+ const match = line.match(/TABLE\s+-\s+(\w+)\./);
2001
+ if (match && match[1] && match[1] !== 'public') {
2002
+ schemas.add(match[1]);
2003
+ }
2004
+ });
2005
+
2006
+ // Método 3: Buscar "SCHEMA COMMENT" o "ACL - SCHEMA"
2007
+ const schemaRefs = listOutput.match(/(?:SCHEMA COMMENT|ACL - SCHEMA)\s+-\s+(\w+)/g) || [];
2008
+ schemaRefs.forEach(line => {
2009
+ const match = line.match(/(?:SCHEMA COMMENT|ACL - SCHEMA)\s+-\s+(\w+)/);
2010
+ if (match && match[1] && match[1] !== 'public') {
2011
+ schemas.add(match[1]);
2012
+ }
2013
+ });
2014
+
2015
+ logInfo(`pg_restore --list ejecutado correctamente`);
2016
+ } else {
2017
+ logInfo(`pg_restore --list no devolvió output, buscando en contenido del backup...`);
2018
+ }
2019
+
2020
+ // Si no encontramos schemas con --list, parsear el archivo directamente
2021
+ if (schemas.size === 0) {
2022
+ try {
2023
+ const backupContent = readFileSync(localFilePath, 'utf-8').substring(0, 500000); // Primeros 500KB
2024
+
2025
+ // Buscar patrones "schema.table" o "CREATE TABLE schema."
2026
+ const schemaPatterns = [
2027
+ /CREATE TABLE\s+(\w+)\./gi,
2028
+ /INSERT INTO\s+(\w+)\./gi,
2029
+ /ALTER TABLE\s+(\w+)\./gi,
2030
+ /DROP.*ON\s+(\w+)\./gi,
2031
+ ];
2032
+
2033
+ schemaPatterns.forEach(pattern => {
2034
+ const matches = backupContent.matchAll(pattern);
2035
+ for (const match of matches) {
2036
+ if (match[1] && match[1] !== 'public' && match[1] !== 'pg_catalog') {
2037
+ schemas.add(match[1]);
2038
+ }
2039
+ }
2040
+ });
2041
+
2042
+ logInfo('Schemas detectados por análisis directo del backup');
2043
+ } catch (parseError) {
2044
+ logInfo(`No se pudo parsear directamente: ${parseError.message}`);
2045
+ }
2046
+ }
2047
+
2048
+ } catch (listError) {
2049
+ logInfo(`Error en detección de schemas: ${listError.message}`);
2050
+ }
2051
+
2052
+ // Crear los schemas detectados
2053
+ if (schemas.size > 0) {
2054
+ logInfo(`Schemas detectados: ${Array.from(schemas).join(', ')}`);
2055
+ await addCommandLog('info', `Creando schemas: ${Array.from(schemas).join(', ')}`);
2056
+
2057
+ const psqlPath = pgRestorePath.replace('pg_restore', 'psql');
2058
+ const psqlExe = psqlPath.replace('.exe', '') + '.exe';
2059
+
2060
+ // Verificar que psql existe
2061
+ if (!existsSync(psqlExe)) {
2062
+ logInfo('psql no disponible, schemas deben existir previamente');
2063
+ } else {
2064
+ // Crear cada schema
2065
+ for (const schemaName of schemas) {
2066
+ try {
2067
+ const createSchemaSQL = `CREATE SCHEMA IF NOT EXISTS "${schemaName}";`;
2068
+ const psqlArgs = [];
2069
+
2070
+ // Conexión (misma que pg_restore)
2071
+ if (target_environment.connection_type === 'supabase') {
2072
+ const projectRef = target_environment.supabase_project_ref;
2073
+ psqlArgs.push(`--host=db.${projectRef}.supabase.co`);
2074
+ psqlArgs.push(`--port=5432`);
2075
+ psqlArgs.push(`--username=postgres`);
2076
+ psqlArgs.push(`--dbname=postgres`);
2077
+ } else if (target_environment.connection_type === 'postgresql') {
2078
+ psqlArgs.push(`--host=${target_environment.db_host}`);
2079
+ psqlArgs.push(`--port=${target_environment.db_port || 5432}`);
2080
+ psqlArgs.push(`--username=${target_environment.db_user}`);
2081
+ psqlArgs.push(`--dbname=${target_environment.db_name}`);
2082
+ }
2083
+
2084
+ psqlArgs.push('--command', createSchemaSQL);
2085
+ psqlArgs.push('--no-psqlrc'); // No cargar archivos de configuración
2086
+ psqlArgs.push('--quiet'); // Silencioso
2087
+
2088
+ logInfo(`Creando schema: ${schemaName}`);
2089
+
2090
+ const createResult = spawnSync(psqlExe, psqlArgs, {
2091
+ env: process.env,
2092
+ encoding: 'utf-8',
2093
+ stdio: 'pipe',
2094
+ });
2095
+
2096
+ if (createResult.status === 0) {
2097
+ logSuccess(`✓ Schema "${schemaName}" creado`);
2098
+ } else {
2099
+ const stderr = createResult.stderr || '';
2100
+ if (stderr.includes('already exists')) {
2101
+ logInfo(`✓ Schema "${schemaName}" ya existe`);
2102
+ } else if (stderr.includes('permission denied')) {
2103
+ logError(`✗ Sin permisos para crear schema "${schemaName}"`);
2104
+ throw new Error(`No hay permisos para crear el schema "${schemaName}"`);
2105
+ } else {
2106
+ logInfo(`⚠ Schema "${schemaName}": ${stderr.substring(0, 200)}`);
2107
+ }
2108
+ }
2109
+ } catch (schemaError) {
2110
+ logError(`Error creando schema "${schemaName}": ${schemaError.message}`);
2111
+ throw schemaError;
2112
+ }
2113
+ }
2114
+ }
2115
+ } else {
2116
+ logInfo('No se detectaron schemas personalizados (solo public)');
2117
+ }
2118
+
2119
+ // PASO 2: Construir argumentos de pg_restore
1962
2120
  const pgRestoreArgs = [];
1963
2121
 
1964
2122
  // Conexión
@@ -2906,6 +3064,24 @@ ${colors.bold}¿QUÉ HACE EL AGENTE?${colors.reset}
2906
3064
  ${colors.green}✓${colors.reset} Ejecuta comandos git (commit, push, pull, etc)
2907
3065
  ${colors.green}✓${colors.reset} Usa IA para interpretar tareas en lenguaje natural
2908
3066
  ${colors.green}✓${colors.reset} Mejora mensajes de commit automáticamente
3067
+ ${colors.green}✓${colors.reset} ${colors.bold}Backup y restore de bases de datos PostgreSQL${colors.reset}
3068
+ ${colors.green}✓${colors.reset} Auto-creación de schemas en restauraciones
3069
+
3070
+ ${colors.bold}BACKUP Y RESTORE DE BASES DE DATOS${colors.reset}
3071
+ El agente puede crear backups y restaurar bases de datos PostgreSQL/Supabase:
3072
+
3073
+ ${colors.yellow}Backup:${colors.reset}
3074
+ • Descarga PostgreSQL 17.2 automáticamente (primera vez)
3075
+ • Crea backups con pg_dump (formato custom, tar o SQL)
3076
+ • Sube a Supabase Storage automáticamente
3077
+ • Soporta backups completos, por schema o por tabla
3078
+
3079
+ ${colors.yellow}Restore:${colors.reset}
3080
+ • Descarga backup desde Storage
3081
+ • ${colors.bold}Detecta y crea schemas automáticamente${colors.reset} (v1.5.0+)
3082
+ • Ejecuta pg_restore con progreso en tiempo real
3083
+ • Maneja warnings de DROP gracefully
3084
+ • Continúa aunque objetos no existan previamente
2909
3085
 
2910
3086
  ${colors.bold}CONFIGURACIÓN${colors.reset}
2911
3087
  La API key de Claude se configura desde:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deinossrl/dgp-agent",
3
- "version": "1.4.59",
3
+ "version": "1.5.1",
4
4
  "description": "Agente local para Despliegue-GPT - Reporta el estado del repositorio Git a la plataforma TenMinute IA",
5
5
  "main": "index.mjs",
6
6
  "bin": {