@saulwade/swl-ses 2.4.1 → 2.4.2

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.
@@ -1,187 +1,189 @@
1
- 'use strict';
2
-
3
- /**
4
- * Pantalla Welcome de la TUI de swl-ses.
5
- *
6
- * Diseño:
7
- * - Logo ASCII centrado en la parte superior
8
- * - Tabla de runtimes detectados (nombre, scope, versión instalada, estado)
9
- * - Pie con atajos: Enter continuar / Esc salir
10
- *
11
- * No-TTY: imprime versión texto plano y retorna { continuar: true } inmediatamente.
12
- */
13
-
14
- const path = require('path');
15
-
16
- const render = require('../lib/render');
17
- const teclas = require('../lib/teclas');
18
- const { colores, semantico, iconos } = require('../lib/colores');
19
-
20
- // Logo en caracteres ASCII art compactos (5 líneas)
21
- const LOGO = [
22
- ' ███████ ██ ██ ██ ███████ ███████ ███████ ',
23
- ' ██ ██ ██ ██ ██ ██ ██ ',
24
- ' ███████ ██ █ ██ ██ ███████ █████ ███████ ',
25
- ' ██ ██ ███ ██ ██ ██ ██ ██ ',
26
- ' ███████ ███ ███ ███████ ███████ ███████ ███████ ',
27
- ];
28
-
29
- const SUBTITULO = 'Sistema de ingeniería de software auto-evolutivo multi-runtime';
30
-
31
- /**
32
- * Detecta runtimes instalados leyendo desde scripts/lib/detectar-runtime.
33
- * No falla si la lib no está disponible — devuelve array vacío.
34
- */
35
- function detectarInstalaciones() {
36
- let detectarRuntimes;
37
- let cargarEstado;
38
- try {
39
- ({ detectarRuntimes } = require('../../lib/detectar-runtime'));
40
- ({ cargarEstado } = require('../../lib/estado'));
41
- } catch (_) {
42
- return [];
43
- }
44
-
45
- const runtimes = detectarRuntimes();
46
- const filas = [];
47
- for (const runtime of runtimes) {
48
- const dirs = [
49
- { ruta: runtime.global, scope: 'global' },
50
- { ruta: path.resolve(runtime.local), scope: 'proyecto' },
51
- ];
52
- for (const { ruta, scope } of dirs) {
53
- try {
54
- const estado = cargarEstado(ruta);
55
- if (!estado) continue;
56
- filas.push({
57
- runtime: runtime.nombre,
58
- scope,
59
- version: estado.versionSistema || 'desconocida',
60
- perfil: estado.perfil || '-',
61
- componentes: estado.componentesInstalados?.length || 0,
62
- });
63
- } catch (_) {
64
- // ignorar errores de lectura
65
- }
66
- }
67
- }
68
- return filas;
69
- }
70
-
71
- /**
72
- * Pinta el contenido completo de la pantalla. Idempotente — se puede invocar
73
- * múltiples veces (resize, re-render).
74
- */
75
- function _renderizar(filas, version) {
76
- render.limpiarPantalla();
77
- const { cols, rows } = render.obtenerDimensiones();
78
-
79
- // Logo centrado
80
- const filaLogo = 2;
81
- for (let i = 0; i < LOGO.length; i++) {
82
- const linea = LOGO[i];
83
- const colLogo = Math.max(1, Math.floor((cols - linea.length) / 2));
84
- render.escribirEn(filaLogo + i, colLogo, semantico.titulo(linea));
85
- }
86
-
87
- // Subtítulo + versión
88
- const filaSub = filaLogo + LOGO.length + 1;
89
- const subFinal = `${SUBTITULO} ${colores.dim('v' + version)}`;
90
- const colSub = Math.max(1, Math.floor((cols - render.anchoVisual(subFinal)) / 2));
91
- render.escribirEn(filaSub, colSub, subFinal);
92
-
93
- // Tabla de runtimes detectados
94
- const filaTabla = filaSub + 3;
95
- if (filas.length === 0) {
96
- const mensaje = colores.dim('No se detectaron instalaciones SWL en este equipo.');
97
- const colMsg = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
98
- render.escribirEn(filaTabla, colMsg, mensaje);
99
- } else {
100
- render.escribirEn(filaTabla - 1, 4, semantico.enfasis('Instalaciones detectadas:'));
101
- const cabecera =
102
- render.rellenarDer(colores.dim('Runtime'), 14) +
103
- render.rellenarDer(colores.dim('Scope'), 10) +
104
- render.rellenarDer(colores.dim('Versión'), 12) +
105
- render.rellenarDer(colores.dim('Perfil'), 18) +
106
- colores.dim('Componentes');
107
- render.escribirEn(filaTabla, 4, cabecera);
108
-
109
- filas.slice(0, rows - filaTabla - 4).forEach((f, i) => {
110
- const estado = f.version === version
111
- ? semantico.exito(iconos.check)
112
- : semantico.warn(iconos.warn);
113
- const linea =
114
- render.rellenarDer(`${estado} ${f.runtime}`, 14) +
115
- render.rellenarDer(f.scope, 10) +
116
- render.rellenarDer('v' + f.version, 12) +
117
- render.rellenarDer(f.perfil, 18) +
118
- colores.cyan(String(f.componentes));
119
- render.escribirEn(filaTabla + 1 + i, 4, linea);
120
- });
121
- }
122
-
123
- // Mensaje central de continuación
124
- const filaCont = rows - 4;
125
- const mensaje = `Presiona ${semantico.cursor('Enter')} para continuar al menú principal o ${semantico.cursor('Esc')} para salir.`;
126
- const colCont = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
127
- render.escribirEn(filaCont, colCont, mensaje);
128
-
129
- // Pie con atajos
130
- render.dibujarPiePagina([
131
- ['Enter', 'continuar'],
132
- ['Esc', 'salir'],
133
- ]);
134
- }
135
-
136
- /**
137
- * Muestra la pantalla Welcome y espera input del usuario.
138
- *
139
- * @returns {Promise<{ continuar: boolean, instalaciones: Array }>}
140
- */
141
- function mostrarWelcome() {
142
- const version = require('../../../package.json').version;
143
- const filas = detectarInstalaciones();
144
-
145
- return new Promise((resolve) => {
146
- // Modo no-TTY: salida texto plano sin esperar input
147
- if (!render.ES_TTY || !process.stdin.isTTY) {
148
- console.log('');
149
- console.log(` swl-ses v${version}`);
150
- console.log(` ${SUBTITULO}`);
151
- console.log('');
152
- if (filas.length > 0) {
153
- console.log(' Instalaciones detectadas:');
154
- for (const f of filas) {
155
- console.log(` ${f.runtime} (${f.scope}): v${f.version}, perfil ${f.perfil}`);
156
- }
157
- } else {
158
- console.log(' No se detectaron instalaciones SWL.');
159
- }
160
- console.log('');
161
- resolve({ continuar: true, instalaciones: filas });
162
- return;
163
- }
164
-
165
- render.iniciarModoTui();
166
- _renderizar(filas, version);
167
-
168
- // Re-renderizar en resize
169
- const onResize = () => _renderizar(filas, version);
170
- process.stdout.on('resize', onResize);
171
-
172
- const teclado = teclas.crearTeclado();
173
-
174
- function finalizar(continuar) {
175
- teclado.desactivar();
176
- process.stdout.removeListener('resize', onResize);
177
- render.salirModoTui();
178
- resolve({ continuar, instalaciones: filas });
179
- }
180
-
181
- teclado.on('return', () => finalizar(true));
182
- teclado.on('escape', () => finalizar(false));
183
- teclado.activar();
184
- });
185
- }
186
-
187
- module.exports = { mostrarWelcome, detectarInstalaciones, LOGO, SUBTITULO };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pantalla Welcome de la TUI de swl-ses.
5
+ *
6
+ * Diseño:
7
+ * - Logo ASCII centrado en la parte superior
8
+ * - Tabla de runtimes detectados (nombre, scope, versión instalada, estado)
9
+ * - Pie con atajos: Enter continuar / Esc salir
10
+ *
11
+ * No-TTY: imprime versión texto plano y retorna { continuar: true } inmediatamente.
12
+ */
13
+
14
+ const path = require('path');
15
+
16
+ const render = require('../lib/render');
17
+ const teclas = require('../lib/teclas');
18
+ const { colores, semantico, iconos } = require('../lib/colores');
19
+
20
+ // Logo en caracteres ASCII art compactos (5 líneas)
21
+ const LOGO = [
22
+ ' ███████ ██ ██ ██ ███████ ███████ ███████ ',
23
+ ' ██ ██ ██ ██ ██ ██ ██ ',
24
+ ' ███████ ██ █ ██ ██ ███████ █████ ███████ ',
25
+ ' ██ ██ ███ ██ ██ ██ ██ ██ ',
26
+ ' ███████ ███ ███ ███████ ███████ ███████ ███████ ',
27
+ ];
28
+
29
+ const SUBTITULO = 'Sistema de ingeniería de software auto-evolutivo multi-runtime';
30
+
31
+ /**
32
+ * Detecta runtimes instalados leyendo desde scripts/lib/detectar-runtime.
33
+ * No falla si la lib no está disponible — devuelve array vacío.
34
+ */
35
+ function detectarInstalaciones() {
36
+ let detectarRuntimes;
37
+ let cargarEstado;
38
+ try {
39
+ ({ detectarRuntimes, scopesReales } = require('../../lib/detectar-runtime'));
40
+ ({ cargarEstado } = require('../../lib/estado'));
41
+ } catch (_) {
42
+ return [];
43
+ }
44
+
45
+ const runtimes = detectarRuntimes();
46
+ const filas = [];
47
+ for (const runtime of runtimes) {
48
+ // Dedup + guard anti-fantasma: desde HOME, el local relativo resuelve al
49
+ // global propio (duplicado) o al de OTRO runtime (p.ej. .claude de
50
+ // OpenClaude = global de Claude Code) ver scopesReales().
51
+ const dirs = scopesReales(runtime).map(({ dir, esGlobal }) => (
52
+ { ruta: dir, scope: esGlobal ? 'global' : 'proyecto' }
53
+ ));
54
+ for (const { ruta, scope } of dirs) {
55
+ try {
56
+ const estado = cargarEstado(ruta);
57
+ if (!estado) continue;
58
+ filas.push({
59
+ runtime: runtime.nombre,
60
+ scope,
61
+ version: estado.versionSistema || 'desconocida',
62
+ perfil: estado.perfil || '-',
63
+ componentes: estado.componentesInstalados?.length || 0,
64
+ });
65
+ } catch (_) {
66
+ // ignorar errores de lectura
67
+ }
68
+ }
69
+ }
70
+ return filas;
71
+ }
72
+
73
+ /**
74
+ * Pinta el contenido completo de la pantalla. Idempotente — se puede invocar
75
+ * múltiples veces (resize, re-render).
76
+ */
77
+ function _renderizar(filas, version) {
78
+ render.limpiarPantalla();
79
+ const { cols, rows } = render.obtenerDimensiones();
80
+
81
+ // Logo centrado
82
+ const filaLogo = 2;
83
+ for (let i = 0; i < LOGO.length; i++) {
84
+ const linea = LOGO[i];
85
+ const colLogo = Math.max(1, Math.floor((cols - linea.length) / 2));
86
+ render.escribirEn(filaLogo + i, colLogo, semantico.titulo(linea));
87
+ }
88
+
89
+ // Subtítulo + versión
90
+ const filaSub = filaLogo + LOGO.length + 1;
91
+ const subFinal = `${SUBTITULO} ${colores.dim('v' + version)}`;
92
+ const colSub = Math.max(1, Math.floor((cols - render.anchoVisual(subFinal)) / 2));
93
+ render.escribirEn(filaSub, colSub, subFinal);
94
+
95
+ // Tabla de runtimes detectados
96
+ const filaTabla = filaSub + 3;
97
+ if (filas.length === 0) {
98
+ const mensaje = colores.dim('No se detectaron instalaciones SWL en este equipo.');
99
+ const colMsg = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
100
+ render.escribirEn(filaTabla, colMsg, mensaje);
101
+ } else {
102
+ render.escribirEn(filaTabla - 1, 4, semantico.enfasis('Instalaciones detectadas:'));
103
+ const cabecera =
104
+ render.rellenarDer(colores.dim('Runtime'), 14) +
105
+ render.rellenarDer(colores.dim('Scope'), 10) +
106
+ render.rellenarDer(colores.dim('Versión'), 12) +
107
+ render.rellenarDer(colores.dim('Perfil'), 18) +
108
+ colores.dim('Componentes');
109
+ render.escribirEn(filaTabla, 4, cabecera);
110
+
111
+ filas.slice(0, rows - filaTabla - 4).forEach((f, i) => {
112
+ const estado = f.version === version
113
+ ? semantico.exito(iconos.check)
114
+ : semantico.warn(iconos.warn);
115
+ const linea =
116
+ render.rellenarDer(`${estado} ${f.runtime}`, 14) +
117
+ render.rellenarDer(f.scope, 10) +
118
+ render.rellenarDer('v' + f.version, 12) +
119
+ render.rellenarDer(f.perfil, 18) +
120
+ colores.cyan(String(f.componentes));
121
+ render.escribirEn(filaTabla + 1 + i, 4, linea);
122
+ });
123
+ }
124
+
125
+ // Mensaje central de continuación
126
+ const filaCont = rows - 4;
127
+ const mensaje = `Presiona ${semantico.cursor('Enter')} para continuar al menú principal o ${semantico.cursor('Esc')} para salir.`;
128
+ const colCont = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
129
+ render.escribirEn(filaCont, colCont, mensaje);
130
+
131
+ // Pie con atajos
132
+ render.dibujarPiePagina([
133
+ ['Enter', 'continuar'],
134
+ ['Esc', 'salir'],
135
+ ]);
136
+ }
137
+
138
+ /**
139
+ * Muestra la pantalla Welcome y espera input del usuario.
140
+ *
141
+ * @returns {Promise<{ continuar: boolean, instalaciones: Array }>}
142
+ */
143
+ function mostrarWelcome() {
144
+ const version = require('../../../package.json').version;
145
+ const filas = detectarInstalaciones();
146
+
147
+ return new Promise((resolve) => {
148
+ // Modo no-TTY: salida texto plano sin esperar input
149
+ if (!render.ES_TTY || !process.stdin.isTTY) {
150
+ console.log('');
151
+ console.log(` swl-ses v${version}`);
152
+ console.log(` ${SUBTITULO}`);
153
+ console.log('');
154
+ if (filas.length > 0) {
155
+ console.log(' Instalaciones detectadas:');
156
+ for (const f of filas) {
157
+ console.log(` ${f.runtime} (${f.scope}): v${f.version}, perfil ${f.perfil}`);
158
+ }
159
+ } else {
160
+ console.log(' No se detectaron instalaciones SWL.');
161
+ }
162
+ console.log('');
163
+ resolve({ continuar: true, instalaciones: filas });
164
+ return;
165
+ }
166
+
167
+ render.iniciarModoTui();
168
+ _renderizar(filas, version);
169
+
170
+ // Re-renderizar en resize
171
+ const onResize = () => _renderizar(filas, version);
172
+ process.stdout.on('resize', onResize);
173
+
174
+ const teclado = teclas.crearTeclado();
175
+
176
+ function finalizar(continuar) {
177
+ teclado.desactivar();
178
+ process.stdout.removeListener('resize', onResize);
179
+ render.salirModoTui();
180
+ resolve({ continuar, instalaciones: filas });
181
+ }
182
+
183
+ teclado.on('return', () => finalizar(true));
184
+ teclado.on('escape', () => finalizar(false));
185
+ teclado.activar();
186
+ });
187
+ }
188
+
189
+ module.exports = { mostrarWelcome, detectarInstalaciones, LOGO, SUBTITULO };