@deinossrl/dgp-agent 1.4.38 → 1.4.40

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 (2) hide show
  1. package/index.mjs +75 -27
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -352,7 +352,7 @@ function getPlinkPath() {
352
352
  }
353
353
 
354
354
  // Versión del agente
355
- const AGENT_VERSION = '1.4.38';
355
+ const AGENT_VERSION = '1.4.40';
356
356
  let AGENT_MODE = 'smart'; // Siempre inteligente
357
357
 
358
358
  // Configuración (prioridad: env vars > archivo config > platform config > defaults)
@@ -887,6 +887,61 @@ function shell(command, options = {}) {
887
887
  // Alias para compatibilidad
888
888
  const shellSync = shell;
889
889
 
890
+ /**
891
+ * Verifica si un error es recuperable (timeout, conexión, etc)
892
+ */
893
+ function isRecoverableError(error) {
894
+ const msg = error.message.toLowerCase();
895
+ return (
896
+ msg.includes('timeout') ||
897
+ msg.includes('timed out') ||
898
+ msg.includes('connection refused') ||
899
+ msg.includes('network') ||
900
+ msg.includes('temporarily unavailable') ||
901
+ (error.code === 255 && msg.includes('ssh'))
902
+ );
903
+ }
904
+
905
+ /**
906
+ * Ejecuta un comando shell de forma asíncrona con reintentos automáticos
907
+ */
908
+ async function shellAsyncWithRetry(command, options = {}, maxRetries = 3, baseDelay = 2000) {
909
+ let lastError;
910
+
911
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
912
+ try {
913
+ logInfo(`[Attempt ${attempt}/${maxRetries}] Executing: ${command.substring(0, 80)}...`);
914
+ const result = await shellAsync(command, options);
915
+ if (attempt > 1) {
916
+ logSuccess(`✓ Command succeeded after ${attempt} attempts`);
917
+ }
918
+ return result;
919
+ } catch (error) {
920
+ lastError = error;
921
+
922
+ // Si no es recuperable, fallar inmediatamente
923
+ if (!isRecoverableError(error)) {
924
+ throw error;
925
+ }
926
+
927
+ // Si es el último intento, fallar
928
+ if (attempt === maxRetries) {
929
+ logError(`✗ Command failed after ${maxRetries} attempts`);
930
+ throw error;
931
+ }
932
+
933
+ // Calcular delay con backoff exponencial
934
+ const delay = baseDelay * Math.pow(2, attempt - 1);
935
+ log(`⚠️ Recoverable error (${error.message}), retrying in ${delay}ms...`, 'yellow');
936
+
937
+ // Esperar antes de reintentar
938
+ await new Promise(resolve => setTimeout(resolve, delay));
939
+ }
940
+ }
941
+
942
+ throw lastError;
943
+ }
944
+
890
945
  /**
891
946
  * Ejecuta un comando shell de forma asíncrona con output en tiempo real
892
947
  */
@@ -1225,24 +1280,27 @@ async function executeDeploy(command) {
1225
1280
  ? '/var/www/tenminuteia-prod/'
1226
1281
  : '/var/www/tenminuteia-staging/');
1227
1282
 
1228
- // Construir base del comando SSH
1283
+ // Construir base del comando SSH con timeout largo para conexiones lentas
1229
1284
  const sshBase = sshKeyPath
1230
- ? `ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no ${ssh_user}@${server_host}`
1231
- : `ssh ${ssh_user}@${server_host}`;
1285
+ ? `ssh -i ${sshKeyPath} -o StrictHostKeyChecking=no -o ConnectTimeout=60 -o ServerAliveInterval=10 ${ssh_user}@${server_host}`
1286
+ : `ssh -o ConnectTimeout=60 -o ServerAliveInterval=10 ${ssh_user}@${server_host}`;
1232
1287
 
1233
1288
  // Step 1: Git clone/pull on server
1234
1289
  currentStep = 'git_setup_server';
1235
1290
  logCommand(`[1/4] Setting up repository on server...`);
1236
1291
  steps.push({ step: currentStep, status: 'running' });
1237
1292
 
1238
- // Verificar si el directorio existe
1239
- const checkDirCmd = `${sshBase} "test -d ${projectFolder} && echo EXISTS || echo NOT_EXISTS"`;
1240
- const dirCheck = await shellAsync(checkDirCmd);
1241
- const dirExists = dirCheck.stdout.trim() === 'EXISTS';
1242
-
1243
- if (!dirExists) {
1244
- // El directorio no existe - clonar el repositorio
1245
- logInfo(`Repository directory doesn't exist, cloning...`);
1293
+ // Intentar pull, si falla entonces clonar
1294
+ let needsClone = false;
1295
+ try {
1296
+ logInfo(`Attempting to update existing repository...`);
1297
+ const gitPullCmd = `${sshBase} "cd ${projectFolder} && git fetch origin && git checkout ${branch} && git pull origin ${branch}"`;
1298
+ await shellAsyncWithRetry(gitPullCmd, {}, 5, 3000); // 5 reintentos, 3s base delay
1299
+ logSuccess(`Code updated on server`);
1300
+ } catch (pullError) {
1301
+ // Si falla el pull, probablemente el repo no existe
1302
+ logInfo(`Repository not found, cloning...`);
1303
+ needsClone = true;
1246
1304
 
1247
1305
  // Obtener la URL del repositorio desde el remoto local
1248
1306
  let repoUrl = '';
@@ -1256,20 +1314,10 @@ async function executeDeploy(command) {
1256
1314
 
1257
1315
  // Crear directorio padre y clonar
1258
1316
  const parentDir = projectFolder.substring(0, projectFolder.lastIndexOf('/'));
1259
- const cloneCmd = `${sshBase} "mkdir -p ${parentDir} && git clone ${repoUrl} ${projectFolder}"`;
1260
- await shellAsync(cloneCmd);
1261
-
1262
- // Checkout de la rama correcta
1263
- const checkoutCmd = `${sshBase} "cd ${projectFolder} && git checkout ${branch}"`;
1264
- await shellAsync(checkoutCmd);
1317
+ const cloneCmd = `${sshBase} "mkdir -p ${parentDir} && git clone ${repoUrl} ${projectFolder} && cd ${projectFolder} && git checkout ${branch}"`;
1318
+ await shellAsyncWithRetry(cloneCmd, {}, 5, 3000); // 5 reintentos, 3s base delay
1265
1319
 
1266
1320
  logSuccess(`Repository cloned successfully`);
1267
- } else {
1268
- // El directorio existe - hacer pull
1269
- logInfo(`Repository exists, pulling latest changes...`);
1270
- const gitPullCmd = `${sshBase} "cd ${projectFolder} && git fetch origin && git checkout ${branch} && git pull origin ${branch}"`;
1271
- await shellAsync(gitPullCmd);
1272
- logSuccess(`Code updated on server`);
1273
1321
  }
1274
1322
 
1275
1323
  steps[steps.length - 1].status = 'success';
@@ -1280,7 +1328,7 @@ async function executeDeploy(command) {
1280
1328
  steps.push({ step: currentStep, status: 'running' });
1281
1329
 
1282
1330
  const npmInstallCmd = `${sshBase} "cd ${projectFolder} && npm ci"`;
1283
- await shellAsync(npmInstallCmd);
1331
+ await shellAsyncWithRetry(npmInstallCmd, {}, 3, 2000); // 3 reintentos, 2s base delay
1284
1332
 
1285
1333
  steps[steps.length - 1].status = 'success';
1286
1334
  logSuccess(`Dependencies installed on server`);
@@ -1291,7 +1339,7 @@ async function executeDeploy(command) {
1291
1339
  steps.push({ step: currentStep, status: 'running' });
1292
1340
 
1293
1341
  const npmBuildCmd = `${sshBase} "cd ${projectFolder} && npm run build"`;
1294
- await shellAsync(npmBuildCmd);
1342
+ await shellAsyncWithRetry(npmBuildCmd, {}, 3, 2000); // 3 reintentos, 2s base delay
1295
1343
 
1296
1344
  steps[steps.length - 1].status = 'success';
1297
1345
  logSuccess(`Build completed on server`);
@@ -1302,7 +1350,7 @@ async function executeDeploy(command) {
1302
1350
  steps.push({ step: currentStep, status: 'running' });
1303
1351
 
1304
1352
  const reloadNginxCmd = `${sshBase} "sudo nginx -t && sudo systemctl reload nginx"`;
1305
- await shellAsync(reloadNginxCmd);
1353
+ await shellAsyncWithRetry(reloadNginxCmd, {}, 3, 2000); // 3 reintentos, 2s base delay
1306
1354
 
1307
1355
  steps[steps.length - 1].status = 'success';
1308
1356
  logSuccess(`Nginx reloaded`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deinossrl/dgp-agent",
3
- "version": "1.4.38",
3
+ "version": "1.4.40",
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": {