create-canaima-app 1.0.4 → 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 +115 -159
  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) {
@@ -49,28 +57,26 @@ class CoatiAnimator {
49
57
  this.interval = null;
50
58
  this.frame = 0;
51
59
  this.msgIndex = 0;
52
- this.linesDrawn = 0;
53
- process.stdout.write('\x1b[?25l'); // Ocultar cursor
60
+ this.isAnimating = false;
61
+ process.stdout.write('\x1b[?25l');
54
62
  }
55
63
 
56
64
  clear() {
57
- if (this.linesDrawn > 0) {
58
- readline.moveCursor(process.stdout, 0, -this.linesDrawn);
65
+ if (this.isAnimating) {
66
+ readline.moveCursor(process.stdout, 0, -2);
67
+ readline.cursorTo(process.stdout, 0);
59
68
  readline.clearScreenDown(process.stdout);
60
69
  }
61
- this.linesDrawn = 0;
62
70
  }
63
71
 
64
72
  start() {
65
73
  this.render();
74
+ this.isAnimating = true;
66
75
  this.interval = setInterval(() => {
67
76
  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
- }
77
+ if (this.frame % 15 === 0) this.msgIndex = (this.msgIndex + 1) % this.messages.length;
72
78
  this.render();
73
- }, 250); // Velocidad de la animación
79
+ }, 250);
74
80
  }
75
81
 
76
82
  render(isFinal = false, finalMessage = null) {
@@ -85,202 +91,142 @@ class CoatiAnimator {
85
91
 
86
92
  const line = '─'.repeat(displayMsg.length + 2);
87
93
 
94
+ // Animación indentada para que quede en línea con el estilo Astro
88
95
  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}╯`)
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}╯`)}`
92
99
  ];
93
100
 
94
- process.stdout.write(out.join('\n') + '\n');
95
- this.linesDrawn = 3;
101
+ process.stdout.write(out.join('\n'));
102
+ this.isAnimating = true;
96
103
  }
97
104
 
98
- stop(finalMessage = null) {
105
+ // Si clearAfter es true, el Coatí desaparece al terminar y deja el espacio limpio
106
+ stop(finalMessage = null, clearAfter = false) {
99
107
  if (this.interval) clearInterval(this.interval);
100
- process.stdout.write('\x1b[?25h'); // Mostrar cursor
108
+ process.stdout.write('\x1b[?25h');
101
109
 
102
- this.render(true, finalMessage);
103
- console.log();
110
+ if (clearAfter) {
111
+ this.clear();
112
+ } else {
113
+ this.render(true, finalMessage);
114
+ console.log('\n');
115
+ }
116
+ this.isAnimating = false;
104
117
  }
105
118
  }
106
119
 
107
120
  // ==========================================
108
- // VERIFICACIÓN DE DEPENDENCIAS (Debian/Rust)
121
+ // DEPENDENCIAS DEL SISTEMA
109
122
  // ==========================================
110
123
  async function checkAndInstallDependencies() {
111
124
  const isDebian = existsSync('/etc/debian_version');
112
125
  let missingApt = false;
113
126
  let missingRust = false;
114
127
 
115
- try {
116
- await execPromise('command -v cargo');
117
- } catch {
118
- missingRust = true;
119
- }
120
-
128
+ try { await execPromise('command -v cargo'); } catch { missingRust = true; }
121
129
  if (isDebian) {
122
- try {
123
- await execPromise('dpkg -l libwebkit2gtk-4.1-dev');
124
- } catch {
125
- missingApt = true;
126
- }
130
+ try { await execPromise('dpkg -l libwebkit2gtk-4.1-dev'); } catch { missingApt = true; }
127
131
  }
128
132
 
129
133
  if (!missingApt && !missingRust) return;
130
134
 
131
- console.log(c.yellow('\n Faltan dependencias necesarias para compilar Tauri.'));
135
+ console.log(`\n${tags.deps} Pre-flight check: Faltan dependencias de sistema.`);
132
136
 
133
137
  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
- }
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
141
  }
142
142
 
143
143
  const anim = new CoatiAnimator([
144
- "Revisando el sistema",
145
144
  "Canaima GNU/Linux: puro talento local",
146
145
  "Instalando magia negra (y librerías)",
147
- "Estabilidad y rendimiento basados en Debian",
148
- "Descargando herramientas de Rust",
149
- "¡Ya casi termino!"
146
+ "Descargando herramientas de Rust"
150
147
  ]);
151
148
 
152
149
  anim.start();
153
150
 
154
151
  try {
155
152
  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);
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`);
158
154
  }
159
-
160
155
  if (missingRust) {
161
156
  await execPromise(`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`);
162
157
  process.env.PATH = `${process.env.HOME}/.cargo/bin:${process.env.PATH}`;
163
158
  }
164
-
165
- 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`);
166
161
  } catch (err) {
167
162
  anim.stop("¡Ups! Algo falló.");
168
- console.error(c.red(`\n ✖ Error instalando dependencias: ${err.message}\n`));
163
+ console.error(`\n${c.red(' ✖ Error instalando dependencias:')} ${err.message}\n`);
169
164
  process.exit(1);
170
165
  }
171
166
  }
172
167
 
173
168
  // ==========================================
174
- // FLUJO PRINCIPAL DE PREGUNTAS
169
+ // PREGUNTAS INTERACTIVAS
175
170
  // ==========================================
176
171
  async function askQuestions() {
177
172
  return await prompts([
178
- {
179
- type : 'text',
180
- name : 'projectName',
181
- message : '¿Nombre de tu nuevo proyecto?',
182
- initial : 'mi-app-tauri',
183
- validate: (value) => value.trim().length > 0 ? true : 'El nombre no puede estar vacío.',
184
- },
185
- {
186
- type : 'select',
187
- name : 'templateType',
188
- message: '¿Qué framework de diseño prefieres?',
189
- choices: Object.entries(TEMPLATES).map(([value, { label }]) => ({ title: label, value })),
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
- }
199
- ], {
200
- onCancel: () => {
201
- console.log(c.yellow('\n ⚠ Operación cancelada.\n'));
202
- process.exit(0);
203
- }
204
- });
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); } });
205
177
  }
206
178
 
179
+ // ==========================================
180
+ // LÓGICA DE ARCHIVOS
181
+ // ==========================================
207
182
  async function copyTemplate(folderName, targetDir) {
208
- const templateDir = path.join(__dirname, folderName);
209
- await fs.cp(templateDir, targetDir, {
210
- recursive: true,
211
- filter: (source) => {
212
- const basename = path.basename(source);
213
- return !['node_modules', 'target', '.git', 'package-lock.json', 'dist'].includes(basename);
214
- }
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))
215
185
  });
216
-
217
- const npmignorePath = path.join(targetDir, '.npmignore');
218
- try {
219
- await fs.rename(npmignorePath, path.join(targetDir, '.gitignore'));
220
- } catch (e) {}
186
+ try { await fs.rename(path.join(targetDir, '.npmignore'), path.join(targetDir, '.gitignore')); } catch (e) {}
221
187
  }
222
188
 
223
189
  async function personalizeProject(targetDir, projectName) {
224
- const pkgPath = path.join(targetDir, 'package.json');
225
190
  try {
226
- const pkgRaw = await fs.readFile(pkgPath, 'utf-8');
227
- const pkgJson = JSON.parse(pkgRaw);
191
+ const pkgPath = path.join(targetDir, 'package.json');
192
+ const pkgJson = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
228
193
  pkgJson.name = projectName;
229
194
  await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
230
195
  } catch (err) {}
231
196
 
232
- const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
233
197
  try {
198
+ const cargoPath = path.join(targetDir, 'src-tauri', 'Cargo.toml');
234
199
  let cargo = await fs.readFile(cargoPath, 'utf-8');
235
- cargo = cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`);
236
- await fs.writeFile(cargoPath, cargo);
200
+ await fs.writeFile(cargoPath, cargo.replace(/^(name\s*=\s*)"[^"]*"/m, `$1"${projectName}"`));
237
201
  } catch (err) {}
238
202
 
239
- const tauriConfPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
240
203
  try {
241
- const confRaw = await fs.readFile(tauriConfPath, 'utf-8');
242
- const confJson = JSON.parse(confRaw);
243
-
204
+ const confPath = path.join(targetDir, 'src-tauri', 'tauri.conf.json');
205
+ const confJson = JSON.parse(await fs.readFile(confPath, 'utf-8'));
244
206
  if (confJson.productName !== undefined) confJson.productName = projectName;
245
207
  if (confJson.identifier !== undefined) confJson.identifier = `com.canaima.${projectName}`;
246
208
  if (confJson.bundle?.identifier) confJson.bundle.identifier = `com.canaima.${projectName}`;
247
-
248
- await fs.writeFile(tauriConfPath, JSON.stringify(confJson, null, 2) + '\n');
209
+ await fs.writeFile(confPath, JSON.stringify(confJson, null, 2) + '\n');
249
210
  } catch (err) {}
250
211
  }
251
212
 
252
213
  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
-
214
+ const anim = new CoatiAnimator(["Configurando los nodos", "Trayendo paquetes del ciberespacio", "Haciendo que todo encaje perfecto"]);
260
215
  anim.start();
261
216
 
262
217
  return new Promise((resolve) => {
263
- const child = spawn('npm', ['install'], {
264
- cwd: targetDir,
265
- shell: true,
266
- stdio: 'ignore'
267
- });
268
-
218
+ const child = spawn('npm', ['install'], { cwd: targetDir, shell: true, stdio: 'ignore' });
269
219
  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
- }
220
+ anim.stop(null, true); // Oculta el Coatí animado limpiamente al finalizar
221
+ resolve(code === 0);
277
222
  });
278
223
  });
279
224
  }
280
225
 
226
+ // ==========================================
227
+ // FLUJO PRINCIPAL
228
+ // ==========================================
281
229
  async function main() {
282
- console.log(c.bold(c.white('\n 🚀 Generador de proyectos CanaimaApp (Tauri + Vue + Rust)\n')));
283
-
284
230
  await checkAndInstallDependencies();
285
231
 
286
232
  const answers = await askQuestions();
@@ -288,35 +234,45 @@ async function main() {
288
234
  if (!projectName || !templateType) process.exit(1);
289
235
 
290
236
  const targetDir = resolve(process.cwd(), projectName);
291
- const template = TEMPLATES[templateType];
292
237
 
293
- try {
294
- await fs.access(targetDir);
295
- console.log(c.red(`\n ✖ La carpeta "${projectName}" ya existe. Elige otro nombre.\n`));
296
- process.exit(1);
297
- } catch {}
298
-
299
- try {
300
- console.log(c.cyan(`\n 📦 Generando archivos del proyecto...`));
301
- await copyTemplate(template.folder, targetDir);
302
- await personalizeProject(targetDir, projectName);
303
- 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 {}
304
239
 
305
- if (installNpm) {
306
- await runNpmInstall(targetDir);
307
- }
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`);
308
251
 
309
- console.log(c.bgGreen(c.white(' ✅ ¡Proyecto creado con éxito! ')));
310
- console.log(c.bold('\n Siguientes pasos:\n'));
311
- console.log(c.white(` ${c.cyan('$')} cd ${projectName}`));
312
- if (!installNpm) console.log(c.white(` ${c.cyan('$')} npm install`));
313
- console.log(c.white(` ${c.cyan('$')} npm run tauri dev`));
314
- 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`);
315
259
 
316
- } catch (err) {
317
- console.error(c.red(`\n ✖ Error: ${err.message}\n`));
318
- process.exit(1);
260
+ if (installNpm) {
261
+ await runNpmInstall(targetDir);
262
+ console.log(`${tags.step} Dependencies installed`);
319
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`);
320
276
  }
321
277
 
322
278
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-canaima-app",
3
- "version": "1.0.4",
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",