qdd-framework 2.10.0-rc.1 → 2.10.0-rc.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.
Files changed (3) hide show
  1. package/bin/qdd.js +44 -17
  2. package/install.js +121 -9
  3. package/package.json +1 -1
package/bin/qdd.js CHANGED
@@ -2,26 +2,53 @@
2
2
 
3
3
  const { spawnSync } = require('child_process');
4
4
  const path = require('path');
5
- const os = require('os');
5
+ const fs = require('fs');
6
6
 
7
- const binaryExt = os.platform() === 'win32' ? '.exe' : '';
8
- const binaryPath = path.join(__dirname, `qdd${binaryExt}`);
9
-
10
- const result = spawnSync(binaryPath, process.argv.slice(2), {
11
- stdio: 'inherit'
12
- });
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
+ }
13
18
 
14
- // Sin binario, lo más probable es que npm esté reinstalando qdd: 75 es «todavía no» para los pedidos del panel (FND-135).
15
- if (result.error && result.error.code === 'ENOENT') {
16
- console.error('[QDD] qdd se está actualizando: inténtalo de nuevo en un momento.');
17
- console.error('[QDD] Si sigue así, reinstala el paquete con: npm install -g qdd-framework');
18
- process.exit(75);
19
+ function versionDelPaquete() {
20
+ try {
21
+ return require('../package.json').version;
22
+ } catch (_) {
23
+ return '';
24
+ }
19
25
  }
20
26
 
21
- if (result.error) {
22
- console.error(`[QDD] No se pudo ejecutar el binario nativo ${binaryPath}: ${result.error.message}`);
23
- console.error('[QDD] Reinstala el paquete con: npm install -g qdd-framework');
24
- process.exit(1);
27
+ function main() {
28
+ const binaryPath = rutaDelBinario({ platform: process.platform, env: process.env, dir: __dirname, version: versionDelPaquete(), existe: fs.existsSync });
29
+
30
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
31
+ stdio: 'inherit'
32
+ });
33
+
34
+ // Sin binario, lo más probable es que npm esté reinstalando qdd: 75 es «todavía no» para los pedidos del panel (FND-135).
35
+ if (result.error && result.error.code === 'ENOENT') {
36
+ console.error('[QDD] qdd se está actualizando: inténtalo de nuevo en un momento.');
37
+ console.error('[QDD] Si sigue así, reinstala el paquete con: npm install -g qdd-framework');
38
+ process.exit(75);
39
+ }
40
+
41
+ if (result.error) {
42
+ console.error(`[QDD] No se pudo ejecutar el binario nativo ${binaryPath}: ${result.error.message}`);
43
+ console.error('[QDD] Reinstala el paquete con: npm install -g qdd-framework');
44
+ process.exit(1);
45
+ }
46
+
47
+ process.exit(result.status === null ? 1 : result.status);
25
48
  }
26
49
 
27
- process.exit(result.status === null ? 1 : result.status);
50
+ module.exports = { rutaDelBinario };
51
+
52
+ if (require.main === module) {
53
+ main();
54
+ }
package/install.js CHANGED
@@ -69,6 +69,9 @@ function main() {
69
69
  const binaryName = `qdd${binaryExt}`;
70
70
  const binDir = path.join(__dirname, 'bin');
71
71
 
72
+ const carpeta = carpetaDeVersiones({ os, env: process.env });
73
+ const destino = destinoDelBinario({ carpeta, version: pkg.version, binDir, binaryName });
74
+
72
75
  if (!fs.existsSync(binDir)) {
73
76
  fs.mkdirSync(binDir, { recursive: true });
74
77
  }
@@ -82,11 +85,32 @@ function main() {
82
85
  process.exit(0);
83
86
  }
84
87
 
85
- descargarDesdeRelease({ os, arch, ext, binaryName, binDir });
88
+ // Reinstalar la misma versión: su binario puede estar corriendo y no hace falta tocarlo.
89
+ if (carpeta && respondeBinario(destino)) {
90
+ console.log(`[QDD] La versión ${pkg.version} ya está en ${destino}.`);
91
+ terminarInstalacion({ bin: destino, carpeta });
92
+ return;
93
+ }
94
+
95
+ if (process.env.QDD_INSTALL_BINARIO) {
96
+ instalarBinarioIndicado({ origen: process.env.QDD_INSTALL_BINARIO, destino, carpeta });
97
+ return;
98
+ }
99
+
100
+ descargarDesdeRelease({ os, arch, ext, binaryName, binDir, destino, carpeta });
101
+ }
102
+
103
+ // QDD_INSTALL_BINARIO instala ese binario en vez de descargarlo: es como el CI prueba el paquete antes de publicarlo.
104
+ function instalarBinarioIndicado({ origen, destino, carpeta }) {
105
+ try {
106
+ terminarInstalacion({ bin: instalarCopia(origen, destino), carpeta });
107
+ } catch (err) {
108
+ fallar(`no se pudo instalar ${origen}: ${err.message}`);
109
+ }
86
110
  }
87
111
 
88
112
  // 2. Descarga desde GitHub Releases
89
- function descargarDesdeRelease({ os, arch, ext, binaryName, binDir }) {
113
+ function descargarDesdeRelease({ os, arch, ext, binaryName, binDir, destino, carpeta }) {
90
114
  const url = `https://github.com/${REPO}/releases/download/${VERSION}/qdd_${os}_${arch}.${ext}`;
91
115
  const downloadDest = path.join(__dirname, `qdd-download.${ext}`);
92
116
 
@@ -121,9 +145,9 @@ function descargarDesdeRelease({ os, arch, ext, binaryName, binDir }) {
121
145
  function extractBinary() {
122
146
  console.log('[QDD] Extrayendo...');
123
147
  try {
124
- const bin = instalarDescarga({ os, paquete: downloadDest, carpeta: __dirname, binaryName, binDir, env: process.env });
148
+ const bin = instalarDescarga({ os, paquete: downloadDest, carpeta: __dirname, binaryName, binDir, env: process.env, destino });
125
149
  console.log('[QDD] ¡Instalación nativa completada exitosamente!');
126
- actualizarConsola(bin);
150
+ terminarInstalacion({ bin, carpeta });
127
151
  } catch (err) {
128
152
  fallar(`no se pudo instalar el binario: ${err.message}`);
129
153
  }
@@ -140,8 +164,96 @@ function comandoExtraer(os, paquete, destino, env) {
140
164
  return { programa: 'tar', args: ['-xzf', paquete, '-C', destino] };
141
165
  }
142
166
 
143
- // instalarDescarga deja el binario en bin/ solo si responde; si algo falla, lanza el error y el binario anterior sigue ahí.
144
- function instalarDescarga({ os, paquete, carpeta, binaryName, binDir, env }) {
167
+ // En Windows, npm no puede reemplazar un qdd.exe en uso, y siempre hay uno: el panel y los pedidos corren desde él (issue
168
+ // #107, FND-142). Por eso ahí el binario vive fuera del paquete, en %LOCALAPPDATA%\qdd\bin\<versión>\qdd.exe: lo que
169
+ // 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
170
+ // mira para saber que tiene que cerrarse. Linux y macOS pueden reemplazar un binario en uso: siguen con él en el paquete.
171
+ function carpetaDeVersiones({ os, env }) {
172
+ if (os !== 'windows' || !env.LOCALAPPDATA) {
173
+ return '';
174
+ }
175
+ return path.join(env.LOCALAPPDATA, 'qdd', 'bin');
176
+ }
177
+
178
+ function destinoDelBinario({ carpeta, version, binDir, binaryName }) {
179
+ if (!carpeta) {
180
+ return path.join(binDir, binaryName);
181
+ }
182
+ return path.join(carpeta, version, binaryName);
183
+ }
184
+
185
+ function respondeBinario(ruta) {
186
+ try {
187
+ execFileSync(ruta, ['--version'], { stdio: 'pipe', timeout: TOPE_VERSION_MS });
188
+ return true;
189
+ } catch (_) {
190
+ return false;
191
+ }
192
+ }
193
+
194
+ // moverBinario copia si el destino está en otro volumen: el prefijo de npm y %LOCALAPPDATA% pueden estarlo.
195
+ function moverBinario(origen, destino, renombrar = fs.renameSync) {
196
+ try {
197
+ renombrar(origen, destino);
198
+ return;
199
+ } catch (err) {
200
+ if (err.code !== 'EXDEV') {
201
+ throw err;
202
+ }
203
+ }
204
+ copiarBinario(origen, destino);
205
+ fs.rmSync(origen, { force: true });
206
+ }
207
+
208
+ // copiarBinario nunca deja un binario a medias con el nombre final.
209
+ function copiarBinario(origen, destino) {
210
+ const temporal = destino + '.nuevo';
211
+ fs.copyFileSync(origen, temporal);
212
+ fs.renameSync(temporal, destino);
213
+ }
214
+
215
+ function instalarCopia(origen, destino) {
216
+ execFileSync(origen, ['--version'], { stdio: 'pipe', timeout: TOPE_VERSION_MS });
217
+ fs.mkdirSync(path.dirname(destino), { recursive: true });
218
+ copiarBinario(origen, destino);
219
+ return destino;
220
+ }
221
+
222
+ // registrarVersion avisa a lo que corre que hay otra versión y borra las viejas: quedan esta y la anterior. Lo que no se
223
+ // puede borrar sigue corriendo: se borrará en otra actualización.
224
+ function registrarVersion({ carpeta, version, borrar = fs.rmSync }) {
225
+ const puntero = path.join(carpeta, 'actual.txt');
226
+ const anterior = esArchivo(puntero) ? fs.readFileSync(puntero, 'utf8').trim() : '';
227
+ if (anterior !== version) {
228
+ fs.writeFileSync(puntero, version);
229
+ }
230
+ for (const nombre of fs.readdirSync(carpeta)) {
231
+ if (!/^\d+\.\d+\.\d+/.test(nombre) || nombre === version || nombre === anterior) {
232
+ continue;
233
+ }
234
+ try {
235
+ borrar(path.join(carpeta, nombre), { recursive: true, force: true });
236
+ } catch (_) {
237
+ console.log(`[QDD] La versión ${nombre} sigue en uso: se borrará más adelante.`);
238
+ }
239
+ }
240
+ }
241
+
242
+ // terminarInstalacion: primero el panel, y al final el puntero, porque lo que corre se cierra apenas cambia.
243
+ function terminarInstalacion({ bin, carpeta }) {
244
+ actualizarConsola(bin);
245
+ if (!carpeta) {
246
+ return;
247
+ }
248
+ try {
249
+ registrarVersion({ carpeta, version: pkg.version });
250
+ } catch (err) {
251
+ console.error('[QDD] No se pudo registrar la versión instalada:', err.message);
252
+ }
253
+ }
254
+
255
+ // instalarDescarga deja el binario en su destino solo si responde; si algo falla, lanza el error y el binario anterior sigue ahí.
256
+ function instalarDescarga({ os, paquete, carpeta, binaryName, binDir, env, destino = path.join(binDir, binaryName) }) {
145
257
  const { programa, args } = comandoExtraer(os, paquete, carpeta, env);
146
258
  execFileSync(programa, args, { stdio: 'pipe' });
147
259
  const extraido = path.join(carpeta, binaryName);
@@ -150,8 +262,8 @@ function instalarDescarga({ os, paquete, carpeta, binaryName, binDir, env }) {
150
262
  }
151
263
  fs.chmodSync(extraido, 0o755);
152
264
  execFileSync(extraido, ['--version'], { stdio: 'pipe', timeout: TOPE_VERSION_MS });
153
- const destino = path.join(binDir, binaryName);
154
- fs.renameSync(extraido, destino);
265
+ fs.mkdirSync(path.dirname(destino), { recursive: true });
266
+ moverBinario(extraido, destino);
155
267
  fs.rmSync(paquete, { force: true });
156
268
  return destino;
157
269
  }
@@ -178,7 +290,7 @@ function actualizarConsola(bin) {
178
290
  }
179
291
  }
180
292
 
181
- module.exports = { esArchivo, estaDentroDeNodeModules, resolverBinarioLocal, comandoExtraer, instalarDescarga, codigoDeFalla };
293
+ module.exports = { esArchivo, estaDentroDeNodeModules, resolverBinarioLocal, comandoExtraer, instalarDescarga, codigoDeFalla, carpetaDeVersiones, destinoDelBinario, respondeBinario, moverBinario, instalarCopia, registrarVersion };
182
294
 
183
295
  if (require.main === module) {
184
296
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdd-framework",
3
- "version": "2.10.0-rc.1",
3
+ "version": "2.10.0-rc.3",
4
4
  "description": "Quality-Driven Development Framework",
5
5
  "bin": {
6
6
  "qdd": "bin/qdd.js"