entexto-cli 1.4.2 → 1.4.3
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/lib/commands/sync.js +40 -17
- package/package.json +1 -1
package/lib/commands/sync.js
CHANGED
|
@@ -231,21 +231,45 @@ module.exports = async function sync(options) {
|
|
|
231
231
|
let sseStream = null;
|
|
232
232
|
let sseReconnectTimer = null;
|
|
233
233
|
|
|
234
|
+
// Reintenta una operación FS hasta maxRetries veces con delay ms entre intentos.
|
|
235
|
+
// Necesario en Windows donde los archivos abiertos en VS Code quedan bloqueados (EPERM/EBUSY).
|
|
236
|
+
function retryFsOp(opName, ruta, fn, maxRetries, delay) {
|
|
237
|
+
let attempt = 0;
|
|
238
|
+
function tryOnce() {
|
|
239
|
+
try {
|
|
240
|
+
fn();
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const locked = err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES';
|
|
243
|
+
if (locked && attempt < maxRetries) {
|
|
244
|
+
attempt++;
|
|
245
|
+
if (attempt === 1) {
|
|
246
|
+
console.log(' ' + chalk.yellow('⏳') + ' ' + chalk.white(ruta) + chalk.gray(' bloqueado por otro proceso, reintentando...'));
|
|
247
|
+
}
|
|
248
|
+
setTimeout(tryOnce, delay);
|
|
249
|
+
} else if (locked) {
|
|
250
|
+
console.log(' ' + chalk.red('✖') + ' ' + chalk.white(ruta) + chalk.red(' bloqueado — cierra el archivo en VS Code e intenta de nuevo'));
|
|
251
|
+
} else {
|
|
252
|
+
console.log(' ' + chalk.red('✖') + ' ' + opName + ' fallido (' + (err.code || err.message) + '): ' + ruta);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
tryOnce();
|
|
257
|
+
}
|
|
258
|
+
|
|
234
259
|
function manejarEventoRemoto(eventType, data) {
|
|
235
260
|
if (eventType === 'file-delete') {
|
|
236
261
|
const { ruta } = data || {};
|
|
237
262
|
if (!ruta) return;
|
|
238
263
|
const destFile = path.join(absDir, ruta);
|
|
239
|
-
// ⚠️
|
|
240
|
-
//
|
|
241
|
-
//
|
|
264
|
+
// ⚠️ Guard ANTES de la operación FS: chokidar detecta el 'unlink'
|
|
265
|
+
// síncronamente y dispararía eliminarArchivoRemoto antes de que el
|
|
266
|
+
// guard estuviera listo (race condition).
|
|
242
267
|
localDeletedAt.set(ruta, Date.now());
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} catch { console.log(' ' + chalk.red('✖') + ' No se pudo borrar: ' + ruta); }
|
|
268
|
+
if (!fs.existsSync(destFile)) return;
|
|
269
|
+
retryFsOp('borrar', ruta, () => {
|
|
270
|
+
fs.unlinkSync(destFile);
|
|
271
|
+
console.log(' ' + chalk.red('✖') + ' ' + chalk.white(ruta) + chalk.gray(' ← borrado remoto'));
|
|
272
|
+
}, 5, 500);
|
|
249
273
|
return;
|
|
250
274
|
}
|
|
251
275
|
if (eventType === 'file-rename') {
|
|
@@ -254,16 +278,15 @@ module.exports = async function sync(options) {
|
|
|
254
278
|
const oldFile = path.join(absDir, ruta_antigua);
|
|
255
279
|
const newFile = path.join(absDir, ruta_nueva);
|
|
256
280
|
const newDir = path.dirname(newFile);
|
|
257
|
-
// ⚠️ Guards ANTES de
|
|
281
|
+
// ⚠️ Guards ANTES de la operación FS por el mismo motivo.
|
|
258
282
|
localDeletedAt.set(ruta_antigua, Date.now());
|
|
259
283
|
localUploadedAt.set(ruta_nueva, Date.now());
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
} catch { console.log(' ' + chalk.red('✖') + ' No se pudo renombrar: ' + ruta_antigua); }
|
|
284
|
+
if (!fs.existsSync(oldFile)) return;
|
|
285
|
+
retryFsOp('renombrar', ruta_antigua, () => {
|
|
286
|
+
if (!fs.existsSync(newDir)) fs.mkdirSync(newDir, { recursive: true });
|
|
287
|
+
fs.renameSync(oldFile, newFile);
|
|
288
|
+
console.log(' ' + chalk.yellow('↔') + ' ' + chalk.white(ruta_antigua) + chalk.gray(' → ') + chalk.white(ruta_nueva));
|
|
289
|
+
}, 5, 500);
|
|
267
290
|
return;
|
|
268
291
|
}
|
|
269
292
|
if (eventType !== 'file-update') return;
|