@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.
- package/index.mjs +75 -27
- 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.
|
|
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
|
-
//
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`);
|