create-canaima-app 1.0.5 → 1.0.6

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 +106 -155
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -10,7 +10,6 @@ import readline from 'readline';
10
10
 
11
11
  const execPromise = promisify(exec);
12
12
 
13
- // Resolver __dirname en módulos ES
14
13
  const __filename = fileURLToPath(import.meta.url);
15
14
  const __dirname = path.dirname(__filename);
16
15
  const { resolve } = path;
@@ -25,23 +24,32 @@ const c = {
25
24
  yellow : (t) => `\x1b[33m${t}\x1b[0m`,
26
25
  red : (t) => `\x1b[31m${t}\x1b[0m`,
27
26
  white : (t) => `\x1b[97m${t}\x1b[0m`,
28
- bgGreen: (t) => `\x1b[42m\x1b[30m${t}\x1b[0m`,
27
+ black : (t) => `\x1b[30m${t}\x1b[0m`,
28
+ bgGreen: (t) => `\x1b[42m${t}\x1b[0m`,
29
+ bgCyan : (t) => `\x1b[46m${t}\x1b[0m`,
30
+ bgMagenta: (t) => `\x1b[45m${t}\x1b[0m`,
31
+ bgBlue : (t) => `\x1b[44m${t}\x1b[0m`,
29
32
  };
30
33
 
31
- // Carpetas locales de plantillas
34
+ // Diseño de Etiquetas estilo Astro (todas miden exactamente 9 caracteres)
35
+ const tags = {
36
+ canaima: c.bgGreen(c.black(' canaima ')),
37
+ dir: c.bgCyan(c.black(' dir ')),
38
+ tmpl: c.bgMagenta(c.black(' tmpl ')),
39
+ deps: c.bgBlue(c.black(' deps ')),
40
+ next: c.bgGreen(c.black(' next ')),
41
+ done: c.green(' ✔ '),
42
+ step: c.dim(' ■')
43
+ };
44
+ const indent = ' '; // 9 espacios para alinear con las etiquetas
45
+
32
46
  const TEMPLATES = {
33
- 'tauri-materialize': {
34
- label : 'Materialize CSS (Clásico / Estable)',
35
- folder : 'tauri-materialize',
36
- },
37
- 'tauri-material-tailwind': {
38
- label : 'Material Tailwind (Moderno / Solo CSS)',
39
- folder : 'tauri-material-tailwind',
40
- },
47
+ 'tauri-materialize': { label: 'Materialize CSS (Clásico / Estable)', folder: 'tauri-materialize' },
48
+ 'tauri-material-tailwind': { label: 'Material Tailwind (Moderno / Solo CSS)', folder: 'tauri-material-tailwind' },
41
49
  };
42
50
 
43
51
  // ==========================================
44
- // SISTEMA DE ANIMACIÓN DEL COATÍ (TUI)
52
+ // SISTEMA DE ANIMACIÓN DEL COATÍ (Estilo Astro)
45
53
  // ==========================================
46
54
  class CoatiAnimator {
47
55
  constructor(messages) {
@@ -50,13 +58,11 @@ class CoatiAnimator {
50
58
  this.frame = 0;
51
59
  this.msgIndex = 0;
52
60
  this.isAnimating = false;
53
- process.stdout.write('\x1b[?25l'); // Ocultar cursor
61
+ process.stdout.write('\x1b[?25l');
54
62
  }
55
63
 
56
64
  clear() {
57
65
  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
66
  readline.moveCursor(process.stdout, 0, -2);
61
67
  readline.cursorTo(process.stdout, 0);
62
68
  readline.clearScreenDown(process.stdout);
@@ -68,9 +74,7 @@ class CoatiAnimator {
68
74
  this.isAnimating = true;
69
75
  this.interval = setInterval(() => {
70
76
  this.frame++;
71
- if (this.frame % 15 === 0) {
72
- this.msgIndex = (this.msgIndex + 1) % this.messages.length;
73
- }
77
+ if (this.frame % 15 === 0) this.msgIndex = (this.msgIndex + 1) % this.messages.length;
74
78
  this.render();
75
79
  }, 250);
76
80
  }
@@ -87,205 +91,142 @@ class CoatiAnimator {
87
91
 
88
92
  const line = '─'.repeat(displayMsg.length + 2);
89
93
 
94
+ // Animación indentada para que quede en línea con el estilo Astro
90
95
  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}╯`)
96
+ `${indent}${c.cyan(` ^---^ `)}${c.dim(`╭${line}╮`)}`,
97
+ `${indent}${c.cyan(`${face} `)}${c.dim(`│ `)}${isFinal ? c.bold(c.green(displayMsg)) : c.bold(c.white(displayMsg))}${c.dim(` │`)}`,
98
+ `${indent}${c.cyan(` `)}${c.dim(`╰${line}╯`)}`
94
99
  ];
95
100
 
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
101
  process.stdout.write(out.join('\n'));
99
102
  this.isAnimating = true;
100
103
  }
101
104
 
102
- stop(finalMessage = null) {
105
+ // Si clearAfter es true, el Coatí desaparece al terminar y deja el espacio limpio
106
+ stop(finalMessage = null, clearAfter = false) {
103
107
  if (this.interval) clearInterval(this.interval);
104
- process.stdout.write('\x1b[?25h'); // Mostrar cursor nuevamente
108
+ process.stdout.write('\x1b[?25h');
105
109
 
106
- this.render(true, finalMessage);
107
- console.log('\n'); // Damos el salto de línea al final, cuando ya terminó
110
+ if (clearAfter) {
111
+ this.clear();
112
+ } else {
113
+ this.render(true, finalMessage);
114
+ console.log('\n');
115
+ }
108
116
  this.isAnimating = false;
109
117
  }
110
118
  }
111
119
 
112
120
  // ==========================================
113
- // VERIFICACIÓN DE DEPENDENCIAS (Debian/Rust)
121
+ // DEPENDENCIAS DEL SISTEMA
114
122
  // ==========================================
115
123
  async function checkAndInstallDependencies() {
116
124
  const isDebian = existsSync('/etc/debian_version');
117
125
  let missingApt = false;
118
126
  let missingRust = false;
119
127
 
120
- try {
121
- await execPromise('command -v cargo');
122
- } catch {
123
- missingRust = true;
124
- }
125
-
128
+ try { await execPromise('command -v cargo'); } catch { missingRust = true; }
126
129
  if (isDebian) {
127
- try {
128
- await execPromise('dpkg -l libwebkit2gtk-4.1-dev');
129
- } catch {
130
- missingApt = true;
131
- }
130
+ try { await execPromise('dpkg -l libwebkit2gtk-4.1-dev'); } catch { missingApt = true; }
132
131
  }
133
132
 
134
133
  if (!missingApt && !missingRust) return;
135
134
 
136
- console.log(c.yellow('\n Faltan dependencias necesarias para compilar Tauri.'));
135
+ console.log(`\n${tags.deps} Pre-flight check: Faltan dependencias de sistema.`);
137
136
 
138
137
  if (missingApt) {
139
- console.log(c.dim(' Se solicitará tu contraseña para instalar paquetes del sistema...'));
140
- try {
141
- await execPromise('sudo -v', { stdio: 'inherit' });
142
- } catch (err) {
143
- console.log(c.red(' ✖ No se pudo obtener permisos de administrador.'));
144
- process.exit(1);
145
- }
138
+ console.log(`${indent}${c.dim('Se solicitará tu contraseña para instalar paquetes...')} `);
139
+ try { await execPromise('sudo -v', { stdio: 'inherit' }); }
140
+ catch (err) { console.log(`\n${c.red(' ✖ No se pudo obtener permisos de administrador.')}`); process.exit(1); }
146
141
  }
147
142
 
148
143
  const anim = new CoatiAnimator([
149
- "Revisando el sistema",
150
144
  "Canaima GNU/Linux: puro talento local",
151
145
  "Instalando magia negra (y librerías)",
152
- "Estabilidad y rendimiento basados en Debian",
153
- "Descargando herramientas de Rust",
154
- "¡Ya casi termino!"
146
+ "Descargando herramientas de Rust"
155
147
  ]);
156
148
 
157
149
  anim.start();
158
150
 
159
151
  try {
160
152
  if (missingApt) {
161
- 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`;
162
- await execPromise(aptCmd);
153
+ await execPromise(`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`);
163
154
  }
164
-
165
155
  if (missingRust) {
166
156
  await execPromise(`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`);
167
157
  process.env.PATH = `${process.env.HOME}/.cargo/bin:${process.env.PATH}`;
168
158
  }
169
-
170
- anim.stop(Dependencias instaladas con éxito!");
159
+ anim.stop(null, true); // Oculta el Coatí al terminar
160
+ console.log(`${tags.step} Dependencias de sistema instaladas\n`);
171
161
  } catch (err) {
172
162
  anim.stop("¡Ups! Algo falló.");
173
- console.error(c.red(`\n ✖ Error instalando dependencias: ${err.message}\n`));
163
+ console.error(`\n${c.red(' ✖ Error instalando dependencias:')} ${err.message}\n`);
174
164
  process.exit(1);
175
165
  }
176
166
  }
177
167
 
178
168
  // ==========================================
179
- // FLUJO PRINCIPAL DE PREGUNTAS
169
+ // PREGUNTAS INTERACTIVAS
180
170
  // ==========================================
181
171
  async function askQuestions() {
182
172
  return await prompts([
183
- {
184
- type : 'text',
185
- name : 'projectName',
186
- message : '¿Nombre de tu nuevo proyecto?',
187
- initial : 'mi-app-tauri',
188
- validate: (value) => value.trim().length > 0 ? true : 'El nombre no puede estar vacío.',
189
- },
190
- {
191
- type : 'select',
192
- name : 'templateType',
193
- message: '¿Qué framework de diseño prefieres?',
194
- choices: Object.entries(TEMPLATES).map(([value, { label }]) => ({ title: label, value })),
195
- },
196
- {
197
- type : 'toggle',
198
- name : 'installNpm',
199
- message: '¿Deseas instalar las dependencias de NPM ahora mismo?',
200
- initial: true,
201
- active : 'Sí',
202
- inactive: 'No'
203
- }
204
- ], {
205
- onCancel: () => {
206
- console.log(c.yellow('\n ⚠ Operación cancelada.\n'));
207
- process.exit(0);
208
- }
209
- });
173
+ { type: 'text', name: 'projectName', message: '¿Nombre de tu nuevo proyecto?', initial: 'mi-app-tauri', validate: (v) => v.trim().length > 0 ? true : 'No puede estar vacío.' },
174
+ { type: 'select', name: 'templateType', message: '¿Qué framework de diseño prefieres?', choices: Object.entries(TEMPLATES).map(([value, { label }]) => ({ title: label, value })) },
175
+ { type: 'toggle', name: 'installNpm', message: '¿Deseas instalar dependencias de NPM ahora?', initial: true, active: 'Sí', inactive: 'No' }
176
+ ], { onCancel: () => { console.log(c.yellow('\n ⚠ Operación cancelada.\n')); process.exit(0); } });
210
177
  }
211
178
 
179
+ // ==========================================
180
+ // LÓGICA DE ARCHIVOS
181
+ // ==========================================
212
182
  async function copyTemplate(folderName, targetDir) {
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);
219
- }
183
+ await fs.cp(path.join(__dirname, folderName), targetDir, {
184
+ recursive: true, filter: (src) => !['node_modules', 'target', '.git', 'package-lock.json', 'dist'].includes(path.basename(src))
220
185
  });
221
-
222
- const npmignorePath = path.join(targetDir, '.npmignore');
223
- try {
224
- await fs.rename(npmignorePath, path.join(targetDir, '.gitignore'));
225
- } catch (e) {}
186
+ try { await fs.rename(path.join(targetDir, '.npmignore'), path.join(targetDir, '.gitignore')); } catch (e) {}
226
187
  }
227
188
 
228
189
  async function personalizeProject(targetDir, projectName) {
229
- const pkgPath = path.join(targetDir, 'package.json');
230
190
  try {
231
- const pkgRaw = await fs.readFile(pkgPath, 'utf-8');
232
- const pkgJson = JSON.parse(pkgRaw);
191
+ const pkgPath = path.join(targetDir, 'package.json');
192
+ const pkgJson = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
233
193
  pkgJson.name = projectName;
234
194
  await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
235
195
  } catch (err) {}
236
196
 
237
- const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
238
197
  try {
198
+ const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
239
199
  let cargo = await fs.readFile(cargoPath, 'utf-8');
240
- cargo = cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`);
241
- await fs.writeFile(cargoPath, cargo);
200
+ await fs.writeFile(cargoPath, cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`));
242
201
  } catch (err) {}
243
202
 
244
- const tauriConfPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
245
203
  try {
246
- const confRaw = await fs.readFile(tauriConfPath, 'utf-8');
247
- const confJson = JSON.parse(confRaw);
248
-
204
+ const confPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
205
+ const confJson = JSON.parse(await fs.readFile(confPath, 'utf-8'));
249
206
  if (confJson.productName !== undefined) confJson.productName = projectName;
250
207
  if (confJson.identifier !== undefined) confJson.identifier = `com.canaima.${projectName}`;
251
208
  if (confJson.bundle?.identifier) confJson.bundle.identifier = `com.canaima.${projectName}`;
252
-
253
- await fs.writeFile(tauriConfPath, JSON.stringify(confJson, null, 2) + '\n');
209
+ await fs.writeFile(confPath, JSON.stringify(confJson, null, 2) + '\n');
254
210
  } catch (err) {}
255
211
  }
256
212
 
257
213
  async function runNpmInstall(targetDir) {
258
- const anim = new CoatiAnimator([
259
- "Configurando los nodos",
260
- "Trayendo paquetes del ciberespacio",
261
- "¡Preparando el entorno Vue!",
262
- "Haciendo que todo encaje perfecto"
263
- ]);
264
-
214
+ const anim = new CoatiAnimator(["Configurando los nodos", "Trayendo paquetes del ciberespacio", "Haciendo que todo encaje perfecto"]);
265
215
  anim.start();
266
216
 
267
217
  return new Promise((resolve) => {
268
- const child = spawn('npm', ['install'], {
269
- cwd: targetDir,
270
- shell: true,
271
- stdio: 'ignore'
272
- });
273
-
218
+ const child = spawn('npm', ['install'], { cwd: targetDir, shell: true, stdio: 'ignore' });
274
219
  child.on('close', (code) => {
275
- if (code !== 0) {
276
- anim.stop("Mmm, hubo un problema con NPM.");
277
- resolve(false);
278
- } else {
279
- anim.stop("¡NPM Install completado! Listo para la acción.");
280
- resolve(true);
281
- }
220
+ anim.stop(null, true); // Oculta el Coatí animado limpiamente al finalizar
221
+ resolve(code === 0);
282
222
  });
283
223
  });
284
224
  }
285
225
 
226
+ // ==========================================
227
+ // FLUJO PRINCIPAL
228
+ // ==========================================
286
229
  async function main() {
287
- console.log(c.bold(c.white('\n 🚀 Generador de proyectos CanaimaApp (Tauri + Vue + Rust)\n')));
288
-
289
230
  await checkAndInstallDependencies();
290
231
 
291
232
  const answers = await askQuestions();
@@ -293,35 +234,45 @@ async function main() {
293
234
  if (!projectName || !templateType) process.exit(1);
294
235
 
295
236
  const targetDir = resolve(process.cwd(), projectName);
296
- const template = TEMPLATES[templateType];
297
237
 
298
- try {
299
- await fs.access(targetDir);
300
- console.log(c.red(`\n ✖ La carpeta "${projectName}" ya existe. Elige otro nombre.\n`));
301
- process.exit(1);
302
- } catch {}
303
-
304
- try {
305
- console.log(c.cyan(`\n 📦 Generando archivos del proyecto...`));
306
- await copyTemplate(template.folder, targetDir);
307
- await personalizeProject(targetDir, projectName);
308
- console.log(c.green(' ✔ Archivos base copiados y configurados.\n'));
238
+ try { await fs.access(targetDir); console.log(`\n${c.red(` ✖ La carpeta "${projectName}" ya existe.`)}\n`); process.exit(1); } catch {}
309
239
 
310
- if (installNpm) {
311
- await runNpmInstall(targetDir);
312
- }
240
+ // --- INICIO DEL RESUMEN VISUAL ESTILO ASTRO ---
241
+ console.log(`\n${tags.canaima} Launch sequence initiated.\n`);
242
+
243
+ console.log(`${tags.dir} Where should we create your new project?`);
244
+ console.log(`${indent}${c.dim('./' + projectName)}\n`);
245
+
246
+ console.log(`${tags.tmpl} How would you like to start your new project?`);
247
+ console.log(`${indent}${c.dim(TEMPLATES[templateType].label)}\n`);
248
+
249
+ console.log(`${tags.deps} Install dependencies?`);
250
+ console.log(`${indent}${c.dim(installNpm ? 'Yes' : 'No')}\n`);
313
251
 
314
- console.log(c.bgGreen(c.white(' ✅ ¡Proyecto creado con éxito! ')));
315
- console.log(c.bold('\n Siguientes pasos:\n'));
316
- console.log(c.white(` ${c.cyan('$')} cd ${projectName}`));
317
- if (!installNpm) console.log(c.white(` ${c.cyan('$')} npm install`));
318
- console.log(c.white(` ${c.cyan('$')} npm run tauri dev`));
319
- console.log(c.dim('\n ¡A programar! 🦅\n'));
252
+ console.log(`${tags.done} Project initialized!`);
253
+
254
+ await copyTemplate(TEMPLATES[templateType].folder, targetDir);
255
+ console.log(`${tags.step} Template copied`);
256
+
257
+ await personalizeProject(targetDir, projectName);
258
+ console.log(`${tags.step} Configuration updated`);
320
259
 
321
- } catch (err) {
322
- console.error(c.red(`\n ✖ Error: ${err.message}\n`));
323
- process.exit(1);
260
+ if (installNpm) {
261
+ await runNpmInstall(targetDir);
262
+ console.log(`${tags.step} Dependencies installed`);
324
263
  }
264
+
265
+ // --- NEXT STEPS ESTILO ASTRO ---
266
+ console.log(`\n${tags.next} Liftoff confirmed. Explore your project!\n`);
267
+ console.log(`${indent}Enter your project directory using ${c.cyan(`cd ./${projectName}`)}`);
268
+ if (!installNpm) console.log(`${indent}Run ${c.cyan('npm install')} to install dependencies.`);
269
+ console.log(`${indent}Run ${c.cyan('npm run tauri dev')} to start the dev server.\n`);
270
+
271
+ // --- LA CAJA FINAL DEL MASCOTA ---
272
+ console.log(`╭───────╮ ${c.cyan('Coati:')}`);
273
+ console.log(`│ ${c.bold('^---^')} │ ¡Buena suerte ahí afuera, astronauta! 🚀`);
274
+ console.log(`│ ${c.bold('( ^_^ )')} │`);
275
+ console.log(`╰───────╯\n`);
325
276
  }
326
277
 
327
278
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-canaima-app",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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",