create-canaima-app 1.0.2 → 1.0.4

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 +195 -41
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,15 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import prompts from 'prompts';
4
- import { promises as fs } from 'fs';
4
+ import { promises as fs, existsSync } from 'fs';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
+ import { exec, spawn } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import readline from 'readline';
10
+
11
+ const execPromise = promisify(exec);
7
12
 
8
13
  // Resolver __dirname en módulos ES
9
14
  const __filename = fileURLToPath(import.meta.url);
10
15
  const __dirname = path.dirname(__filename);
16
+ const { resolve } = path;
11
17
 
12
- // Colores ANSI sin dependencias extra
18
+ // Colores ANSI
13
19
  const c = {
14
20
  reset : (t) => `\x1b[0m${t}\x1b[0m`,
15
21
  bold : (t) => `\x1b[1m${t}\x1b[0m`,
@@ -22,31 +28,151 @@ const c = {
22
28
  bgGreen: (t) => `\x1b[42m\x1b[30m${t}\x1b[0m`,
23
29
  };
24
30
 
25
- // Tus carpetas locales exactas
31
+ // Carpetas locales de plantillas
26
32
  const TEMPLATES = {
27
33
  'tauri-materialize': {
28
34
  label : 'Materialize CSS (Clásico / Estable)',
29
35
  folder : 'tauri-materialize',
30
36
  },
31
37
  'tauri-material-tailwind': {
32
- label : 'Material Tailwind (Moderno / Utility-First)',
38
+ label : 'Material Tailwind (Moderno / Solo CSS)',
33
39
  folder : 'tauri-material-tailwind',
34
40
  },
35
41
  };
36
42
 
37
- function printBanner() {
38
- const banner = `
39
- ██████╗ █████╗ ███╗ ██╗ █████╗ ██╗███╗ ███╗ █████╗ ██████╗██╗ ██╗
40
- ██╔════╝██╔══██╗████╗ ██║██╔══██╗██║████╗ ████║██╔══██╗ ██╔════╝██║ ██║
41
- ██║ ███████║██╔██╗ ██║███████║██║██╔████╔██║███████║ ██║ ██║ ██║
42
- ██║ ██╔══██║██║╚██╗██║██╔══██║██║██║╚██╔╝██║██╔══██║ ██║ ██║ ██║
43
- ╚██████╗██║ ██║██║ ╚████║██║ ██║██║██║ ╚═╝ ██║██║ ██║ ╚██████╗███████╗██║
44
- ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝
45
- `;
46
- console.log(c.cyan(banner));
47
- console.log(c.bold(c.white(' 🚀 Generador de proyectos CanaimaApp (Tauri + Vue + Rust)\n')));
43
+ // ==========================================
44
+ // SISTEMA DE ANIMACIÓN DEL COATÍ (TUI)
45
+ // ==========================================
46
+ class CoatiAnimator {
47
+ constructor(messages) {
48
+ this.messages = messages;
49
+ this.interval = null;
50
+ this.frame = 0;
51
+ this.msgIndex = 0;
52
+ this.linesDrawn = 0;
53
+ process.stdout.write('\x1b[?25l'); // Ocultar cursor
54
+ }
55
+
56
+ clear() {
57
+ if (this.linesDrawn > 0) {
58
+ readline.moveCursor(process.stdout, 0, -this.linesDrawn);
59
+ readline.clearScreenDown(process.stdout);
60
+ }
61
+ this.linesDrawn = 0;
62
+ }
63
+
64
+ start() {
65
+ this.render();
66
+ this.interval = setInterval(() => {
67
+ this.frame++;
68
+ // Cambiar de frase cada 15 frames
69
+ if (this.frame % 15 === 0) {
70
+ this.msgIndex = (this.msgIndex + 1) % this.messages.length;
71
+ }
72
+ this.render();
73
+ }, 250); // Velocidad de la animación
74
+ }
75
+
76
+ render(isFinal = false, finalMessage = null) {
77
+ this.clear();
78
+
79
+ const isBlinking = !isFinal && this.frame % 12 === 0;
80
+ const face = isFinal ? '( ^_^ )' : (isBlinking ? '( -_- )' : '( ._. )');
81
+
82
+ const baseMsg = finalMessage || this.messages[this.msgIndex];
83
+ const dots = isFinal ? " " : ".".repeat(this.frame % 4).padEnd(3, " ");
84
+ const displayMsg = baseMsg + dots;
85
+
86
+ const line = '─'.repeat(displayMsg.length + 2);
87
+
88
+ const out = [
89
+ c.cyan(` ^---^ `) + c.dim(`╭${line}╮`),
90
+ c.cyan(` ${face} `) + c.dim(`│ `) + (isFinal ? c.bold(c.green(displayMsg)) : c.bold(c.white(displayMsg))) + c.dim(` │`),
91
+ ` ` + c.dim(`╰${line}╯`)
92
+ ];
93
+
94
+ process.stdout.write(out.join('\n') + '\n');
95
+ this.linesDrawn = 3;
96
+ }
97
+
98
+ stop(finalMessage = null) {
99
+ if (this.interval) clearInterval(this.interval);
100
+ process.stdout.write('\x1b[?25h'); // Mostrar cursor
101
+
102
+ this.render(true, finalMessage);
103
+ console.log();
104
+ }
105
+ }
106
+
107
+ // ==========================================
108
+ // VERIFICACIÓN DE DEPENDENCIAS (Debian/Rust)
109
+ // ==========================================
110
+ async function checkAndInstallDependencies() {
111
+ const isDebian = existsSync('/etc/debian_version');
112
+ let missingApt = false;
113
+ let missingRust = false;
114
+
115
+ try {
116
+ await execPromise('command -v cargo');
117
+ } catch {
118
+ missingRust = true;
119
+ }
120
+
121
+ if (isDebian) {
122
+ try {
123
+ await execPromise('dpkg -l libwebkit2gtk-4.1-dev');
124
+ } catch {
125
+ missingApt = true;
126
+ }
127
+ }
128
+
129
+ if (!missingApt && !missingRust) return;
130
+
131
+ console.log(c.yellow('\n ⚠ Faltan dependencias necesarias para compilar Tauri.'));
132
+
133
+ if (missingApt) {
134
+ console.log(c.dim(' Se solicitará tu contraseña para instalar paquetes del sistema...'));
135
+ try {
136
+ await execPromise('sudo -v', { stdio: 'inherit' });
137
+ } catch (err) {
138
+ console.log(c.red(' ✖ No se pudo obtener permisos de administrador.'));
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ const anim = new CoatiAnimator([
144
+ "Revisando el sistema",
145
+ "Canaima GNU/Linux: puro talento local",
146
+ "Instalando magia negra (y librerías)",
147
+ "Estabilidad y rendimiento basados en Debian",
148
+ "Descargando herramientas de Rust",
149
+ "¡Ya casi termino!"
150
+ ]);
151
+
152
+ anim.start();
153
+
154
+ try {
155
+ if (missingApt) {
156
+ const aptCmd = `sudo apt update && sudo DEBIAN_FRONTEND=noninteractive apt install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev`;
157
+ await execPromise(aptCmd);
158
+ }
159
+
160
+ if (missingRust) {
161
+ await execPromise(`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`);
162
+ process.env.PATH = `${process.env.HOME}/.cargo/bin:${process.env.PATH}`;
163
+ }
164
+
165
+ anim.stop("¡Dependencias instaladas con éxito!");
166
+ } catch (err) {
167
+ anim.stop("¡Ups! Algo falló.");
168
+ console.error(c.red(`\n ✖ Error instalando dependencias: ${err.message}\n`));
169
+ process.exit(1);
170
+ }
48
171
  }
49
172
 
173
+ // ==========================================
174
+ // FLUJO PRINCIPAL DE PREGUNTAS
175
+ // ==========================================
50
176
  async function askQuestions() {
51
177
  return await prompts([
52
178
  {
@@ -62,6 +188,14 @@ async function askQuestions() {
62
188
  message: '¿Qué framework de diseño prefieres?',
63
189
  choices: Object.entries(TEMPLATES).map(([value, { label }]) => ({ title: label, value })),
64
190
  },
191
+ {
192
+ type : 'toggle',
193
+ name : 'installNpm',
194
+ message: '¿Deseas instalar las dependencias de NPM ahora mismo?',
195
+ initial: true,
196
+ active : 'Sí',
197
+ inactive: 'No'
198
+ }
65
199
  ], {
66
200
  onCancel: () => {
67
201
  console.log(c.yellow('\n ⚠ Operación cancelada.\n'));
@@ -71,34 +205,22 @@ async function askQuestions() {
71
205
  }
72
206
 
73
207
  async function copyTemplate(folderName, targetDir) {
74
- console.log(c.cyan(`\n 📦 Generando archivos del proyecto...`));
75
-
76
208
  const templateDir = path.join(__dirname, folderName);
77
-
78
209
  await fs.cp(templateDir, targetDir, {
79
210
  recursive: true,
80
- // Ignoramos carpetas pesadas o de git si quedaron en tu entorno local
81
211
  filter: (source) => {
82
212
  const basename = path.basename(source);
83
213
  return !['node_modules', 'target', '.git', 'package-lock.json', 'dist'].includes(basename);
84
214
  }
85
215
  });
86
216
 
87
- // NPM renombra .gitignore a .npmignore a veces. Esto lo soluciona si pasa.
88
217
  const npmignorePath = path.join(targetDir, '.npmignore');
89
- if (true) {
90
- try {
91
- await fs.rename(npmignorePath, path.join(targetDir, '.gitignore'));
92
- } catch (e) {} // Ignorar si no existe
93
- }
94
-
95
- console.log(c.green(' ✔ Archivos base copiados.'));
218
+ try {
219
+ await fs.rename(npmignorePath, path.join(targetDir, '.gitignore'));
220
+ } catch (e) {}
96
221
  }
97
-
222
+
98
223
  async function personalizeProject(targetDir, projectName) {
99
- console.log(c.cyan(' 🔧 Configurando dependencias y Rust...'));
100
-
101
- // 1. package.json
102
224
  const pkgPath = path.join(targetDir, 'package.json');
103
225
  try {
104
226
  const pkgRaw = await fs.readFile(pkgPath, 'utf-8');
@@ -107,7 +229,6 @@ async function personalizeProject(targetDir, projectName) {
107
229
  await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
108
230
  } catch (err) {}
109
231
 
110
- // 2. Cargo.toml
111
232
  const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
112
233
  try {
113
234
  let cargo = await fs.readFile(cargoPath, 'utf-8');
@@ -115,7 +236,6 @@ async function personalizeProject(targetDir, projectName) {
115
236
  await fs.writeFile(cargoPath, cargo);
116
237
  } catch (err) {}
117
238
 
118
- // 3. tauri.conf.json
119
239
  const tauriConfPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
120
240
  try {
121
241
  const confRaw = await fs.readFile(tauriConfPath, 'utf-8');
@@ -123,23 +243,51 @@ async function personalizeProject(targetDir, projectName) {
123
243
 
124
244
  if (confJson.productName !== undefined) confJson.productName = projectName;
125
245
  if (confJson.identifier !== undefined) confJson.identifier = `com.canaima.${projectName}`;
126
-
127
- // Soporte Tauri v2
128
246
  if (confJson.bundle?.identifier) confJson.bundle.identifier = `com.canaima.${projectName}`;
129
247
 
130
248
  await fs.writeFile(tauriConfPath, JSON.stringify(confJson, null, 2) + '\n');
131
249
  } catch (err) {}
250
+ }
132
251
 
133
- console.log(c.green(' ✔ Proyecto personalizado con éxito.'));
252
+ async function runNpmInstall(targetDir) {
253
+ const anim = new CoatiAnimator([
254
+ "Configurando los nodos",
255
+ "Trayendo paquetes del ciberespacio",
256
+ "¡Preparando el entorno Vue!",
257
+ "Haciendo que todo encaje perfecto"
258
+ ]);
259
+
260
+ anim.start();
261
+
262
+ return new Promise((resolve) => {
263
+ const child = spawn('npm', ['install'], {
264
+ cwd: targetDir,
265
+ shell: true,
266
+ stdio: 'ignore'
267
+ });
268
+
269
+ child.on('close', (code) => {
270
+ if (code !== 0) {
271
+ anim.stop("Mmm, hubo un problema con NPM.");
272
+ resolve(false);
273
+ } else {
274
+ anim.stop("¡NPM Install completado! Listo para la acción.");
275
+ resolve(true);
276
+ }
277
+ });
278
+ });
134
279
  }
135
280
 
136
281
  async function main() {
137
- printBanner();
282
+ console.log(c.bold(c.white('\n 🚀 Generador de proyectos CanaimaApp (Tauri + Vue + Rust)\n')));
138
283
 
139
- const { projectName, templateType } = await askQuestions();
284
+ await checkAndInstallDependencies();
285
+
286
+ const answers = await askQuestions();
287
+ const { projectName, templateType, installNpm } = answers;
140
288
  if (!projectName || !templateType) process.exit(1);
141
289
 
142
- const targetDir = path.resolve(process.cwd(), projectName);
290
+ const targetDir = resolve(process.cwd(), projectName);
143
291
  const template = TEMPLATES[templateType];
144
292
 
145
293
  try {
@@ -149,13 +297,19 @@ async function main() {
149
297
  } catch {}
150
298
 
151
299
  try {
300
+ console.log(c.cyan(`\n 📦 Generando archivos del proyecto...`));
152
301
  await copyTemplate(template.folder, targetDir);
153
302
  await personalizeProject(targetDir, projectName);
303
+ console.log(c.green(' ✔ Archivos base copiados y configurados.\n'));
304
+
305
+ if (installNpm) {
306
+ await runNpmInstall(targetDir);
307
+ }
154
308
 
155
- console.log('\n' + c.bgGreen(c.white(' ✅ ¡Proyecto creado con éxito! ')));
309
+ console.log(c.bgGreen(c.white(' ✅ ¡Proyecto creado con éxito! ')));
156
310
  console.log(c.bold('\n Siguientes pasos:\n'));
157
311
  console.log(c.white(` ${c.cyan('$')} cd ${projectName}`));
158
- console.log(c.white(` ${c.cyan('$')} npm install`));
312
+ if (!installNpm) console.log(c.white(` ${c.cyan('$')} npm install`));
159
313
  console.log(c.white(` ${c.cyan('$')} npm run tauri dev`));
160
314
  console.log(c.dim('\n ¡A programar! 🦅\n'));
161
315
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-canaima-app",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",