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.
- package/index.js +106 -155
- 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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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Í (
|
|
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');
|
|
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(`
|
|
92
|
-
c.cyan(
|
|
93
|
-
`
|
|
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
|
-
|
|
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');
|
|
108
|
+
process.stdout.write('\x1b[?25h');
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
//
|
|
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(
|
|
135
|
+
console.log(`\n${tags.deps} Pre-flight check: Faltan dependencias de sistema.`);
|
|
137
136
|
|
|
138
137
|
if (missingApt) {
|
|
139
|
-
console.log(c.dim('
|
|
140
|
-
try {
|
|
141
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
//
|
|
169
|
+
// PREGUNTAS INTERACTIVAS
|
|
180
170
|
// ==========================================
|
|
181
171
|
async function askQuestions() {
|
|
182
172
|
return await prompts([
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
|
232
|
-
const pkgJson = JSON.parse(
|
|
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
|
-
|
|
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
|
|
247
|
-
const confJson = JSON.parse(
|
|
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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
311
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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();
|