qdd-framework 2.9.10 → 2.10.0-rc.10

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.
Files changed (3) hide show
  1. package/bin/qdd.js +49 -20
  2. package/install.js +123 -11
  3. package/package.json +1 -1
package/bin/qdd.js CHANGED
@@ -2,40 +2,69 @@
2
2
 
3
3
  const { spawnSync } = require('child_process');
4
4
  const path = require('path');
5
- const os = require('os');
6
5
  const fs = require('fs');
7
6
 
8
- const binaryExt = os.platform() === 'win32' ? '.exe' : '';
9
- const binaryPath = path.join(__dirname, `qdd${binaryExt}`);
10
- const instalador = path.join(__dirname, '..', 'install.js');
7
+ // rutaDelBinario: en Windows, qdd.exe vive fuera del paquete, en la carpeta de su versión (install.js, FND-142). Una
8
+ // instalación anterior a ese cambio, o sin LOCALAPPDATA, lo tiene dentro del paquete.
9
+ function rutaDelBinario({ platform, env, dir, version, existe }) {
10
+ const nombre = platform === 'win32' ? 'qdd.exe' : 'qdd';
11
+ const enElPaquete = path.join(dir, nombre);
12
+ if (platform !== 'win32' || !env.LOCALAPPDATA || !version) {
13
+ return enElPaquete;
14
+ }
15
+ const porVersion = path.join(env.LOCALAPPDATA, 'qdd', 'bin', version, nombre);
16
+ return existe(porVersion) ? porVersion : enElPaquete;
17
+ }
18
+
19
+ function versionDelPaquete() {
20
+ try {
21
+ return require('../package.json').version;
22
+ } catch (_) {
23
+ return '';
24
+ }
25
+ }
26
+
27
+ function binarioActual() {
28
+ return rutaDelBinario({ platform: process.platform, env: process.env, dir: __dirname, version: versionDelPaquete(), existe: fs.existsSync });
29
+ }
11
30
 
12
31
  // Con npm 12, quien instala sin --allow-scripts=qdd-framework no corrió el postinstall (FND-143): el primer qdd baja el
13
32
  // binario con el mismo instalador. Lo que escribe va a stderr: otro programa puede estar leyendo la salida de qdd.
14
33
  function bajarAlPrimerUso() {
15
- if (fs.existsSync(binaryPath) || !fs.existsSync(instalador)) {
34
+ const instalador = path.join(__dirname, '..', 'install.js');
35
+ if (fs.existsSync(binarioActual()) || !fs.existsSync(instalador)) {
16
36
  return;
17
37
  }
18
38
  console.error('[QDD] qdd no tiene su binario: lo bajo ahora, porque npm no corrió su instalación.');
19
39
  spawnSync(process.execPath, [instalador, '--primer-uso'], { stdio: ['ignore', 2, 'inherit'] });
20
40
  }
21
41
 
22
- bajarAlPrimerUso();
42
+ function main() {
43
+ bajarAlPrimerUso();
44
+ const binaryPath = binarioActual();
23
45
 
24
- const result = spawnSync(binaryPath, process.argv.slice(2), {
25
- stdio: 'inherit'
26
- });
46
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
47
+ stdio: 'inherit'
48
+ });
27
49
 
28
- // Sin binario, lo más probable es que npm esté reinstalando qdd: 75 es «todavía no» para los pedidos del panel (FND-135).
29
- if (result.error && result.error.code === 'ENOENT') {
30
- console.error('[QDD] qdd no tiene su binario: se está actualizando, o npm bloqueó su instalación (npm 12 lo hace por defecto).');
31
- console.error('[QDD] Si sigue así, reinstala el paquete con: npm install -g qdd-framework --allow-scripts=qdd-framework');
32
- process.exit(75);
33
- }
50
+ // Sin binario, lo más probable es que npm esté reinstalando qdd: 75 es «todavía no» para los pedidos del panel (FND-135).
51
+ if (result.error && result.error.code === 'ENOENT') {
52
+ console.error('[QDD] qdd no tiene su binario: se está actualizando, o npm bloqueó su instalación (npm 12 lo hace por defecto).');
53
+ console.error('[QDD] Si sigue así, reinstala el paquete con: npm install -g qdd-framework --allow-scripts=qdd-framework');
54
+ process.exit(75);
55
+ }
56
+
57
+ if (result.error) {
58
+ console.error(`[QDD] No se pudo ejecutar el binario nativo ${binaryPath}: ${result.error.message}`);
59
+ console.error('[QDD] Reinstala el paquete con: npm install -g qdd-framework --allow-scripts=qdd-framework');
60
+ process.exit(1);
61
+ }
34
62
 
35
- if (result.error) {
36
- console.error(`[QDD] No se pudo ejecutar el binario nativo ${binaryPath}: ${result.error.message}`);
37
- console.error('[QDD] Reinstala el paquete con: npm install -g qdd-framework --allow-scripts=qdd-framework');
38
- process.exit(1);
63
+ process.exit(result.status === null ? 1 : result.status);
39
64
  }
40
65
 
41
- process.exit(result.status === null ? 1 : result.status);
66
+ module.exports = { rutaDelBinario };
67
+
68
+ if (require.main === module) {
69
+ main();
70
+ }
package/install.js CHANGED
@@ -71,6 +71,9 @@ function main() {
71
71
  const binaryName = `qdd${binaryExt}`;
72
72
  const binDir = path.join(__dirname, 'bin');
73
73
 
74
+ const carpeta = carpetaDeVersiones({ os, env: process.env });
75
+ const destino = destinoDelBinario({ carpeta, version: pkg.version, binDir, binaryName });
76
+
74
77
  if (!fs.existsSync(binDir)) {
75
78
  fs.mkdirSync(binDir, { recursive: true });
76
79
  }
@@ -88,7 +91,28 @@ function main() {
88
91
  process.exit(0);
89
92
  }
90
93
 
91
- descargarDesdeRelease({ os, arch, ext, binaryName, binDir });
94
+ // Reinstalar la misma versión: su binario puede estar corriendo y no hace falta tocarlo.
95
+ if (carpeta && respondeBinario(destino)) {
96
+ console.log(`[QDD] La versión ${pkg.version} ya está en ${destino}.`);
97
+ terminarInstalacion({ bin: destino, carpeta });
98
+ return;
99
+ }
100
+
101
+ if (process.env.QDD_INSTALL_BINARIO) {
102
+ instalarBinarioIndicado({ origen: process.env.QDD_INSTALL_BINARIO, destino, carpeta });
103
+ return;
104
+ }
105
+
106
+ descargarDesdeRelease({ os, arch, ext, binaryName, binDir, destino, carpeta });
107
+ }
108
+
109
+ // QDD_INSTALL_BINARIO instala ese binario en vez de descargarlo: es como el CI prueba el paquete antes de publicarlo.
110
+ function instalarBinarioIndicado({ origen, destino, carpeta }) {
111
+ try {
112
+ terminarInstalacion({ bin: instalarCopia(origen, destino), carpeta });
113
+ } catch (err) {
114
+ fallar(`no se pudo instalar ${origen}: ${err.message}`);
115
+ }
92
116
  }
93
117
 
94
118
  // tomarTurno: varios qdd sin binario a la vez (un panel, por ejemplo) no bajan cada uno el suyo.
@@ -133,10 +157,10 @@ function carpetaDeDescarga() {
133
157
  }
134
158
 
135
159
  // 2. Descarga desde GitHub Releases
136
- function descargarDesdeRelease({ os, arch, ext, binaryName, binDir }) {
160
+ function descargarDesdeRelease({ os, arch, ext, binaryName, binDir, destino, carpeta }) {
137
161
  const url = `https://github.com/${REPO}/releases/download/${VERSION}/qdd_${os}_${arch}.${ext}`;
138
- const carpeta = carpetaDeDescarga();
139
- const downloadDest = path.join(carpeta, `qdd-download.${ext}`);
162
+ const descarga = carpetaDeDescarga();
163
+ const downloadDest = path.join(descarga, `qdd-download.${ext}`);
140
164
 
141
165
  console.log(`[QDD] Descargando binario nativo para ${os}-${arch}...`);
142
166
  console.log(`[QDD] URL: ${url}`);
@@ -169,9 +193,9 @@ function descargarDesdeRelease({ os, arch, ext, binaryName, binDir }) {
169
193
  function extractBinary() {
170
194
  console.log('[QDD] Extrayendo...');
171
195
  try {
172
- const bin = instalarDescarga({ os, paquete: downloadDest, carpeta, binaryName, binDir, env: process.env });
196
+ const bin = instalarDescarga({ os, paquete: downloadDest, carpeta: descarga, binaryName, binDir, env: process.env, destino });
173
197
  console.log('[QDD] ¡Instalación nativa completada exitosamente!');
174
- actualizarConsola(bin);
198
+ terminarInstalacion({ bin, carpeta });
175
199
  } catch (err) {
176
200
  fallar(`no se pudo instalar el binario: ${err.message}`);
177
201
  }
@@ -188,8 +212,96 @@ function comandoExtraer(os, paquete, destino, env) {
188
212
  return { programa: 'tar', args: ['-xzf', paquete, '-C', destino] };
189
213
  }
190
214
 
191
- // instalarDescarga deja el binario en bin/ solo si responde; si algo falla, lanza el error y el binario anterior sigue ahí.
192
- function instalarDescarga({ os, paquete, carpeta, binaryName, binDir, env }) {
215
+ // En Windows, npm no puede reemplazar un qdd.exe en uso, y siempre hay uno: el panel y los pedidos corren desde él (issue
216
+ // #107, FND-142). Por eso ahí el binario vive fuera del paquete, en %LOCALAPPDATA%\qdd\bin\<versión>\qdd.exe: lo que
217
+ // corre se queda con su versión y npm no se topa con nada. bin\actual.txt dice cuál es la instalada: lo que corre lo
218
+ // mira para saber que tiene que cerrarse. Linux y macOS pueden reemplazar un binario en uso: siguen con él en el paquete.
219
+ function carpetaDeVersiones({ os, env }) {
220
+ if (os !== 'windows' || !env.LOCALAPPDATA) {
221
+ return '';
222
+ }
223
+ return path.join(env.LOCALAPPDATA, 'qdd', 'bin');
224
+ }
225
+
226
+ function destinoDelBinario({ carpeta, version, binDir, binaryName }) {
227
+ if (!carpeta) {
228
+ return path.join(binDir, binaryName);
229
+ }
230
+ return path.join(carpeta, version, binaryName);
231
+ }
232
+
233
+ function respondeBinario(ruta) {
234
+ try {
235
+ execFileSync(ruta, ['--version'], { stdio: 'pipe', timeout: TOPE_VERSION_MS });
236
+ return true;
237
+ } catch (_) {
238
+ return false;
239
+ }
240
+ }
241
+
242
+ // moverBinario copia si el destino está en otro volumen: el prefijo de npm y %LOCALAPPDATA% pueden estarlo.
243
+ function moverBinario(origen, destino, renombrar = fs.renameSync) {
244
+ try {
245
+ renombrar(origen, destino);
246
+ return;
247
+ } catch (err) {
248
+ if (err.code !== 'EXDEV') {
249
+ throw err;
250
+ }
251
+ }
252
+ copiarBinario(origen, destino);
253
+ fs.rmSync(origen, { force: true });
254
+ }
255
+
256
+ // copiarBinario nunca deja un binario a medias con el nombre final.
257
+ function copiarBinario(origen, destino) {
258
+ const temporal = destino + '.nuevo';
259
+ fs.copyFileSync(origen, temporal);
260
+ fs.renameSync(temporal, destino);
261
+ }
262
+
263
+ function instalarCopia(origen, destino) {
264
+ execFileSync(origen, ['--version'], { stdio: 'pipe', timeout: TOPE_VERSION_MS });
265
+ fs.mkdirSync(path.dirname(destino), { recursive: true });
266
+ copiarBinario(origen, destino);
267
+ return destino;
268
+ }
269
+
270
+ // registrarVersion avisa a lo que corre que hay otra versión y borra las viejas: quedan esta y la anterior. Lo que no se
271
+ // puede borrar sigue corriendo: se borrará en otra actualización.
272
+ function registrarVersion({ carpeta, version, borrar = fs.rmSync }) {
273
+ const puntero = path.join(carpeta, 'actual.txt');
274
+ const anterior = esArchivo(puntero) ? fs.readFileSync(puntero, 'utf8').trim() : '';
275
+ if (anterior !== version) {
276
+ fs.writeFileSync(puntero, version);
277
+ }
278
+ for (const nombre of fs.readdirSync(carpeta)) {
279
+ if (!/^\d+\.\d+\.\d+/.test(nombre) || nombre === version || nombre === anterior) {
280
+ continue;
281
+ }
282
+ try {
283
+ borrar(path.join(carpeta, nombre), { recursive: true, force: true });
284
+ } catch (_) {
285
+ console.log(`[QDD] La versión ${nombre} sigue en uso: se borrará más adelante.`);
286
+ }
287
+ }
288
+ }
289
+
290
+ // terminarInstalacion: primero el panel, y al final el puntero, porque lo que corre se cierra apenas cambia.
291
+ function terminarInstalacion({ bin, carpeta }) {
292
+ actualizarConsola(bin);
293
+ if (!carpeta) {
294
+ return;
295
+ }
296
+ try {
297
+ registrarVersion({ carpeta, version: pkg.version });
298
+ } catch (err) {
299
+ console.error('[QDD] No se pudo registrar la versión instalada:', err.message);
300
+ }
301
+ }
302
+
303
+ // instalarDescarga deja el binario en su destino solo si responde; si algo falla, lanza el error y el binario anterior sigue ahí.
304
+ function instalarDescarga({ os, paquete, carpeta, binaryName, binDir, env, destino = path.join(binDir, binaryName) }) {
193
305
  const { programa, args } = comandoExtraer(os, paquete, carpeta, env);
194
306
  execFileSync(programa, args, { stdio: 'pipe' });
195
307
  const extraido = path.join(carpeta, binaryName);
@@ -198,8 +310,8 @@ function instalarDescarga({ os, paquete, carpeta, binaryName, binDir, env }) {
198
310
  }
199
311
  fs.chmodSync(extraido, 0o755);
200
312
  execFileSync(extraido, ['--version'], { stdio: 'pipe', timeout: TOPE_VERSION_MS });
201
- const destino = path.join(binDir, binaryName);
202
- fs.renameSync(extraido, destino);
313
+ fs.mkdirSync(path.dirname(destino), { recursive: true });
314
+ moverBinario(extraido, destino);
203
315
  fs.rmSync(paquete, { force: true });
204
316
  return destino;
205
317
  }
@@ -226,7 +338,7 @@ function actualizarConsola(bin) {
226
338
  }
227
339
  }
228
340
 
229
- module.exports = { esArchivo, estaDentroDeNodeModules, resolverBinarioLocal, comandoExtraer, instalarDescarga, codigoDeFalla, tomarTurno };
341
+ module.exports = { esArchivo, estaDentroDeNodeModules, resolverBinarioLocal, comandoExtraer, instalarDescarga, codigoDeFalla, carpetaDeVersiones, destinoDelBinario, respondeBinario, moverBinario, instalarCopia, registrarVersion, tomarTurno };
230
342
 
231
343
  if (require.main === module) {
232
344
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdd-framework",
3
- "version": "2.9.10",
3
+ "version": "2.10.0-rc.10",
4
4
  "description": "Quality-Driven Development Framework",
5
5
  "bin": {
6
6
  "qdd": "bin/qdd.js"