@deinossrl/dgp-agent 1.4.53 → 1.4.55

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 +18 -0
  2. package/index.mjs +219 -1
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog - DGP Agent
2
2
 
3
+ ## [1.4.55] - 2026-01-12
4
+
5
+ ### Fixed
6
+ - Ahora se copia `pg_restore.exe` junto con `pg_dump.exe` al instalar PostgreSQL
7
+ - Resuelve error ENOENT al ejecutar comando de restore
8
+
9
+ ## [1.4.54] - 2026-01-12
10
+
11
+ ### Added
12
+ - Implementado comando `pg_restore` para restaurar bases de datos desde backups
13
+ - Descarga automática de backups desde Supabase Storage
14
+ - Soporte para restauración completa, solo esquema, o solo datos
15
+ - Actualización automática de estado en tabla `db_restores`
16
+
17
+ ### Changed
18
+ - `pg_restore` viene incluido con pg_dump (mismo binario de PostgreSQL)
19
+ - Limpieza automática de archivos temporales después de restore
20
+
3
21
  ## [1.4.53] - 2026-01-12
4
22
 
5
23
  ### Fixed
package/index.mjs CHANGED
@@ -1519,12 +1519,19 @@ async function findOrInstallPgDump() {
1519
1519
  const extractedPgDump = join(BIN_DIR, 'pgsql', 'bin', 'pg_dump.exe');
1520
1520
 
1521
1521
  if (existsSync(extractedPgDump)) {
1522
- // Copiar pg_dump.exe y TODAS las DLLs al BIN_DIR raíz
1522
+ // Copiar pg_dump.exe, pg_restore.exe y TODAS las DLLs al BIN_DIR raíz
1523
1523
  const binSrcDir = join(BIN_DIR, 'pgsql', 'bin');
1524
1524
 
1525
1525
  // Copiar pg_dump.exe
1526
1526
  execSync(`copy "${extractedPgDump}" "${localPgDump}"`, { shell: 'cmd.exe' });
1527
1527
 
1528
+ // Copiar pg_restore.exe
1529
+ const extractedPgRestore = join(BIN_DIR, 'pgsql', 'bin', 'pg_restore.exe');
1530
+ const localPgRestore = join(BIN_DIR, 'pg_restore.exe');
1531
+ if (existsSync(extractedPgRestore)) {
1532
+ execSync(`copy "${extractedPgRestore}" "${localPgRestore}"`, { shell: 'cmd.exe' });
1533
+ }
1534
+
1528
1535
  // Copiar TODAS las DLLs (no solo algunas específicas)
1529
1536
  execSync(`copy "${binSrcDir}\\*.dll" "${BIN_DIR}\\"`, { shell: 'cmd.exe' });
1530
1537
 
@@ -1568,6 +1575,13 @@ async function findOrInstallPgDump() {
1568
1575
  // Copiar pg_dump.exe
1569
1576
  execSync(`copy "${extractedPgDump}" "${localPgDump}"`, { shell: 'cmd.exe' });
1570
1577
 
1578
+ // Copiar pg_restore.exe
1579
+ const extractedPgRestore = join(BIN_DIR, 'pgsql', 'bin', 'pg_restore.exe');
1580
+ const localPgRestore = join(BIN_DIR, 'pg_restore.exe');
1581
+ if (existsSync(extractedPgRestore)) {
1582
+ execSync(`copy "${extractedPgRestore}" "${localPgRestore}"`, { shell: 'cmd.exe' });
1583
+ }
1584
+
1571
1585
  // Copiar TODAS las DLLs
1572
1586
  execSync(`copy "${binSrcDir}\\*.dll" "${BIN_DIR}\\"`, { shell: 'cmd.exe' });
1573
1587
 
@@ -1872,6 +1886,207 @@ async function executePgDump(command) {
1872
1886
  }
1873
1887
  }
1874
1888
 
1889
+ /**
1890
+ * Ejecuta pg_restore para restaurar una base de datos desde un backup
1891
+ * @param {Object} command - El comando con params de restore
1892
+ */
1893
+ async function executePgRestore(command) {
1894
+ const { id, params } = command;
1895
+ const { restore_id, backup_storage_path, target_environment, restore_type, clean_before_restore } = params;
1896
+
1897
+ let localFilePath = null;
1898
+
1899
+ try {
1900
+ console.log('');
1901
+ logInfo(`=== Ejecutando pg_restore (Restauración) ===`);
1902
+ await addCommandLog('info', 'Iniciando restore...');
1903
+
1904
+ // Verificar que pg_restore esté disponible
1905
+ const pgRestoreCmd = await findOrInstallPgDump(); // Usa la misma función, pg_restore viene con pg_dump
1906
+ if (!pgRestoreCmd) {
1907
+ throw new Error('pg_restore no está disponible');
1908
+ }
1909
+
1910
+ const pgRestorePath = pgRestoreCmd.replace('pg_dump', 'pg_restore');
1911
+ logInfo(`Usando pg_restore: ${pgRestorePath}`);
1912
+
1913
+ // Descargar backup desde Storage
1914
+ logInfo(`Descargando backup desde Storage: ${backup_storage_path}`);
1915
+ await addCommandLog('info', `Descargando backup: ${backup_storage_path}`);
1916
+
1917
+ const supabase = createClient(
1918
+ CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
1919
+ CONFIG.supabaseKey
1920
+ );
1921
+
1922
+ const { data: fileData, error: downloadError } = await supabase.storage
1923
+ .from('database-backups')
1924
+ .download(backup_storage_path);
1925
+
1926
+ if (downloadError) {
1927
+ throw new Error(`Error descargando backup: ${downloadError.message}`);
1928
+ }
1929
+
1930
+ // Guardar temporalmente
1931
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
1932
+ const tempFileName = `restore_${timestamp}.dump`;
1933
+ localFilePath = join(BIN_DIR, tempFileName);
1934
+
1935
+ // Convertir Blob a Buffer y guardar
1936
+ const arrayBuffer = await fileData.arrayBuffer();
1937
+ const buffer = Buffer.from(arrayBuffer);
1938
+ writeFileSync(localFilePath, buffer);
1939
+
1940
+ const fileSize = statSync(localFilePath).size;
1941
+ logSuccess(`Backup descargado (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
1942
+ await addCommandLog('success', `Backup descargado: ${(fileSize / 1024 / 1024).toFixed(2)} MB`);
1943
+
1944
+ // Marcar restore como running
1945
+ await supabase
1946
+ .schema('dgp')
1947
+ .from('db_restores')
1948
+ .update({ status: 'running', started_at: new Date().toISOString() })
1949
+ .eq('id', restore_id);
1950
+
1951
+ // Construir argumentos de pg_restore
1952
+ const pgRestoreArgs = [];
1953
+
1954
+ // Conexión
1955
+ if (target_environment.connection_type === 'supabase') {
1956
+ const projectRef = target_environment.supabase_project_ref;
1957
+ pgRestoreArgs.push(`--host=db.${projectRef}.supabase.co`);
1958
+ pgRestoreArgs.push(`--port=5432`);
1959
+ pgRestoreArgs.push(`--username=postgres`);
1960
+ pgRestoreArgs.push(`--dbname=postgres`);
1961
+
1962
+ // Service role key como password
1963
+ if (target_environment.metadata?.service_role_key) {
1964
+ process.env.PGPASSWORD = target_environment.metadata.service_role_key;
1965
+ }
1966
+ } else if (target_environment.connection_type === 'postgresql') {
1967
+ pgRestoreArgs.push(`--host=${target_environment.db_host}`);
1968
+ pgRestoreArgs.push(`--port=${target_environment.db_port || 5432}`);
1969
+ pgRestoreArgs.push(`--username=${target_environment.db_user}`);
1970
+ pgRestoreArgs.push(`--dbname=${target_environment.db_name}`);
1971
+
1972
+ if (target_environment.db_password_encrypted || target_environment.metadata?.password) {
1973
+ process.env.PGPASSWORD = target_environment.db_password_encrypted || target_environment.metadata.password;
1974
+ }
1975
+ }
1976
+
1977
+ // Opciones de restore
1978
+ if (clean_before_restore) {
1979
+ pgRestoreArgs.push('--clean');
1980
+ }
1981
+
1982
+ if (restore_type === 'schema_only') {
1983
+ pgRestoreArgs.push('--schema-only');
1984
+ } else if (restore_type === 'data_only') {
1985
+ pgRestoreArgs.push('--data-only');
1986
+ }
1987
+
1988
+ // No detener en errores (útil para restauraciones parciales)
1989
+ pgRestoreArgs.push('--exit-on-error');
1990
+ pgRestoreArgs.push('--no-owner');
1991
+ pgRestoreArgs.push('--no-privileges');
1992
+
1993
+ // Archivo de entrada
1994
+ pgRestoreArgs.push(localFilePath);
1995
+
1996
+ // Ejecutar pg_restore
1997
+ logCommand(`Ejecutando: pg_restore [conexión oculta]`);
1998
+ await addCommandLog('command', 'Ejecutando pg_restore...');
1999
+
2000
+ const result = spawnSync(pgRestorePath, pgRestoreArgs, {
2001
+ env: process.env,
2002
+ encoding: 'utf-8',
2003
+ stdio: 'pipe',
2004
+ });
2005
+
2006
+ if (result.error) {
2007
+ throw new Error(`Error ejecutando pg_restore: ${result.error.message}`);
2008
+ }
2009
+
2010
+ if (result.status !== 0) {
2011
+ const errorMsg = result.stderr || result.stdout || 'Unknown error';
2012
+ throw new Error(`pg_restore failed with code ${result.status}: ${errorMsg}`);
2013
+ }
2014
+
2015
+ logSuccess('Restauración completada');
2016
+ await addCommandLog('success', 'Base de datos restaurada');
2017
+
2018
+ // Actualizar estado en DB
2019
+ await supabase
2020
+ .schema('dgp')
2021
+ .from('db_restores')
2022
+ .update({
2023
+ status: 'completed',
2024
+ completed_at: new Date().toISOString()
2025
+ })
2026
+ .eq('id', restore_id);
2027
+
2028
+ logSuccess('Registro de restore actualizado');
2029
+
2030
+ // Limpiar archivo local
2031
+ try {
2032
+ unlinkSync(localFilePath);
2033
+ logInfo('Archivo temporal eliminado');
2034
+ } catch (e) {
2035
+ // Ignorar
2036
+ }
2037
+
2038
+ // Marcar comando como completado
2039
+ await updateCommandStatus(id, 'completed', {
2040
+ restore_id: restore_id,
2041
+ restored_from: backup_storage_path
2042
+ });
2043
+
2044
+ console.log('');
2045
+ logSuccess(`=== Restauración completada exitosamente ===`);
2046
+ console.log('');
2047
+
2048
+ return { success: true };
2049
+
2050
+ } catch (error) {
2051
+ logError(`Restore failed: ${error.message}`);
2052
+ await addCommandLog('error', error.message);
2053
+
2054
+ // Limpiar archivo local si existe
2055
+ if (localFilePath && existsSync(localFilePath)) {
2056
+ try {
2057
+ unlinkSync(localFilePath);
2058
+ } catch (e) {
2059
+ // Ignorar
2060
+ }
2061
+ }
2062
+
2063
+ // Actualizar estado del restore en DB
2064
+ try {
2065
+ const supabase = createClient(
2066
+ CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
2067
+ CONFIG.supabaseKey
2068
+ );
2069
+
2070
+ await supabase
2071
+ .schema('dgp')
2072
+ .from('db_restores')
2073
+ .update({
2074
+ status: 'failed',
2075
+ error_message: error.message
2076
+ })
2077
+ .eq('id', restore_id);
2078
+ } catch (e) {
2079
+ // Ignorar errores al actualizar
2080
+ }
2081
+
2082
+ await updateCommandStatus(id, 'failed', {}, error.message);
2083
+ return { success: false, error: error.message };
2084
+ } finally {
2085
+ // Limpiar variables de entorno
2086
+ delete process.env.PGPASSWORD;
2087
+ }
2088
+ }
2089
+
1875
2090
  /**
1876
2091
  * Ejecuta un comando recibido
1877
2092
  * @param {Object} command - El comando a ejecutar
@@ -1898,6 +2113,9 @@ async function executeCommand(command, useAI = false) {
1898
2113
  case 'pg_dump':
1899
2114
  return await executePgDump(command);
1900
2115
 
2116
+ case 'pg_restore':
2117
+ return await executePgRestore(command);
2118
+
1901
2119
  case 'rollback':
1902
2120
  logError('Rollback not implemented yet');
1903
2121
  await updateCommandStatus(command.id, 'failed', {}, 'Rollback not implemented');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deinossrl/dgp-agent",
3
- "version": "1.4.53",
3
+ "version": "1.4.55",
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": {