create-canaima-app 1.0.3 → 1.0.5

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 (2) hide show
  1. package/index.js +96 -96
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -6,14 +6,16 @@ import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { exec, spawn } from 'child_process';
8
8
  import { promisify } from 'util';
9
+ import readline from 'readline';
9
10
 
10
11
  const execPromise = promisify(exec);
11
12
 
12
13
  // Resolver __dirname en módulos ES
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
16
+ const { resolve } = path;
15
17
 
16
- // Colores ANSI sin dependencias extra
18
+ // Colores ANSI
17
19
  const c = {
18
20
  reset : (t) => `\x1b[0m${t}\x1b[0m`,
19
21
  bold : (t) => `\x1b[1m${t}\x1b[0m`,
@@ -26,7 +28,7 @@ const c = {
26
28
  bgGreen: (t) => `\x1b[42m\x1b[30m${t}\x1b[0m`,
27
29
  };
28
30
 
29
- // Tus carpetas locales exactas
31
+ // Carpetas locales de plantillas
30
32
  const TEMPLATES = {
31
33
  'tauri-materialize': {
32
34
  label : 'Materialize CSS (Clásico / Estable)',
@@ -47,52 +49,63 @@ class CoatiAnimator {
47
49
  this.interval = null;
48
50
  this.frame = 0;
49
51
  this.msgIndex = 0;
50
- // Ocultar el cursor para que se vea limpio
51
- process.stdout.write('\x1b[?25l');
52
+ this.isAnimating = false;
53
+ process.stdout.write('\x1b[?25l'); // Ocultar cursor
54
+ }
55
+
56
+ clear() {
57
+ if (this.isAnimating) {
58
+ // Como ya NO imprimimos un salto de línea al final, el cursor
59
+ // está en la última línea dibujada. Solo necesitamos subir 2 líneas.
60
+ readline.moveCursor(process.stdout, 0, -2);
61
+ readline.cursorTo(process.stdout, 0);
62
+ readline.clearScreenDown(process.stdout);
63
+ }
52
64
  }
53
65
 
54
66
  start() {
55
- this.render(); // Render inicial
67
+ this.render();
68
+ this.isAnimating = true;
56
69
  this.interval = setInterval(() => {
57
70
  this.frame++;
58
- if (this.frame % 8 === 0) {
59
- // Cambiar mensaje cada ciertos frames
71
+ if (this.frame % 15 === 0) {
60
72
  this.msgIndex = (this.msgIndex + 1) % this.messages.length;
61
73
  }
62
74
  this.render();
63
- }, 400); // Velocidad del parpadeo
75
+ }, 250);
64
76
  }
65
77
 
66
- render() {
67
- // Subir 3 líneas y limpiar para redibujar
68
- if (this.frame > 0) {
69
- process.stdout.write('\x1b[3A\x1b[0J');
70
- }
78
+ render(isFinal = false, finalMessage = null) {
79
+ this.clear();
71
80
 
72
- const isBlinking = this.frame % 2 !== 0; // Parpadea intercalado
73
- const face = isBlinking ? '( -_- )' : '( ._. )';
74
- const msg = this.messages[this.msgIndex];
81
+ const isBlinking = !isFinal && this.frame % 12 === 0;
82
+ const face = isFinal ? '( ^_^ )' : (isBlinking ? '( -_- )' : '( ._. )');
75
83
 
76
- // Borde de la caja dinámico
77
- const line = '─'.repeat(msg.length + 2);
78
-
79
- console.log(c.cyan(` ^---^ `) + c.dim(`╭${line}╮`));
80
- console.log(c.cyan(` ${face} `) + c.dim(`│ `) + c.bold(c.green(msg)) + c.dim(` │`));
81
- console.log(` ` + c.dim(`╰${line}╯`));
84
+ const baseMsg = finalMessage || this.messages[this.msgIndex];
85
+ const dots = isFinal ? " " : ".".repeat(this.frame % 4).padEnd(3, " ");
86
+ const displayMsg = baseMsg + dots;
87
+
88
+ const line = '─'.repeat(displayMsg.length + 2);
89
+
90
+ const out = [
91
+ c.cyan(` ^---^ `) + c.dim(`╭${line}╮`),
92
+ c.cyan(` ${face} `) + c.dim(`│ `) + (isFinal ? c.bold(c.green(displayMsg)) : c.bold(c.white(displayMsg))) + c.dim(` │`),
93
+ ` ` + c.dim(`╰${line}╯`)
94
+ ];
95
+
96
+ // ATENCIÓN: Imprimimos con .join('\n') pero SIN añadir un '\n' al final.
97
+ // Esto evita que la terminal haga scroll automático.
98
+ process.stdout.write(out.join('\n'));
99
+ this.isAnimating = true;
82
100
  }
83
101
 
84
102
  stop(finalMessage = null) {
85
103
  if (this.interval) clearInterval(this.interval);
86
- // Mostrar cursor de nuevo
87
- process.stdout.write('\x1b[?25h');
104
+ process.stdout.write('\x1b[?25h'); // Mostrar cursor nuevamente
88
105
 
89
- if (finalMessage) {
90
- process.stdout.write('\x1b[3A\x1b[0J');
91
- const line = '─'.repeat(finalMessage.length + 2);
92
- console.log(c.cyan(` ^---^ `) + c.dim(`╭${line}╮`));
93
- console.log(c.cyan(` ( ^_^ ) `) + c.dim(`│ `) + c.bold(c.white(finalMessage)) + c.dim(` │`));
94
- console.log(` ` + c.dim(`╰${line}╯\n`));
95
- }
106
+ this.render(true, finalMessage);
107
+ console.log('\n'); // Damos el salto de línea al final, cuando ya terminó
108
+ this.isAnimating = false;
96
109
  }
97
110
  }
98
111
 
@@ -104,30 +117,26 @@ async function checkAndInstallDependencies() {
104
117
  let missingApt = false;
105
118
  let missingRust = false;
106
119
 
107
- // Verificar Rust
108
120
  try {
109
121
  await execPromise('command -v cargo');
110
122
  } catch {
111
123
  missingRust = true;
112
124
  }
113
125
 
114
- // Verificar librerías solo si es Debian/Canaima
115
126
  if (isDebian) {
116
127
  try {
117
- // Usamos pkg-config o dpkg para ver si está la librería principal
118
128
  await execPromise('dpkg -l libwebkit2gtk-4.1-dev');
119
129
  } catch {
120
130
  missingApt = true;
121
131
  }
122
132
  }
123
133
 
124
- if (!missingApt && !missingRust) return; // Todo listo
134
+ if (!missingApt && !missingRust) return;
125
135
 
126
136
  console.log(c.yellow('\n ⚠ Faltan dependencias necesarias para compilar Tauri.'));
127
137
 
128
138
  if (missingApt) {
129
139
  console.log(c.dim(' Se solicitará tu contraseña para instalar paquetes del sistema...'));
130
- // Pedimos sudo por adelantado para que no rompa nuestra interfaz ASCII
131
140
  try {
132
141
  await execPromise('sudo -v', { stdio: 'inherit' });
133
142
  } catch (err) {
@@ -137,11 +146,11 @@ async function checkAndInstallDependencies() {
137
146
  }
138
147
 
139
148
  const anim = new CoatiAnimator([
140
- "Revisando el sistema...",
141
- "Canaima GNU/Linux: puro talento local.",
142
- "Instalando magia negra (y librerías)...",
143
- "Estabilidad y rendimiento basados en Debian.",
144
- "Descargando herramientas de Rust...",
149
+ "Revisando el sistema",
150
+ "Canaima GNU/Linux: puro talento local",
151
+ "Instalando magia negra (y librerías)",
152
+ "Estabilidad y rendimiento basados en Debian",
153
+ "Descargando herramientas de Rust",
145
154
  "¡Ya casi termino!"
146
155
  ]);
147
156
 
@@ -155,7 +164,6 @@ async function checkAndInstallDependencies() {
155
164
 
156
165
  if (missingRust) {
157
166
  await execPromise(`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`);
158
- // Aplicar el env al proceso actual de Node por si acaso
159
167
  process.env.PATH = `${process.env.HOME}/.cargo/bin:${process.env.PATH}`;
160
168
  }
161
169
 
@@ -201,64 +209,62 @@ async function askQuestions() {
201
209
  });
202
210
  }
203
211
 
204
- // ... [copyTemplate y personalizeProject se mantienen iguales] ...
205
212
  async function copyTemplate(folderName, targetDir) {
206
- const templateDir = path.join(__dirname, folderName);
207
- await fs.cp(templateDir, targetDir, {
208
- recursive: true,
209
- filter: (source) => {
210
- const basename = path.basename(source);
211
- return !['node_modules', 'target', '.git', 'package-lock.json', 'dist'].includes(basename);
212
- }
213
- });
214
-
215
- const npmignorePath = path.join(targetDir, '.npmignore');
216
- if (true) {
217
- try { await fs.rename(npmignorePath, path.join(targetDir, '.gitignore')); } catch (e) {}
213
+ const templateDir = path.join(__dirname, folderName);
214
+ await fs.cp(templateDir, targetDir, {
215
+ recursive: true,
216
+ filter: (source) => {
217
+ const basename = path.basename(source);
218
+ return !['node_modules', 'target', '.git', 'package-lock.json', 'dist'].includes(basename);
218
219
  }
220
+ });
221
+
222
+ const npmignorePath = path.join(targetDir, '.npmignore');
223
+ try {
224
+ await fs.rename(npmignorePath, path.join(targetDir, '.gitignore'));
225
+ } catch (e) {}
219
226
  }
220
227
 
221
228
  async function personalizeProject(targetDir, projectName) {
222
- const pkgPath = path.join(targetDir, 'package.json');
223
- try {
224
- const pkgRaw = await fs.readFile(pkgPath, 'utf-8');
225
- const pkgJson = JSON.parse(pkgRaw);
226
- pkgJson.name = projectName;
227
- await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
228
- } catch (err) {}
229
-
230
- const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
231
- try {
232
- let cargo = await fs.readFile(cargoPath, 'utf-8');
233
- cargo = cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`);
234
- await fs.writeFile(cargoPath, cargo);
235
- } catch (err) {}
236
-
237
- const tauriConfPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
238
- try {
239
- const confRaw = await fs.readFile(tauriConfPath, 'utf-8');
240
- const confJson = JSON.parse(confRaw);
241
-
242
- if (confJson.productName !== undefined) confJson.productName = projectName;
243
- if (confJson.identifier !== undefined) confJson.identifier = `com.canaima.${projectName}`;
244
- if (confJson.bundle?.identifier) confJson.bundle.identifier = `com.canaima.${projectName}`;
245
-
246
- await writeFile(tauriConfPath, JSON.stringify(confJson, null, 2) + '\n');
247
- } catch (err) {}
229
+ const pkgPath = path.join(targetDir, 'package.json');
230
+ try {
231
+ const pkgRaw = await fs.readFile(pkgPath, 'utf-8');
232
+ const pkgJson = JSON.parse(pkgRaw);
233
+ pkgJson.name = projectName;
234
+ await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
235
+ } catch (err) {}
236
+
237
+ const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
238
+ try {
239
+ let cargo = await fs.readFile(cargoPath, 'utf-8');
240
+ cargo = cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`);
241
+ await fs.writeFile(cargoPath, cargo);
242
+ } catch (err) {}
243
+
244
+ const tauriConfPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
245
+ try {
246
+ const confRaw = await fs.readFile(tauriConfPath, 'utf-8');
247
+ const confJson = JSON.parse(confRaw);
248
+
249
+ if (confJson.productName !== undefined) confJson.productName = projectName;
250
+ if (confJson.identifier !== undefined) confJson.identifier = `com.canaima.${projectName}`;
251
+ if (confJson.bundle?.identifier) confJson.bundle.identifier = `com.canaima.${projectName}`;
252
+
253
+ await fs.writeFile(tauriConfPath, JSON.stringify(confJson, null, 2) + '\n');
254
+ } catch (err) {}
248
255
  }
249
256
 
250
257
  async function runNpmInstall(targetDir) {
251
258
  const anim = new CoatiAnimator([
252
- "Configurando los nodos...",
253
- "Trayendo paquetes del ciberespacio...",
259
+ "Configurando los nodos",
260
+ "Trayendo paquetes del ciberespacio",
254
261
  "¡Preparando el entorno Vue!",
255
- "Haciendo que todo encaje perfecto..."
262
+ "Haciendo que todo encaje perfecto"
256
263
  ]);
257
264
 
258
265
  anim.start();
259
266
 
260
267
  return new Promise((resolve) => {
261
- // Usamos spawn en background para que no ensucie la TUI
262
268
  const child = spawn('npm', ['install'], {
263
269
  cwd: targetDir,
264
270
  shell: true,
@@ -278,13 +284,12 @@ async function runNpmInstall(targetDir) {
278
284
  }
279
285
 
280
286
  async function main() {
281
- console.log(); // Espacio inicial para limpieza
287
+ console.log(c.bold(c.white('\n 🚀 Generador de proyectos CanaimaApp (Tauri + Vue + Rust)\n')));
282
288
 
283
- // 1. Revisar e instalar dependencias del sistema primero
284
289
  await checkAndInstallDependencies();
285
290
 
286
- // 2. Preguntas al usuario
287
- const { projectName, templateType, installNpm } = await askQuestions();
291
+ const answers = await askQuestions();
292
+ const { projectName, templateType, installNpm } = answers;
288
293
  if (!projectName || !templateType) process.exit(1);
289
294
 
290
295
  const targetDir = resolve(process.cwd(), projectName);
@@ -300,15 +305,13 @@ async function main() {
300
305
  console.log(c.cyan(`\n 📦 Generando archivos del proyecto...`));
301
306
  await copyTemplate(template.folder, targetDir);
302
307
  await personalizeProject(targetDir, projectName);
303
- console.log(c.green(' ✔ Archivos base copiados y configurados.'));
308
+ console.log(c.green(' ✔ Archivos base copiados y configurados.\n'));
304
309
 
305
- // 3. Instalación de NPM si el usuario aceptó
306
310
  if (installNpm) {
307
- console.log();
308
311
  await runNpmInstall(targetDir);
309
312
  }
310
313
 
311
- console.log('\n' + c.bgGreen(c.white(' ✅ ¡Proyecto creado con éxito! ')));
314
+ console.log(c.bgGreen(c.white(' ✅ ¡Proyecto creado con éxito! ')));
312
315
  console.log(c.bold('\n Siguientes pasos:\n'));
313
316
  console.log(c.white(` ${c.cyan('$')} cd ${projectName}`));
314
317
  if (!installNpm) console.log(c.white(` ${c.cyan('$')} npm install`));
@@ -321,7 +324,4 @@ async function main() {
321
324
  }
322
325
  }
323
326
 
324
- // Helper para path.resolve
325
- const { resolve } = path;
326
-
327
327
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-canaima-app",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "CLI para scaffolding de proyectos de escritorio con Tauri, Vue 3 y Rust.",
5
5
  "main": "index.js",
6
6
  "type": "module",