entexto-cli 1.4.1 → 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 +43 -16
- package/package.json +1 -1
package/lib/commands/sync.js
CHANGED
|
@@ -231,18 +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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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).
|
|
267
|
+
localDeletedAt.set(ruta, Date.now());
|
|
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);
|
|
246
273
|
return;
|
|
247
274
|
}
|
|
248
275
|
if (eventType === 'file-rename') {
|
|
@@ -251,15 +278,15 @@ module.exports = async function sync(options) {
|
|
|
251
278
|
const oldFile = path.join(absDir, ruta_antigua);
|
|
252
279
|
const newFile = path.join(absDir, ruta_nueva);
|
|
253
280
|
const newDir = path.dirname(newFile);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
281
|
+
// ⚠️ Guards ANTES de la operación FS por el mismo motivo.
|
|
282
|
+
localDeletedAt.set(ruta_antigua, Date.now());
|
|
283
|
+
localUploadedAt.set(ruta_nueva, Date.now());
|
|
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);
|
|
263
290
|
return;
|
|
264
291
|
}
|
|
265
292
|
if (eventType !== 'file-update') return;
|