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