entexto-cli 1.2.0 → 1.3.0

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.
@@ -8,7 +8,7 @@ const ora = require('ora');
8
8
  const FormData = require('form-data');
9
9
  const chokidar = require('chokidar');
10
10
  const { getToken } = require('../utils/config');
11
- const { getProjects, deployFiles, getManifest, streamEvents } = require('../utils/api');
11
+ const { getProjects, deployFiles, getManifest, pullProject, streamEvents } = require('../utils/api');
12
12
  const { hashFile, hashContent } = require('../utils/delta');
13
13
 
14
14
  const IGNORE_NAMES = new Set([
@@ -103,9 +103,10 @@ module.exports = async function sync(options) {
103
103
  process.exit(1);
104
104
  }
105
105
 
106
- // ── 1. Delta inicial: subir solo cambios locales → remoto ───────────────
107
- console.log(chalk.bold('\n🔄 Delta inicial...\n'));
108
- const todosArchivos = recolectarArchivos(absDir);
106
+ // ── 1. Delta inicial bidireccional ──────────────────────────────────────
107
+ console.log(chalk.bold('\n🔄 Sincronización inicial...\n'));
108
+
109
+ // 1a. Manifest remoto { ruta: hash }
109
110
  let manifest = {};
110
111
  try {
111
112
  manifest = await getManifest(uuid);
@@ -113,19 +114,82 @@ module.exports = async function sync(options) {
113
114
  console.log(chalk.gray(' (manifest no disponible, comparando todos)'));
114
115
  }
115
116
 
116
- const aSubir = [];
117
- for (const filePath of todosArchivos) {
118
- const ruta = path.relative(absDir, filePath).replace(/\\/g, '/');
119
- const localHash = hashFile(filePath);
120
- if (manifest[ruta] && manifest[ruta] === localHash) {
117
+ // 1b. Hashes de los archivos locales
118
+ const todosArchivos = recolectarArchivos(absDir);
119
+ const localHashes = new Map();
120
+ for (const fp of todosArchivos) {
121
+ const ruta = path.relative(absDir, fp).replace(/\\/g, '/');
122
+ localHashes.set(ruta, hashFile(fp));
123
+ }
124
+
125
+ // 1c. Clasificar qué hacer con cada archivo
126
+ const aDescargar = []; // ruta[] — remoto → local
127
+ const aSubir = []; // { filePath, ruta }[] — local → remoto
128
+
129
+ // Recorrer archivos remotos
130
+ for (const [ruta, remHash] of Object.entries(manifest)) {
131
+ const localHash = localHashes.get(ruta);
132
+ if (!localHash) {
133
+ // No existe localmente → descargar
134
+ aDescargar.push(ruta);
135
+ } else if (localHash === remHash) {
136
+ // Mismo contenido → sin cambios
121
137
  console.log(' ' + chalk.gray('──') + ' ' + chalk.gray(ruta));
122
138
  } else {
123
- aSubir.push({ filePath, ruta });
139
+ // Hash diferente → si el archivo local fue modificado hace < 10s, local gana
140
+ // de lo contrario remoto gana (remoto es fuente de verdad en sync inicial)
141
+ try {
142
+ const stat = fs.statSync(path.join(absDir, ruta));
143
+ if (Date.now() - stat.mtimeMs < 10000) {
144
+ aSubir.push({ filePath: path.join(absDir, ruta), ruta });
145
+ } else {
146
+ aDescargar.push(ruta);
147
+ }
148
+ } catch {
149
+ aDescargar.push(ruta);
150
+ }
151
+ }
152
+ }
153
+
154
+ // Archivos solo en local (no están en remoto) → subir
155
+ for (const [ruta] of localHashes) {
156
+ if (!(ruta in manifest)) {
157
+ aSubir.push({ filePath: path.join(absDir, ruta), ruta });
158
+ }
159
+ }
160
+
161
+ // 1d. Descargar archivos remotos faltantes o actualizados
162
+ if (aDescargar.length > 0) {
163
+ const sp = ora(`Descargando ${aDescargar.length} archivo(s) del servidor...`).start();
164
+ try {
165
+ const pullData = await pullProject(uuid);
166
+ const archivos = pullData.archivos || [];
167
+ const contentMap = new Map(archivos.map(a => [a.ruta, a.content]));
168
+ sp.stop();
169
+ let downloaded = 0;
170
+ for (const ruta of aDescargar) {
171
+ const contenido = contentMap.get(ruta);
172
+ if (contenido === undefined) {
173
+ console.log(' ' + chalk.yellow('⚠') + ' No encontrado en pull: ' + chalk.gray(ruta));
174
+ continue;
175
+ }
176
+ const destFile = path.join(absDir, ruta);
177
+ const destDir = path.dirname(destFile);
178
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
179
+ fs.writeFileSync(destFile, contenido, 'utf8');
180
+ localUploadedAt.set(ruta, Date.now()); // evitar re-subida por chokidar
181
+ console.log(' ' + chalk.cyan('↓') + ' ' + chalk.white(ruta) + chalk.gray(' ← remoto'));
182
+ downloaded++;
183
+ }
184
+ console.log(chalk.green(` ✔ ${downloaded} archivo(s) descargados\n`));
185
+ } catch (err) {
186
+ sp.fail('Error descargando: ' + (err.response?.data?.error || err.message));
124
187
  }
125
188
  }
126
189
 
190
+ // 1e. Subir archivos locales nuevos o modificados recientemente
127
191
  if (aSubir.length > 0) {
128
- const sp = ora(`Subiendo ${aSubir.length} archivo(s) modificados...`).start();
192
+ const sp = ora(`Subiendo ${aSubir.length} archivo(s) al servidor...`).start();
129
193
  try {
130
194
  const form = new FormData();
131
195
  for (const { filePath, ruta } of aSubir) {
@@ -133,13 +197,18 @@ module.exports = async function sync(options) {
133
197
  localUploadedAt.set(ruta, Date.now());
134
198
  }
135
199
  await deployFiles(uuid, form, false);
136
- sp.succeed(`${aSubir.length} archivo(s) subidos`);
137
- aSubir.forEach(({ ruta }) => console.log(' ' + chalk.green('↑') + ' ' + chalk.white(ruta)));
200
+ sp.stop();
201
+ aSubir.forEach(({ ruta }) =>
202
+ console.log(' ' + chalk.green('↑') + ' ' + chalk.white(ruta) + chalk.gray(' → remoto'))
203
+ );
204
+ console.log(chalk.green(` ✔ ${aSubir.length} archivo(s) subidos\n`));
138
205
  } catch (err) {
139
- sp.fail('Error en delta inicial: ' + (err.response?.data?.error || err.message));
206
+ sp.fail('Error subiendo: ' + (err.response?.data?.error || err.message));
140
207
  }
141
- } else {
142
- console.log(chalk.green(' ✔ Todo sincronizado, sin cambios locales.\n'));
208
+ }
209
+
210
+ if (aDescargar.length === 0 && aSubir.length === 0) {
211
+ console.log(chalk.green(' ✔ Todo sincronizado, sin cambios.\n'));
143
212
  }
144
213
 
145
214
  // ── 2. Canal SSE (remoto → local) ───────────────────────────────────────
@@ -179,6 +248,7 @@ module.exports = async function sync(options) {
179
248
  try {
180
249
  if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
181
250
  fs.writeFileSync(destFile, contenido, 'utf8');
251
+ localUploadedAt.set(ruta, Date.now()); // evitar re-subida por chokidar
182
252
  console.log(' ' + chalk.cyan('↓') + ' ' + chalk.white(ruta) + chalk.gray(' ← remoto'));
183
253
  } catch {
184
254
  console.log(' ' + chalk.red('✖') + ' No se pudo escribir: ' + ruta);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entexto-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
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": {