entexto-cli 1.4.3 → 1.4.4

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.
@@ -85,13 +85,17 @@ module.exports = async function pull(options) {
85
85
 
86
86
  // Escribir archivos localmente
87
87
  let escritos = 0;
88
- for (const { ruta, content } of data.archivos) {
88
+ for (const { ruta, content, isBinary } of data.archivos) {
89
89
  const destFile = path.join(absDir, ruta);
90
90
  const dir = path.dirname(destFile);
91
91
 
92
92
  try {
93
93
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
94
- fs.writeFileSync(destFile, content, 'utf8');
94
+ if (isBinary) {
95
+ fs.writeFileSync(destFile, Buffer.from(content, 'base64'));
96
+ } else {
97
+ fs.writeFileSync(destFile, content, 'utf8');
98
+ }
95
99
  escritos++;
96
100
  } catch (err) {
97
101
  console.warn(chalk.yellow(` ⚠ No se pudo escribir: ${ruta} — ${err.message}`));
@@ -178,19 +178,24 @@ module.exports = async function sync(options) {
178
178
  try {
179
179
  const pullData = await pullProject(uuid);
180
180
  const archivos = pullData.archivos || [];
181
- const contentMap = new Map(archivos.map(a => [a.ruta, a.content]));
181
+ const contentMap = new Map(archivos.map(a => [a.ruta, a]));
182
182
  sp.stop();
183
183
  let downloaded = 0;
184
184
  for (const ruta of aDescargar) {
185
- const contenido = contentMap.get(ruta);
186
- if (contenido === undefined) {
185
+ const entrada = contentMap.get(ruta);
186
+ if (!entrada) {
187
187
  console.log(' ' + chalk.yellow('⚠') + ' No encontrado en pull: ' + chalk.gray(ruta));
188
188
  continue;
189
189
  }
190
+ const contenido = entrada.content;
190
191
  const destFile = path.join(absDir, ruta);
191
192
  const destDir = path.dirname(destFile);
192
193
  if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
193
- fs.writeFileSync(destFile, contenido, 'utf8');
194
+ if (entrada.isBinary) {
195
+ fs.writeFileSync(destFile, Buffer.from(contenido, 'base64'));
196
+ } else {
197
+ fs.writeFileSync(destFile, contenido, 'utf8');
198
+ }
194
199
  localUploadedAt.set(ruta, Date.now()); // evitar re-subida por chokidar
195
200
  console.log(' ' + chalk.cyan('↓') + ' ' + chalk.white(ruta) + chalk.gray(' ← remoto'));
196
201
  downloaded++;
@@ -233,11 +238,16 @@ module.exports = async function sync(options) {
233
238
 
234
239
  // Reintenta una operación FS hasta maxRetries veces con delay ms entre intentos.
235
240
  // Necesario en Windows donde los archivos abiertos en VS Code quedan bloqueados (EPERM/EBUSY).
236
- function retryFsOp(opName, ruta, fn, maxRetries, delay) {
241
+ // retryFsOp: reintenta una operación FS hasta maxRetries veces si el archivo
242
+ // está bloqueado (EBUSY/EPERM/EACCES). Si se agotan los intentos, llama a
243
+ // onFail() para que el caller pueda limpiar cualquier guard de estado
244
+ // (ej. localDeletedAt) y no quede desincronizado.
245
+ function retryFsOp(opName, ruta, fn, maxRetries, delay, onFail) {
237
246
  let attempt = 0;
238
247
  function tryOnce() {
239
248
  try {
240
249
  fn();
250
+ // Éxito: no se llama onFail
241
251
  } catch (err) {
242
252
  const locked = err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES';
243
253
  if (locked && attempt < maxRetries) {
@@ -247,9 +257,11 @@ module.exports = async function sync(options) {
247
257
  }
248
258
  setTimeout(tryOnce, delay);
249
259
  } else if (locked) {
250
- console.log(' ' + chalk.red('✖') + ' ' + chalk.white(ruta) + chalk.red(' bloqueado — cierra el archivo en VS Code e intenta de nuevo'));
260
+ console.log(' ' + chalk.red('✖') + ' ' + chalk.white(ruta) + chalk.red(' bloqueado — cierra el archivo e intenta de nuevo'));
261
+ if (typeof onFail === 'function') onFail();
251
262
  } else {
252
263
  console.log(' ' + chalk.red('✖') + ' ' + opName + ' fallido (' + (err.code || err.message) + '): ' + ruta);
264
+ if (typeof onFail === 'function') onFail();
253
265
  }
254
266
  }
255
267
  }
@@ -269,7 +281,7 @@ module.exports = async function sync(options) {
269
281
  retryFsOp('borrar', ruta, () => {
270
282
  fs.unlinkSync(destFile);
271
283
  console.log(' ' + chalk.red('✖') + ' ' + chalk.white(ruta) + chalk.gray(' ← borrado remoto'));
272
- }, 5, 500);
284
+ }, 5, 500, () => localDeletedAt.delete(ruta));
273
285
  return;
274
286
  }
275
287
  if (eventType === 'file-rename') {
@@ -286,13 +298,23 @@ module.exports = async function sync(options) {
286
298
  if (!fs.existsSync(newDir)) fs.mkdirSync(newDir, { recursive: true });
287
299
  fs.renameSync(oldFile, newFile);
288
300
  console.log(' ' + chalk.yellow('↔') + ' ' + chalk.white(ruta_antigua) + chalk.gray(' → ') + chalk.white(ruta_nueva));
289
- }, 5, 500);
301
+ }, 5, 500, () => {
302
+ localDeletedAt.delete(ruta_antigua);
303
+ localUploadedAt.delete(ruta_nueva);
304
+ });
290
305
  return;
291
306
  }
292
307
  if (eventType !== 'file-update') return;
293
- const { ruta, contenido } = data || {};
308
+ const { ruta, contenido, isBinary, isText } = data || {};
294
309
  if (!ruta || contenido === undefined) return;
295
310
 
311
+ // Binary files via SSE: skip (handled by full pull on reconnect)
312
+ const binary = isBinary === true || isText === false;
313
+ if (binary) {
314
+ console.log(' ' + chalk.gray('⊘') + ' ' + chalk.gray(ruta) + chalk.gray(' (binario, ignorado por SSE)'));
315
+ return;
316
+ }
317
+
296
318
  // Evitar eco: si acabamos de subir este archivo (< 3s), ignorar
297
319
  const uploadedAt = localUploadedAt.get(ruta);
298
320
  if (uploadedAt && Date.now() - uploadedAt < 3000) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entexto-cli",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "CLI oficial de Entexto — Deploy y gestión de proyectos desde tu terminal",
5
5
  "main": "lib/index.js",
6
6
  "bin": {