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.
- package/index.js +115 -159
- 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) {
|
|
@@ -49,28 +57,26 @@ class CoatiAnimator {
|
|
|
49
57
|
this.interval = null;
|
|
50
58
|
this.frame = 0;
|
|
51
59
|
this.msgIndex = 0;
|
|
52
|
-
this.
|
|
53
|
-
process.stdout.write('\x1b[?25l');
|
|
60
|
+
this.isAnimating = false;
|
|
61
|
+
process.stdout.write('\x1b[?25l');
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
clear() {
|
|
57
|
-
if (this.
|
|
58
|
-
readline.moveCursor(process.stdout, 0, -
|
|
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
|
-
|
|
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);
|
|
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(`
|
|
90
|
-
c.cyan(
|
|
91
|
-
`
|
|
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')
|
|
95
|
-
this.
|
|
101
|
+
process.stdout.write(out.join('\n'));
|
|
102
|
+
this.isAnimating = true;
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
|
|
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');
|
|
108
|
+
process.stdout.write('\x1b[?25h');
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
//
|
|
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(
|
|
135
|
+
console.log(`\n${tags.deps} Pre-flight check: Faltan dependencias de sistema.`);
|
|
132
136
|
|
|
133
137
|
if (missingApt) {
|
|
134
|
-
console.log(c.dim('
|
|
135
|
-
try {
|
|
136
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
//
|
|
169
|
+
// PREGUNTAS INTERACTIVAS
|
|
175
170
|
// ==========================================
|
|
176
171
|
async function askQuestions() {
|
|
177
172
|
return await prompts([
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
|
227
|
-
const pkgJson = JSON.parse(
|
|
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
|
-
|
|
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
|
|
242
|
-
const confJson = JSON.parse(
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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();
|