create-canaima-app 1.0.7 → 1.0.10
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 +110 -0
- package/index.js +204 -276
- package/package.json +17 -3
- package/tauri-materialize/package-lock.json +0 -2331
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://canaima.softwarelibre.gob.ve/static/img/canaima-logo.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
|
+
[](https://www.npmjs.com/package/create-canaima-app)
|
|
9
|
+
[](#)
|
|
10
|
+
[](#)
|
|
11
|
+
[](#)
|
|
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
|
+
Nuestro asistente amigable `\(^-^)/` te guiará con unas pocas preguntas:
|
|
30
|
+
1. El nombre de tu proyecto.
|
|
31
|
+
2. La plantilla y el sistema de estilos que prefieras (Tailwind CSS o Materialize CSS).
|
|
32
|
+
3. Si deseas instalar las dependencias de NPM automáticamente.
|
|
33
|
+
4. Si deseas inicializar un repositorio Git local (si tienes Git instalado).
|
|
34
|
+
|
|
35
|
+
¡Y listo! Todo quedará configurado, desde el `package.json` hasta el `tauri.conf.json`, listo para que empieces a programar.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 📦 Plantillas Disponibles
|
|
40
|
+
|
|
41
|
+
Hemos preparado dos plantillas robustas y modernas para que inicies con el pie derecho, ambas bajo la filosofía de **Material Design**:
|
|
42
|
+
|
|
43
|
+
### 1. Tauri + Material Tailwind (`tauri-material-tailwind`)
|
|
44
|
+
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.
|
|
45
|
+
|
|
46
|
+
### 2. Tauri + Materialize CSS (`tauri-materialize`)
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
#### 🗂️ Estructura de `tauri-materialize`
|
|
50
|
+
|
|
51
|
+
Cuando creas un proyecto usando esta plantilla, obtienes una estructura sumamente organizada y orientada a la mantenibilidad:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
📦 mi-app-canaima
|
|
55
|
+
┣ 📂 public/ # Assets públicos (favicon, logos originales)
|
|
56
|
+
┣ 📂 src-tauri/ # Backend en Rust (El corazón de Tauri)
|
|
57
|
+
┃ ┣ 📂 icons/ # Iconos para el binario generado
|
|
58
|
+
┃ ┣ 📂 src/
|
|
59
|
+
┃ ┃ ┗ 📜 main.rs # Punto de entrada de la aplicación Rust
|
|
60
|
+
┃ ┣ 📜 build.rs # Script de compilación de Tauri
|
|
61
|
+
┃ ┣ 📜 Cargo.toml # Dependencias de Rust (¡Ya con el nombre de tu app!)
|
|
62
|
+
┃ ┗ 📜 tauri.conf.json # Configuración maestra de Tauri
|
|
63
|
+
┣ 📂 src/ # Frontend en Vue 3
|
|
64
|
+
┃ ┣ 📂 assets/ # CSS y multimedia
|
|
65
|
+
┃ ┃ ┣ 📜 index.css # Variables Materialize y colores de Canaima
|
|
66
|
+
┃ ┃ ┗ 📜 materialize.css # El framework CSS principal
|
|
67
|
+
┃ ┣ 📂 components/ # Componentes Vue reutilizables
|
|
68
|
+
┃ ┃ ┣ 📜 Greet.vue # Componente de ejemplo (Tauri Invoke)
|
|
69
|
+
┃ ┃ ┗ 📜 Navbar.vue # Barra de navegación principal Materialize
|
|
70
|
+
┃ ┣ 📂 router/ # Configuración de Vue Router
|
|
71
|
+
┃ ┃ ┗ 📜 index.js
|
|
72
|
+
┃ ┣ 📂 views/ # Páginas individuales
|
|
73
|
+
┃ ┃ ┣ 📜 HomeView.vue
|
|
74
|
+
┃ ┃ ┗ 📜 AboutView.vue
|
|
75
|
+
┃ ┣ 📜 App.vue # Componente raíz de Vue
|
|
76
|
+
┃ ┗ 📜 main.js # Punto de entrada del frontend (Inicializa Materialize, Router y Pinia)
|
|
77
|
+
┣ 📜 index.html # Plantilla base (¡Ya con el título de tu app!)
|
|
78
|
+
┣ 📜 package.json # Dependencias de Javascript
|
|
79
|
+
┣ 📜 vite.config.js # Configuración de Vite
|
|
80
|
+
┗ 📜 README.md # Documentación de tu proyecto
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Beneficios incluidos en `tauri-materialize`:**
|
|
84
|
+
- 🌓 Soporte base para el framework Materialize.
|
|
85
|
+
- 🚦 `vue-router` interconectado y listo para añadir múltiples páginas.
|
|
86
|
+
- 🍍 `pinia` listo en caso de necesitar manejo de estado global.
|
|
87
|
+
- 🎨 Variables de color unificadas con la paleta de Canaima (`#0b6793`).
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 💖 Agradecimientos y Tecnologías Base
|
|
92
|
+
|
|
93
|
+
Construimos sobre hombros de gigantes, porque la comunidad del Software Libre nos enseña que colaborando llegamos más lejos. Agradecemos enormemente a:
|
|
94
|
+
|
|
95
|
+
- **[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 🦀.
|
|
96
|
+
- **[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.
|
|
97
|
+
- **[Tailwind CSS](https://tailwindcss.com/):** Por revolucionar la manera en que maquetamos aplicaciones web haciéndolo todo más eficiente.
|
|
98
|
+
- **[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.
|
|
99
|
+
- 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.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 👨💻 Contribuir
|
|
104
|
+
|
|
105
|
+
¡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.
|
|
106
|
+
|
|
107
|
+
<div align="center">
|
|
108
|
+
<br />
|
|
109
|
+
<p>Construido con amor ❤️ y Software Libre por y para Venezuela 🇻🇪.</p>
|
|
110
|
+
</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
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
const ML = ' ';
|
|
93
33
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
126
|
+
// ── Confirmación ─────────────────────────────
|
|
241
127
|
console.log();
|
|
242
|
-
console.log(`${
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
250
|
-
console.log(`${indent} ${c.bold(c.white(TEMPLATES[templateType].label))}`);
|
|
251
|
-
console.log();
|
|
143
|
+
if (!confirm) process.exit(0);
|
|
252
144
|
|
|
253
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
264
|
-
|
|
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
|
-
|
|
267
|
-
await
|
|
268
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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(`${
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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);
|