create-canaima-app 1.0.8 → 1.0.11

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.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ <div align="center">
2
+ <img src="https://canaima.softwarelibre.gob.ve/wp-content/uploads/2024/10/canaima-logos-nuevo-blanco.png" alt="Canaima GNU/Linux Logo" width="200" />
3
+
4
+ # create-canaima-app
5
+
6
+ **El CLI oficial para arrancar proyectos Tauri + Vue 3 al estilo Canaima GNU/Linux 🇻🇪**
7
+
8
+ [![NPM Version](https://img.shields.io/npm/v/create-canaima-app?color=0b6793&style=for-the-badge)](https://www.npmjs.com/package/create-canaima-app)
9
+ [![Tauri](https://img.shields.io/badge/Tauri-2.0-24C8DB?style=for-the-badge&logo=tauri&logoColor=white)](#)
10
+ [![Vue.js](https://img.shields.io/badge/Vue_3-4FC08D?style=for-the-badge&logo=vue.js&logoColor=white)](#)
11
+ [![License](https://img.shields.io/badge/Licencia-GPLv3-blue?style=for-the-badge)](#)
12
+
13
+ </div>
14
+
15
+ ¡Hola! 👋 Bienvenido a **create-canaima-app**. Esta es una herramienta interactiva de línea de comandos (CLI) creada para la comunidad de **Canaima GNU/Linux**, diseñada para facilitarte enormemente la vida al momento de iniciar proyectos de escritorio modernos y excepcionalmente rápidos utilizando **Tauri** y **Vue 3**.
16
+
17
+ Nuestro objetivo es brindar soberanía tecnológica con las mejores herramientas de desarrollo web adaptadas al escritorio.
18
+
19
+ ---
20
+
21
+ ## 🚀 Uso Rápido
22
+
23
+ No necesitas instalar el paquete globalmente, simplemente ejecuta:
24
+
25
+ ```bash
26
+ npx create-canaima-app@latest
27
+ ```
28
+
29
+ 1. El nombre de tu proyecto.
30
+ 2. La plantilla y el sistema de estilos que prefieras (Tailwind CSS o Materialize CSS).
31
+ 3. Si deseas instalar las dependencias de NPM automáticamente.
32
+ 4. Si deseas inicializar un repositorio Git local (si tienes Git instalado).
33
+
34
+ ¡Y listo! Todo quedará configurado, desde el `package.json` hasta el `tauri.conf.json`, listo para que empieces a programar.
35
+
36
+ ---
37
+
38
+ ## 📦 Plantillas Disponibles
39
+
40
+ Hemos preparado dos plantillas robustas y modernas para que inicies con el pie derecho, ambas bajo la filosofía de **Material Design**:
41
+
42
+ ### 1. Tauri + Material Tailwind (`tauri-material-tailwind`)
43
+ La modernidad de Tailwind CSS unida a los componentes prefabricados que emulan Material Design. Ideal para quienes disfrutan maquetar rápidamente con clases utilitarias de Tailwind, sin sacrificar la apariencia pulida de Material.
44
+
45
+ ### 2. Tauri + Materialize CSS (`tauri-materialize`)
46
+ Para los amantes del diseño clásico de Material Design, esta plantilla integra el ecosistema de [Materialize CSS](https://materializecss.com/) nativamente con Vue 3. Está pensada para quienes prefieren la escritura tradicional de CSS, combinada con Javascript modularizado para los componentes interactivos.
47
+
48
+ #### 🗂️ Estructura de `tauri-materialize`
49
+
50
+ Cuando creas un proyecto usando esta plantilla, obtienes una estructura sumamente organizada y orientada a la mantenibilidad:
51
+
52
+ ```text
53
+ 📦 mi-app-canaima
54
+ ┣ 📂 public/ # Assets públicos (favicon, logos originales)
55
+ ┣ 📂 src-tauri/ # Backend en Rust (El corazón de Tauri)
56
+ ┃ ┣ 📂 icons/ # Iconos para el binario generado
57
+ ┃ ┣ 📂 src/
58
+ ┃ ┃ ┗ 📜 main.rs # Punto de entrada de la aplicación Rust
59
+ ┃ ┣ 📜 build.rs # Script de compilación de Tauri
60
+ ┃ ┣ 📜 Cargo.toml # Dependencias de Rust (¡Ya con el nombre de tu app!)
61
+ ┃ ┗ 📜 tauri.conf.json # Configuración maestra de Tauri
62
+ ┣ 📂 src/ # Frontend en Vue 3
63
+ ┃ ┣ 📂 assets/ # CSS y multimedia
64
+ ┃ ┃ ┣ 📜 index.css # Variables Materialize y colores de Canaima
65
+ ┃ ┃ ┗ 📜 materialize.css # El framework CSS principal
66
+ ┃ ┣ 📂 components/ # Componentes Vue reutilizables
67
+ ┃ ┃ ┣ 📜 Greet.vue # Componente de ejemplo (Tauri Invoke)
68
+ ┃ ┃ ┗ 📜 Navbar.vue # Barra de navegación principal Materialize
69
+ ┃ ┣ 📂 router/ # Configuración de Vue Router
70
+ ┃ ┃ ┗ 📜 index.js
71
+ ┃ ┣ 📂 views/ # Páginas individuales
72
+ ┃ ┃ ┣ 📜 HomeView.vue
73
+ ┃ ┃ ┗ 📜 AboutView.vue
74
+ ┃ ┣ 📜 App.vue # Componente raíz de Vue
75
+ ┃ ┗ 📜 main.js # Punto de entrada del frontend (Inicializa Materialize, Router y Pinia)
76
+ ┣ 📜 index.html # Plantilla base (¡Ya con el título de tu app!)
77
+ ┣ 📜 package.json # Dependencias de Javascript
78
+ ┣ 📜 vite.config.js # Configuración de Vite
79
+ ┗ 📜 README.md # Documentación de tu proyecto
80
+ ```
81
+
82
+ **Beneficios incluidos en `tauri-materialize`:**
83
+ - 🌓 Soporte base para el framework Materialize.
84
+ - 🚦 `vue-router` interconectado y listo para añadir múltiples páginas.
85
+ - 🍍 `pinia` listo en caso de necesitar manejo de estado global.
86
+ - 🎨 Variables de color unificadas con la paleta de Canaima (`#0b6793`).
87
+
88
+ ---
89
+
90
+ ## 💖 Agradecimientos y Tecnologías Base
91
+
92
+ Construimos sobre hombros de gigantes, porque la comunidad del Software Libre nos enseña que colaborando llegamos más lejos. Agradecemos enormemente a:
93
+
94
+ - **[Tauri](https://tauri.app/):** Por brindarnos la manera más segura, liviana e increíble de construir aplicaciones de escritorio usando tecnologías web y el poder de Rust 🦀.
95
+ - **[Vue.js (Vue 3)](https://vuejs.org/):** Por ser el framework progresivo de Javascript más elegante, fácil de aprender y con el mejor ecosistema para construir interfaces dinámicas.
96
+ - **[Tailwind CSS](https://tailwindcss.com/):** Por revolucionar la manera en que maquetamos aplicaciones web haciéndolo todo más eficiente.
97
+ - **[Materialize CSS](https://materializecss.com/) y [Material Tailwind](https://www.material-tailwind.com/):** Por facilitarnos la implementación de un Material Design visualmente espectacular sin dolores de cabeza.
98
+ - Y por supuesto, a la comunidad de **[Canaima GNU/Linux](https://canaima.softwarelibre.gob.ve/)**, que siempre se ha mantenido fiel a los propósitos de libertad y soberanía popular.
99
+
100
+ ---
101
+
102
+ ## 👨‍💻 Contribuir
103
+
104
+ ¡Todo aporte es bienvenido! Si encuentras algún issue o quieres proponer una mejora (como nuevas plantillas), siéntete libre de abrir un Pull Request en nuestro repositorio.
105
+
106
+ <div align="center">
107
+ <br />
108
+ <p>Construido con amor ❤️ y Software Libre por y para Venezuela 🇻🇪.</p>
109
+ </div>
package/index.js CHANGED
@@ -1,320 +1,248 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import prompts from 'prompts';
4
- import { promises as fs, existsSync } from 'fs';
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { exec, spawn } from 'child_process';
8
4
  import { promisify } from 'util';
9
- import readline from 'readline';
5
+ import { exec } from 'child_process';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
10
8
 
11
9
  const execPromise = promisify(exec);
12
10
 
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = path.dirname(__filename);
15
- const { resolve } = path;
16
-
17
- // Colores ANSI
18
- const c = {
19
- reset : (t) => `\x1b[0m${t}\x1b[0m`,
20
- bold : (t) => `\x1b[1m${t}\x1b[0m`,
21
- dim : (t) => `\x1b[2m${t}\x1b[0m`,
22
- cyan : (t) => `\x1b[36m${t}\x1b[0m`,
23
- green : (t) => `\x1b[32m${t}\x1b[0m`,
24
- yellow : (t) => `\x1b[33m${t}\x1b[0m`,
25
- red : (t) => `\x1b[31m${t}\x1b[0m`,
26
- white : (t) => `\x1b[97m${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`,
32
- };
33
-
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(' ◆')
11
+ const C = {
12
+ reset: '\x1b[0m',
13
+ bold: '\x1b[1m',
14
+ dim: '\x1b[2m',
15
+ primary: '\x1b[38;5;24m',
16
+ accent: '\x1b[38;5;38m',
17
+ white: '\x1b[97m',
18
+ yellow: '\x1b[38;5;220m',
19
+ green: '\x1b[38;5;78m',
20
+ gray: '\x1b[38;5;245m',
43
21
  };
44
- const indent = ' '; // 9 espacios para alinear con las etiquetas
45
-
46
- const TEMPLATES = {
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' },
49
- };
50
-
51
- // ==========================================
52
- // SISTEMA DE ANIMACIÓN DEL COATÍ (Estilo Astro)
53
- // ==========================================
54
- class CoatiAnimator {
55
- constructor(messages) {
56
- this.messages = messages;
57
- this.interval = null;
58
- this.frame = 0;
59
- this.msgIndex = 0;
60
- this.isAnimating = false;
61
- process.stdout.write('\x1b[?25l');
62
- }
63
-
64
- clear() {
65
- if (this.isAnimating) {
66
- readline.moveCursor(process.stdout, 0, -2);
67
- readline.cursorTo(process.stdout, 0);
68
- readline.clearScreenDown(process.stdout);
69
- }
70
- }
71
-
72
- start() {
73
- this.render();
74
- this.isAnimating = true;
75
- this.interval = setInterval(() => {
76
- this.frame++;
77
- if (this.frame % 15 === 0) this.msgIndex = (this.msgIndex + 1) % this.messages.length;
78
- this.render();
79
- }, 250);
80
- }
81
-
82
- render(isFinal = false, finalMessage = null) {
83
- this.clear();
22
+ const p = t => `${C.primary}${t}${C.reset}`;
23
+ const pb = t => `${C.primary}${C.bold}${t}${C.reset}`;
24
+ const ab = t => `${C.accent}${C.bold}${t}${C.reset}`;
25
+ const g = t => `${C.gray}${t}${C.reset}`;
26
+ const y = t => `${C.yellow}${t}${C.reset}`;
27
+ const gr = t => `${C.green}${t}${C.reset}`;
28
+ const w = t => `${C.white}${t}${C.reset}`;
84
29
 
85
- const isBlinking = !isFinal && this.frame % 12 === 0;
86
- const face = isFinal ? '( ^_^ )' : (isBlinking ? '( -_- )' : '( ._. )');
87
-
88
- const baseMsg = finalMessage || this.messages[this.msgIndex];
89
- const dots = isFinal ? " " : ".".repeat(this.frame % 4).padEnd(3, " ");
90
- const displayMsg = baseMsg + dots;
30
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
91
31
 
92
- const line = ''.repeat(displayMsg.length + 2);
32
+ const ML = ' ';
93
33
 
94
- // Animación indentada para que quede en línea con el estilo Astro
95
- const out = [
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}╯`)}`
99
- ];
100
-
101
- process.stdout.write(out.join('\n'));
102
- this.isAnimating = true;
103
- }
104
-
105
- // Si clearAfter es true, el Coatí desaparece al terminar y deja el espacio limpio
106
- stop(finalMessage = null, clearAfter = false) {
107
- if (this.interval) clearInterval(this.interval);
108
- process.stdout.write('\x1b[?25h');
109
-
110
- if (clearAfter) {
111
- this.clear();
112
- } else {
113
- this.render(true, finalMessage);
114
- console.log('\n');
115
- }
116
- this.isAnimating = false;
34
+ async function checkGitInstalled() {
35
+ try {
36
+ await execPromise('git --version');
37
+ return true;
38
+ } catch (err) {
39
+ return false;
117
40
  }
118
41
  }
119
42
 
120
- // ==========================================
121
- // DEPENDENCIAS DEL SISTEMA
122
- // ==========================================
123
- async function checkAndInstallDependencies() {
124
- const isDebian = existsSync('/etc/debian_version');
125
- let missingApt = false;
126
- let missingRust = false;
127
-
128
- try { await execPromise('command -v cargo'); } catch { missingRust = true; }
129
- if (isDebian) {
130
- try { await execPromise('dpkg -l libwebkit2gtk-4.1-dev'); } catch { missingApt = true; }
131
- }
132
-
133
- if (!missingApt && !missingRust) return;
134
-
135
- console.log(`\n${tags.deps} Pre-flight check: Faltan dependencias de sistema.`);
136
-
137
- if (missingApt) {
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); }
141
- }
142
-
143
- const anim = new CoatiAnimator([
144
- "Canaima GNU/Linux: puro talento local",
145
- "Instalando magia negra (y librerías)",
146
- "Descargando herramientas de Rust"
147
- ]);
148
-
149
- anim.start();
150
-
43
+ async function updateFile(filePath, replacer) {
151
44
  try {
152
- if (missingApt) {
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`);
45
+ const content = await fs.readFile(filePath, 'utf8');
46
+ const newContent = replacer(content);
47
+ if (content !== newContent) {
48
+ await fs.writeFile(filePath, newContent, 'utf8');
154
49
  }
155
- if (missingRust) {
156
- await execPromise(`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`);
157
- process.env.PATH = `${process.env.HOME}/.cargo/bin:${process.env.PATH}`;
158
- }
159
- anim.stop(null, true); // Oculta el Coatí al terminar
160
- console.log(`${tags.step} Dependencias de sistema instaladas\n`);
161
50
  } catch (err) {
162
- anim.stop("¡Ups! Algo falló.");
163
- console.error(`\n${c.red(' ✖ Error instalando dependencias:')} ${err.message}\n`);
164
- process.exit(1);
51
+ // ignorar si no existe
165
52
  }
166
53
  }
167
54
 
168
- // ==========================================
169
- // PREGUNTAS INTERACTIVAS
170
- // ==========================================
171
- async function askQuestions() {
172
- return await prompts([
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); } });
177
- }
178
-
179
- // ==========================================
180
- // LÓGICA DE ARCHIVOS
181
- // ==========================================
182
- async function copyTemplate(folderName, targetDir) {
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))
185
- });
186
- try { await fs.rename(path.join(targetDir, '.npmignore'), path.join(targetDir, '.gitignore')); } catch (e) {}
187
- }
188
-
189
- async function personalizeProject(targetDir, projectName) {
190
- try {
191
- const pkgPath = path.join(targetDir, 'package.json');
192
- const pkgJson = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
193
- pkgJson.name = projectName;
194
- await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
195
- } catch (err) {}
196
-
197
- try {
198
- const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
199
- let cargo = await fs.readFile(cargoPath, 'utf-8');
200
- await fs.writeFile(cargoPath, cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`));
201
- } catch (err) {}
202
-
203
- try {
204
- const confPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
205
- const confJson = JSON.parse(await fs.readFile(confPath, 'utf-8'));
206
- if (confJson.productName !== undefined) confJson.productName = projectName;
207
- if (confJson.identifier !== undefined) confJson.identifier = `com.canaima.${projectName}`;
208
- if (confJson.bundle?.identifier) confJson.bundle.identifier = `com.canaima.${projectName}`;
209
- await fs.writeFile(confPath, JSON.stringify(confJson, null, 2) + '\n');
210
- } catch (err) {}
211
- }
212
-
213
- async function runNpmInstall(targetDir) {
214
- const anim = new CoatiAnimator(["Configurando los nodos", "Trayendo paquetes del ciberespacio", "Haciendo que todo encaje perfecto"]);
215
- anim.start();
216
-
217
- return new Promise((resolve) => {
218
- const child = spawn('npm', ['install'], { cwd: targetDir, shell: true, stdio: 'ignore' });
219
- child.on('close', (code) => {
220
- anim.stop(null, true); // Oculta el Coatí animado limpiamente al finalizar
221
- resolve(code === 0);
222
- });
223
- });
224
- }
225
-
226
- // ==========================================
227
- // FLUJO PRINCIPAL
228
- // ==========================================
229
55
  async function main() {
230
- await checkAndInstallDependencies();
231
56
 
232
- const answers = await askQuestions();
233
- const { projectName, templateType, installNpm } = answers;
234
- if (!projectName || !templateType) process.exit(1);
57
+ console.clear();
58
+ console.log();
59
+ console.log(`${ML}${pb('Canaima')}${p('/')}${w('GNU Linux')} ${g('· create-canaima-app')}`);
60
+ console.log(`${ML}${p('─'.repeat(48))}`);
61
+ console.log();
235
62
 
236
- const targetDir = resolve(process.cwd(), projectName);
63
+ console.log(`${ML}${w('¡Hola! Voy a ayudarte a crear tu proyecto.')}`);
64
+ console.log(`${ML}${g('Responde las siguientes preguntas para comenzar.')}`);
65
+ console.log();
237
66
 
238
- try { await fs.access(targetDir); console.log(`\n${c.red(` ✖ La carpeta "${projectName}" ya existe.`)}\n`); process.exit(1); } catch {}
67
+ const isGitAvailable = await checkGitInstalled();
68
+
69
+ // ── Nombre ───────────────────────────────────
70
+ const { projectName } = await prompts({
71
+ type: 'text',
72
+ name: 'projectName',
73
+ message: `${w('Nombre del proyecto')}`,
74
+ initial: 'mi-app-canaima',
75
+ validate: v => v.trim().length > 0 ? true : 'El nombre no puede estar vacío.',
76
+ }, { onCancel: () => process.exit(0) });
77
+
78
+ if (!projectName) process.exit(0);
79
+
80
+ // ── Estilos ───────────────────────────────────
81
+ const { cssFramework } = await prompts({
82
+ type: 'select',
83
+ name: 'cssFramework',
84
+ message: `${w('Sistema de estilos')}`,
85
+ choices: [
86
+ {
87
+ title: `${ab('Tailwind CSS')} ${g('tauri-material-tailwind')}`,
88
+ description: 'Material Design con Tailwind CSS + Vue 3',
89
+ value: 'tauri-material-tailwind', // Usamos directamente el nombre de la carpeta
90
+ },
91
+ {
92
+ title: `${pb('Materialize CSS')} ${g('tauri-materialize')}`,
93
+ description: 'Material Design clásico con Materialize CSS + Vue 3',
94
+ value: 'tauri-materialize',
95
+ },
96
+ ],
97
+ }, { onCancel: () => process.exit(0) });
98
+
99
+ if (!cssFramework) process.exit(0);
100
+ const templateName = cssFramework;
101
+
102
+ // ── NPM ───────────────────────────────────────
103
+ const { installNpm } = await prompts({
104
+ type: 'toggle',
105
+ name: 'installNpm',
106
+ message: `${w('Instalar dependencias NPM')}`,
107
+ initial: true,
108
+ active: gr('Sí'),
109
+ inactive: y('No'),
110
+ }, { onCancel: () => process.exit(0) });
111
+
112
+ // ── GIT ───────────────────────────────────────
113
+ let initGit = false;
114
+ if (isGitAvailable) {
115
+ const gitAns = await prompts({
116
+ type: 'toggle',
117
+ name: 'initGit',
118
+ message: `${w('Inicializar repositorio Git')}`,
119
+ initial: true,
120
+ active: gr('Sí'),
121
+ inactive: y('No'),
122
+ }, { onCancel: () => process.exit(0) });
123
+ initGit = gitAns.initGit;
124
+ }
239
125
 
240
- // --- INICIO DEL RESUMEN VISUAL ---
126
+ // ── Confirmación ─────────────────────────────
241
127
  console.log();
242
- console.log(`${tags.canaima} ${c.bold('Launch sequence initiated.')}`);
128
+ console.log(`${ML}${g('Proyecto :')} ${ab(projectName)}`);
129
+ console.log(`${ML}${g('Plantilla:')} ${p(templateName)}`);
130
+ console.log(`${ML}${g('NPM :')} ${installNpm ? gr('Sí') : y('No')}`);
131
+ if (isGitAvailable) {
132
+ console.log(`${ML}${g('Git :')} ${initGit ? gr('Sí') : y('No')}`);
133
+ }
243
134
  console.log();
244
135
 
245
- console.log(`${tags.dir} ${c.dim('Where should we create your new project?')}`);
246
- console.log(`${indent} ${c.bold(c.white('./' + projectName))}`);
247
- console.log();
136
+ const { confirm } = await prompts({
137
+ type: 'confirm',
138
+ name: 'confirm',
139
+ message: `${w('¿Todo correcto?')}`,
140
+ initial: true,
141
+ }, { onCancel: () => process.exit(0) });
248
142
 
249
- console.log(`${tags.tmpl} ${c.dim('Template selected')}`);
250
- console.log(`${indent} ${c.bold(c.white(TEMPLATES[templateType].label))}`);
251
- console.log();
143
+ if (!confirm) process.exit(0);
252
144
 
253
- console.log(`${tags.deps} ${c.dim('Install dependencies?')}`);
254
- console.log(`${indent} ${c.bold(c.white(installNpm ? 'Yes' : 'No'))}`);
145
+ // ── Scaffolding ───────────────────────────────
255
146
  console.log();
147
+
148
+ const targetDir = path.resolve(process.cwd(), projectName);
149
+ const templateDir = path.resolve(__dirname, templateName); // Asumimos que las carpetas están al lado del script
256
150
 
257
- // --- PROGRESO ---
258
- console.log(`${tags.done} ${c.bold('Project initialized!')}`);
151
+ try {
152
+ process.stdout.write(`${ML}${p('')} ${g(`Copiando plantilla ${templateName}`)}...`);
153
+
154
+ // Si no estamos usando symlinks locales (o si estamos dentro del mismo monorepo root), cp directo
155
+ await fs.cp(templateDir, targetDir, { recursive: true, force: true });
156
+
157
+ // Eliminar posibles cosas que no queramos de la plantilla base, ej: node_modules, dist, etc.
158
+ await fs.rm(path.join(targetDir, 'node_modules'), { recursive: true, force: true }).catch(()=>null);
159
+ await fs.rm(path.join(targetDir, 'dist'), { recursive: true, force: true }).catch(()=>null);
160
+ await fs.rm(path.join(targetDir, 'src-tauri', 'target'), { recursive: true, force: true }).catch(()=>null);
161
+ await fs.rm(path.join(targetDir, '.git'), { recursive: true, force: true }).catch(()=>null);
162
+
163
+ process.stdout.write(`\r${ML}${gr('✓')} ${w(`Plantilla copiada en ${projectName}`)} \n`);
164
+ await sleep(200);
165
+
166
+ // ── Personalizando Archivos ─────────────────────
167
+ process.stdout.write(`${ML}${p('›')} ${g('Personalizando configuraciones (package.json, Cargo.toml, etc.)')}...`);
168
+
169
+ // 1. package.json
170
+ await updateFile(path.join(targetDir, 'package.json'), content => {
171
+ // Reemplaza "name": "tauri-..." por "name": "mi-proyecto"
172
+ return content.replace(/"name"\s*:\s*"[^"]+"/, `"name": "${projectName}"`);
173
+ });
259
174
 
260
- await copyTemplate(TEMPLATES[templateType].folder, targetDir);
261
- console.log(`${tags.step} ${c.green('')} Template copied`);
175
+ // 2. src-tauri/Cargo.toml
176
+ await updateFile(path.join(targetDir, 'src-tauri', 'Cargo.toml'), content => {
177
+ // Reemplaza name = "tauri-..." por name = "mi-proyecto" bajo [package]
178
+ return content.replace(/name\s*=\s*"[^"]+"/, `name = "${projectName}"`);
179
+ });
262
180
 
263
- await personalizeProject(targetDir, projectName);
264
- console.log(`${tags.step} ${c.green('✓')} Configuration updated`);
181
+ // 3. src-tauri/tauri.conf.json
182
+ await updateFile(path.join(targetDir, 'src-tauri', 'tauri.conf.json'), content => {
183
+ let result = content;
184
+ // Actualiza product name
185
+ result = result.replace(/"productName"\s*:\s*"[^"]+"/, `"productName": "${projectName}"`);
186
+ // Actualiza el title de las ventanas
187
+ result = result.replace(/"title"\s*:\s*"[^"]+"/, `"title": "${projectName}"`);
188
+
189
+ // Actualiza identifier base
190
+ let safeName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '');
191
+ if (safeName === '') safeName = 'app';
192
+ result = result.replace(/"identifier"\s*:\s*"[^"]+"/, `"identifier": "com.${safeName}.dev"`);
193
+
194
+ return result;
195
+ });
265
196
 
266
- if (installNpm) {
267
- await runNpmInstall(targetDir);
268
- console.log(`${tags.step} ${c.green('✓')} Dependencies installed`);
269
- }
197
+ // 4. index.html title
198
+ await updateFile(path.join(targetDir, 'index.html'), content => {
199
+ return content.replace(/<title>.*?<\/title>/, `<title>${projectName}</title>`);
200
+ });
270
201
 
271
- // --- NEXT STEPS (BOX) ---
272
- // Usamos strings sin ANSI para el padding, y agregamos el color después
273
- const inner = 44;
274
- const bar = '─'.repeat(inner);
275
- const title = ' Proyecto listo. Siguientes pasos:';
276
- const titlePad = title + ' '.repeat(inner - title.length - 2);
202
+ process.stdout.write(`\r${ML}${gr('✓')} ${w('Configuraciones personalizadas')} \n`);
203
+ await sleep(200);
204
+
205
+ // ── GIT ───────────────────────────────────────
206
+ if (initGit) {
207
+ process.stdout.write(`${ML}${p('›')} ${g('Inicializando repositorio Git')}...`);
208
+ await execPromise('git init', { cwd: targetDir });
209
+ await execPromise('git add .', { cwd: targetDir });
210
+ try {
211
+ await execPromise('git commit -m "feat: init project via create-canaima-app"', { cwd: targetDir });
212
+ } catch (e) {
213
+ // Ignorar error si git user.email no está configurado (que a veces pasa en máquinas nuevas)
214
+ }
215
+ process.stdout.write(`\r${ML}${gr('✓')} ${w('Git inicializado')} \n`);
216
+ await sleep(200);
217
+ }
277
218
 
278
- const raw1 = ` $ cd ./${projectName}`;
279
- const raw2 = installNpm ? ' $ npm run tauri dev' : ' $ npm install && npm run tauri dev';
280
- const col1 = ` ${c.cyan('$')} cd ./${projectName}`;
281
- const col2 = installNpm
282
- ? ` ${c.cyan('$')} npm run tauri dev`
283
- : ` ${c.cyan('$')} npm install && npm run tauri dev`;
219
+ // ── NPM ───────────────────────────────────────
220
+ if (installNpm) {
221
+ process.stdout.write(`${ML}${p('')} ${g('Instalando dependencias NPM (esto puede tardar unos segundos)')}...`);
222
+ await execPromise('npm install', { cwd: targetDir });
223
+ process.stdout.write(`\r${ML}${gr('')} ${w('Dependencias instaladas')} \n`);
224
+ }
284
225
 
285
- const pad1 = ' '.repeat(Math.max(0, inner - 1 - raw1.length));
286
- const pad2 = ' '.repeat(Math.max(0, inner - 1 - raw2.length));
226
+ } catch (error) {
227
+ console.error(`\n${ML}${C.yellow}Ocurrió un error creando el proyecto:${C.reset}`);
228
+ console.error(error);
229
+ process.exit(1);
230
+ }
287
231
 
232
+ // ── Cierre ────────────────────────────────────
288
233
  console.log();
289
- console.log(`${indent}${c.dim('' + bar + '╮')}`);
290
- console.log(`${indent}${c.dim('│')} ${c.bold(c.green(titlePad))}${c.dim('│')}`);
291
- console.log(`${indent}${c.dim('│' + ' '.repeat(inner) + '│')}`);
292
- console.log(`${indent}${c.dim('│')} ${col1}${pad1}${c.dim('│')}`);
293
- console.log(`${indent}${c.dim('│')} ${col2}${pad2}${c.dim('│')}`);
294
- console.log(`${indent}${c.dim('╰' + bar + '╯')}`);
234
+ console.log(`${ML}${gr('')} ${w('¡Listo!')} ${ab(projectName)} ${w('creado correctamente.')}`);
295
235
  console.log();
296
-
297
- // --- MASCOTA FINAL ---
298
- // Caja de 9 chars internos para que ^---^ y ( ^_^ ) quepan centrados
299
- const coati = [
300
- c.cyan(' ╭─────────╮'),
301
- c.cyan(' │') + ` ^---^ ` + c.cyan('│'),
302
- c.cyan(' │') + ` ( ^_^ ) ` + c.cyan('│'),
303
- c.cyan(' ╰─────────╯'),
304
- ];
305
-
306
- const msg = [
307
- ` ${c.bold(c.cyan('Coatí de Canaima:'))}`,
308
- ` ${c.white('¡Proyecto creado con')}`,
309
- ` ${c.bold(c.green('software libre'))} ${c.white('venezolano!')} 🇻🇪`,
310
- ` ${c.dim('─────────────────────────')}`,
311
- ` ${c.dim('Canaima GNU/Linux')}`,
312
- ];
313
-
314
- for (let i = 0; i < Math.max(coati.length, msg.length); i++) {
315
- console.log(`${indent}${coati[i] || ' '}${msg[i] || ''}`);
316
- }
236
+ console.log(`${ML}${g('$')} ${p(`cd ${projectName}`)}`);
237
+ console.log(`${ML}${g('$')} ${p('npm run tauri dev')}`);
238
+ console.log();
239
+ console.log(`${ML}${g('Canaima/GNU Linux 🇻🇪')}`);
317
240
  console.log();
318
241
  }
319
242
 
320
- main();
243
+ // Necesita el import path y url para __dirname en ESM:
244
+ import { fileURLToPath } from 'url';
245
+ const __filename = fileURLToPath(import.meta.url);
246
+ const __dirname = path.dirname(__filename);
247
+
248
+ main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-canaima-app",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
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",
@@ -39,6 +39,7 @@
39
39
  "author": "Sam Urbina",
40
40
  "license": "ISC",
41
41
  "dependencies": {
42
+ "create-canaima-app": "^1.0.8",
42
43
  "prompts": "^2.4.2"
43
44
  }
44
- }
45
+ }
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "title": "plantilla-tauri-vue",
16
16
  "width": 800,
17
- "height": 600,
17
+ "height": 600
18
18
  }
19
19
  ],
20
20
  "security": {