novatec-cli 3.0.6 → 3.0.8

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/bin/index.js CHANGED
@@ -26,8 +26,9 @@ async function checkForUpdates() {
26
26
  async function showBanner() {
27
27
  console.clear();
28
28
  const logo = figlet.textSync('NOVATEC', { font: 'Slant' });
29
+ const grad = (await import('gradient-string')).default;
29
30
  console.log();
30
- logo.split('\n').filter(l => l.trim()).forEach(l => console.log(' ' + chalk.bold.white(l)));
31
+ logo.split('\n').filter(l => l.trim()).forEach(l => console.log(' ' + grad.cristal.multiline(l)));
31
32
  console.log();
32
33
  const now = new Date().toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
33
34
  console.log(' ' + chalk.gray(`v${pkg.version} · Enterprise Fullstack CLI · ${now}`));
@@ -36,10 +37,40 @@ async function showBanner() {
36
37
  console.log();
37
38
  console.log(boxen(
38
39
  chalk.bold.white(' STACK DISPONIBLE\n\n') +
39
- chalk.white.bold(' Frontend ') + chalk.gray(' │ ') + ['React','Next.js','Vue','Nuxt','Astro','Svelte','SolidJS','Angular','Remix','Qwik'].join(chalk.gray(' · ')) + '\n\n' +
40
- chalk.white.bold(' Backend ') + chalk.gray(' │ ') + ['Express','NestJS','Fastify','Hono','FastAPI','Django','Flask','Spring','Deno','Go/Gin'].join(chalk.gray(' · ')) + '\n\n' +
41
- chalk.white.bold(' Database ') + chalk.gray(' ') + ['PostgreSQL','MySQL','MongoDB','SQLite','Supabase'].join(chalk.gray(' · ')) + '\n\n' +
42
- chalk.white.bold(' Modos ') + chalk.gray('') + chalk.white('--mode business') + chalk.gray(' (landing + auth + dashboard + CRUD)') + '\n\n' +
40
+ chalk.white.bold(' Frontend ') + chalk.gray(' │ ') +
41
+ [
42
+ grad('#61DAFB','#21D4FD')(' React'),
43
+ grad('#fff','#888')(' Next.js'),
44
+ grad('#42b883','#35495e')(' Vue'),
45
+ grad('#00DC82','#003543')(' Nuxt'),
46
+ grad('#FF5D01','#BC52EE')(' Astro'),
47
+ grad('#FF3E00','#FF8C00')(' Svelte'),
48
+ grad('#2C4F7C','#446B9E')(' Solid'),
49
+ grad('#DD0031','#C3002F')(' Angular'),
50
+ grad('#121212','#818CF8')(' Remix'),
51
+ grad('#18B6F6','#AC7EF4')(' Qwik'),
52
+ ].join(chalk.gray(' · ')) + '\n\n' +
53
+ chalk.white.bold(' Backend ') + chalk.gray(' │ ') +
54
+ [
55
+ grad('#fff','#aaa')(' Express'),
56
+ grad('#E0234E','#FF6B6B')(' NestJS'),
57
+ grad('#000','#888')(' Fastify'),
58
+ grad('#FF5733','#FF8C00')(' Hono'),
59
+ grad('#009688','#26A69A')(' FastAPI'),
60
+ grad('#092E20','#44B78B')(' Django'),
61
+ grad('#fff','#aaa')(' Flask'),
62
+ grad('#6DB33F','#77BC1F')(' Spring'),
63
+ grad('#fff','#aaa')(' Deno'),
64
+ grad('#00ACD7','#5DC9E2')(' Go/Gin'),
65
+ ].join(chalk.gray(' · ')) + '\n\n' +
66
+ chalk.white.bold(' Database ') + chalk.gray(' │ ') +
67
+ [
68
+ grad('#336791','#4A90D9')(' PostgreSQL'),
69
+ grad('#4479A1','#00758F')(' MySQL'),
70
+ grad('#47A248','#00ED64')(' MongoDB'),
71
+ grad('#003B57','#0F80CC')(' SQLite'),
72
+ grad('#3ECF8E','#1C1C1C')(' Supabase'),
73
+ ].join(chalk.gray(' · ')) + '\n\n' +
43
74
  chalk.white.bold(' Comandos ') + chalk.gray(' │ ') +
44
75
  [chalk.white('create'), chalk.white('add'), chalk.white('doctor'), chalk.white('build'), chalk.white('deploy'), chalk.white('list'), chalk.white('update')]
45
76
  .join(chalk.gray(' · ')),
@@ -48,7 +79,7 @@ async function showBanner() {
48
79
  console.log();
49
80
  console.log(boxen(
50
81
  chalk.gray(' $ novatec create my-app\n') +
51
- chalk.gray(' $ novatec create my-app --mode business --frontend next --backend express\n') +
82
+ chalk.gray(' $ novatec create my-app --frontend next --backend express --db postgres\n') +
52
83
  chalk.gray(' $ novatec add crud Product\n') +
53
84
  chalk.gray(' $ novatec doctor'),
54
85
  { padding: { top: 0, bottom: 0, left: 1, right: 4 }, margin: { left: 2 }, borderStyle: 'single', borderColor: 'gray', dimBorder: true }
@@ -1,10 +1,10 @@
1
1
  import path from 'path';
2
2
  import chalk from 'chalk';
3
3
  import fse from 'fs-extra';
4
+ import gradient from 'gradient-string';
4
5
  import { execSync } from 'child_process';
5
6
  import { isInstalled } from '../utils.js';
6
7
 
7
- // DB packages por backend type
8
8
  const NODE_DB_DEPS = {
9
9
  postgres: ['pg', 'pg-hstore'],
10
10
  mysql: ['mysql2'],
@@ -14,13 +14,17 @@ const NODE_DB_DEPS = {
14
14
  none: [],
15
15
  };
16
16
 
17
+ const COLORS = {
18
+ express: ['#ffffff', '#aaaaaa'], nestjs: ['#E0234E', '#FF6B6B'],
19
+ fastify: ['#000000', '#888888'], hono: ['#FF5733', '#FF8C00'],
20
+ fastapi: ['#009688', '#26A69A'], django: ['#092E20', '#44B78B'],
21
+ flask: ['#ffffff', '#aaaaaa'], spring: ['#6DB33F', '#77BC1F'],
22
+ deno: ['#ffffff', '#aaaaaa'], gin: ['#00ACD7', '#5DC9E2'],
23
+ };
24
+
17
25
  function installNodeDeps(prod, dev, cwd) {
18
- if (prod.length) {
19
- execSync(`npm install ${prod.join(' ')}`, { cwd, stdio: 'pipe' });
20
- }
21
- if (dev && dev.length) {
22
- execSync(`npm install -D ${dev.join(' ')}`, { cwd, stdio: 'pipe' });
23
- }
26
+ if (prod.length) execSync(`npm install ${prod.join(' ')}`, { cwd, stdio: 'pipe' });
27
+ if (dev && dev.length) execSync(`npm install -D ${dev.join(' ')}`, { cwd, stdio: 'pipe' });
24
28
  }
25
29
 
26
30
  function backendPath(projectPath) {
@@ -28,7 +32,9 @@ function backendPath(projectPath) {
28
32
  }
29
33
 
30
34
  export async function generateBackend(config, projectPath) {
31
- console.log(chalk.blue(`\n ⚙ Generando backend (${chalk.bold(config.backend)})...`));
35
+ const [c1, c2] = COLORS[config.backend] || ['#ffffff', '#aaaaaa'];
36
+ const label = gradient(c1, c2)(` ◆ ${config.backend.toUpperCase()} `);
37
+ console.log(`\n ${label} ${chalk.gray('Generando backend...')}`);
32
38
  const handlers = { express, nestjs, fastify, hono, fastapi, django, flask, spring, deno, gin };
33
39
  const fn = handlers[config.backend];
34
40
  if (!fn) throw new Error(`Backend no soportado: ${config.backend}`);
@@ -2,101 +2,9 @@ import path from 'path';
2
2
  import fse from 'fs-extra';
3
3
  import chalk from 'chalk';
4
4
  import boxen from 'boxen';
5
- import { execSync } from 'child_process';
5
+ import { execSync, spawnSync } from 'child_process';
6
6
  import { isInstalled } from '../utils.js';
7
7
 
8
- // ── GitHub Actions CI ─────────────────────────────────────────────────────────
9
- export async function generateDeploy(config, root) {
10
- const ciDir = path.join(root, '.github', 'workflows');
11
- await fse.ensureDir(ciDir);
12
- const isPython = ['fastapi','django','flask'].includes(config.backend);
13
-
14
- await fse.writeFile(path.join(ciDir, 'ci.yml'),
15
- `name: CI/CD Pipeline
16
-
17
- on:
18
- push:
19
- branches: [main, develop]
20
- pull_request:
21
- branches: [main]
22
-
23
- jobs:
24
- frontend:
25
- name: Frontend — Build & Test
26
- runs-on: ubuntu-latest
27
- defaults:
28
- run:
29
- working-directory: frontend
30
- steps:
31
- - uses: actions/checkout@v4
32
- - uses: actions/setup-node@v4
33
- with:
34
- node-version: 20
35
- cache: npm
36
- cache-dependency-path: frontend/package-lock.json
37
- - run: npm ci
38
- - run: npm run lint --if-present
39
- - run: npm test --if-present
40
- - run: npm run build
41
-
42
- backend:
43
- name: Backend — Lint & Test
44
- runs-on: ubuntu-latest
45
- defaults:
46
- run:
47
- working-directory: backend
48
- steps:
49
- - uses: actions/checkout@v4
50
- ${isPython
51
- ? ` - uses: actions/setup-python@v5
52
- with:
53
- python-version: '3.11'
54
- - run: pip install -r requirements.txt
55
- - run: python -m pytest --if-present`
56
- : ` - uses: actions/setup-node@v4
57
- with:
58
- node-version: 20
59
- - run: npm ci
60
- - run: npm run lint --if-present
61
- - run: npm test --if-present`}
62
- `);
63
-
64
- await fse.writeFile(path.join(ciDir, 'deploy.yml'),
65
- `name: Deploy
66
-
67
- on:
68
- push:
69
- branches: [main]
70
-
71
- jobs:
72
- deploy-frontend:
73
- name: Deploy Frontend → Vercel
74
- runs-on: ubuntu-latest
75
- steps:
76
- - uses: actions/checkout@v4
77
- - uses: actions/setup-node@v4
78
- with:
79
- node-version: 20
80
- - run: npm install -g vercel
81
- - run: cd frontend && npm ci && npm run build
82
- - run: vercel --prod --token \${{ secrets.VERCEL_TOKEN }} --yes
83
- env:
84
- VERCEL_ORG_ID: \${{ secrets.VERCEL_ORG_ID }}
85
- VERCEL_PROJECT_ID: \${{ secrets.VERCEL_PROJECT_ID }}
86
-
87
- deploy-backend:
88
- name: Deploy Backend → Railway
89
- runs-on: ubuntu-latest
90
- needs: deploy-frontend
91
- steps:
92
- - uses: actions/checkout@v4
93
- - uses: railwayapp/railway-github-action@v1
94
- with:
95
- railway-token: \${{ secrets.RAILWAY_TOKEN }}
96
- service: backend
97
- `);
98
- }
99
-
100
8
  // ── novatec build ─────────────────────────────────────────────────────────────
101
9
  export async function runBuild(options) {
102
10
  const root = process.cwd();
@@ -107,12 +15,13 @@ export async function runBuild(options) {
107
15
  console.log(chalk.white(' Building proyecto...\n'));
108
16
 
109
17
  if ((options.frontend || !options.backend) && hasFe) {
110
- const spinner = (await import('ora')).default({ text: chalk.white('Build frontend...'), spinner: 'dots', color: 'white' }).start();
18
+ const ora = (await import('ora')).default;
19
+ const spinner = ora({ text: chalk.white('Build frontend...'), spinner: 'dots', color: 'white' }).start();
111
20
  try {
112
21
  execSync('npm run build', { cwd: path.join(root, 'frontend'), stdio: 'pipe', encoding: 'utf8' });
113
- spinner.succeed(chalk.white('Frontend build OK'));
22
+ spinner.succeed(chalk.white('Frontend build '));
114
23
  } catch (err) {
115
- spinner.fail(chalk.red('Frontend build falló\n' + (err.stderr || '')));
24
+ spinner.fail(chalk.red('Frontend build falló: ' + (err.stderr || err.message || '').split('\n')[0]));
116
25
  }
117
26
  }
118
27
 
@@ -121,13 +30,16 @@ export async function runBuild(options) {
121
30
  if (await fse.pathExists(pkgPath)) {
122
31
  const pkg = await fse.readJSON(pkgPath);
123
32
  if (pkg.scripts?.build) {
124
- const spinner = (await import('ora')).default({ text: chalk.white('Build backend...'), spinner: 'dots', color: 'white' }).start();
33
+ const ora = (await import('ora')).default;
34
+ const spinner = ora({ text: chalk.white('Build backend...'), spinner: 'dots', color: 'white' }).start();
125
35
  try {
126
36
  execSync('npm run build', { cwd: path.join(root, 'backend'), stdio: 'pipe', encoding: 'utf8' });
127
- spinner.succeed(chalk.white('Backend build OK'));
128
- } catch {
129
- spinner.fail(chalk.red('Backend build falló'));
37
+ spinner.succeed(chalk.white('Backend build '));
38
+ } catch (err) {
39
+ spinner.fail(chalk.red('Backend build falló: ' + (err.stderr || err.message || '').split('\n')[0]));
130
40
  }
41
+ } else {
42
+ console.log(chalk.gray(' Backend: no tiene script build, omitiendo.'));
131
43
  }
132
44
  }
133
45
  }
@@ -139,36 +51,145 @@ export async function runBuild(options) {
139
51
 
140
52
  // ── novatec deploy ────────────────────────────────────────────────────────────
141
53
  export async function runDeploy(options) {
54
+ const root = process.cwd();
55
+ const hasFe = await fse.pathExists(path.join(root, 'frontend'));
56
+ const hasBe = await fse.pathExists(path.join(root, 'backend'));
57
+
58
+ const deployFe = options.frontend || (!options.frontend && !options.backend);
59
+ const deployBe = options.backend || (!options.frontend && !options.backend);
60
+
61
+ // Verificar que estamos en un proyecto novatec
62
+ if (!hasFe && !hasBe) {
63
+ console.log(boxen(
64
+ chalk.yellow(' Ejecuta este comando dentro de un proyecto NovaTec\n\n') +
65
+ chalk.gray(' cd mi-proyecto\n') +
66
+ chalk.gray(' novatec deploy'),
67
+ { padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'yellow', dimBorder: true }
68
+ ));
69
+ process.exit(1);
70
+ }
71
+
142
72
  console.log();
143
73
  console.log(boxen(
144
74
  chalk.bold.white(' DEPLOY — NovaTec CLI\n\n') +
145
- chalk.white(' Frontend Vercel\n') +
146
- chalk.white(' Backend Railway\n\n') +
147
- chalk.gray(' Asegúrate de tener instalados:\n') +
148
- chalk.gray(' npm install -g vercel\n') +
149
- chalk.gray(' npm install -g @railway/cli'),
75
+ (deployFe && hasFe ? chalk.white(' Frontend Vercel\n') : '') +
76
+ (deployBe && hasBe ? chalk.white(' Backend Railway\n') : ''),
150
77
  { padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
151
78
  ));
152
79
 
153
- const root = process.cwd();
80
+ // ── FRONTEND → VERCEL ───────────────────────────────────────────────────────
81
+ if (deployFe && hasFe) {
82
+ console.log(chalk.white('\n ▲ Desplegando frontend en Vercel...\n'));
154
83
 
155
- if ((options.frontend || !options.backend)) {
84
+ // Instalar Vercel CLI si no está
156
85
  if (!isInstalled('vercel')) {
157
- console.log(chalk.yellow('\n Instalando Vercel CLI...'));
158
- execSync('npm install -g vercel', { stdio: 'inherit' });
86
+ console.log(chalk.gray(' Instalando Vercel CLI...'));
87
+ try {
88
+ execSync('npm install -g vercel', { stdio: 'pipe' });
89
+ console.log(chalk.green(' ✔ Vercel CLI instalado'));
90
+ } catch (e) {
91
+ console.log(chalk.red(' ✖ Error instalando Vercel CLI: ' + e.message));
92
+ console.log(chalk.gray(' Instala manualmente: npm install -g vercel'));
93
+ process.exit(1);
94
+ }
95
+ }
96
+
97
+ // Verificar login
98
+ try {
99
+ execSync('vercel whoami', { stdio: 'pipe' });
100
+ } catch {
101
+ console.log(chalk.yellow(' No estás logueado en Vercel. Iniciando login...\n'));
102
+ spawnSync('vercel', ['login'], { cwd: path.join(root, 'frontend'), stdio: 'inherit' });
103
+ }
104
+
105
+ // Deploy
106
+ console.log(chalk.gray(' Ejecutando: vercel --prod\n'));
107
+ const r = spawnSync('vercel', ['--prod'], {
108
+ cwd: path.join(root, 'frontend'),
109
+ stdio: 'inherit',
110
+ });
111
+
112
+ if (r.status === 0) {
113
+ console.log(chalk.green('\n ✔ Frontend desplegado en Vercel'));
114
+ } else {
115
+ console.log(chalk.red('\n ✖ Error desplegando frontend'));
159
116
  }
160
- console.log(chalk.white('\n Desplegando frontend en Vercel...'));
161
- execSync('vercel --prod', { cwd: path.join(root, 'frontend'), stdio: 'inherit' });
162
117
  }
163
118
 
164
- if ((options.backend || !options.frontend)) {
119
+ // ── BACKEND RAILWAY ───────────────────────────────────────────────────────
120
+ if (deployBe && hasBe) {
121
+ console.log(chalk.white('\n ◆ Desplegando backend en Railway...\n'));
122
+
123
+ // Instalar Railway CLI si no está
165
124
  if (!isInstalled('railway')) {
166
- console.log(chalk.yellow('\n Instalando Railway CLI...'));
167
- execSync('npm install -g @railway/cli', { stdio: 'inherit' });
125
+ console.log(chalk.gray(' Instalando Railway CLI...'));
126
+ try {
127
+ execSync('npm install -g @railway/cli', { stdio: 'pipe' });
128
+ console.log(chalk.green(' ✔ Railway CLI instalado'));
129
+ } catch (e) {
130
+ console.log(chalk.red(' ✖ Error instalando Railway CLI: ' + e.message));
131
+ console.log(chalk.gray(' Instala manualmente: npm install -g @railway/cli'));
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ // Verificar login
137
+ try {
138
+ execSync('railway whoami', { stdio: 'pipe' });
139
+ } catch {
140
+ console.log(chalk.yellow(' No estás logueado en Railway. Iniciando login...\n'));
141
+ spawnSync('railway', ['login'], { stdio: 'inherit' });
142
+ }
143
+
144
+ // Verificar si hay proyecto Railway vinculado
145
+ const railwayJson = path.join(root, 'backend', '.railway');
146
+ const hasRailwayProject = await fse.pathExists(railwayJson);
147
+
148
+ if (!hasRailwayProject) {
149
+ console.log(chalk.yellow(' No hay proyecto Railway vinculado.'));
150
+ console.log(chalk.gray(' Vinculando proyecto (se abrirá selector)...\n'));
151
+ const link = spawnSync('railway', ['link'], {
152
+ cwd: path.join(root, 'backend'),
153
+ stdio: 'inherit',
154
+ });
155
+ if (link.status !== 0) {
156
+ console.log(chalk.gray('\n O crea uno nuevo con: railway init'));
157
+ console.log(chalk.gray(' Luego ejecuta: novatec deploy --backend'));
158
+ process.exit(1);
159
+ }
160
+ }
161
+
162
+ // Deploy
163
+ console.log(chalk.gray(' Ejecutando: railway up\n'));
164
+ const r = spawnSync('railway', ['up', '--detach'], {
165
+ cwd: path.join(root, 'backend'),
166
+ stdio: 'inherit',
167
+ });
168
+
169
+ if (r.status === 0) {
170
+ console.log(chalk.green('\n ✔ Backend desplegado en Railway'));
171
+ console.log(chalk.gray(' Ver logs: railway logs'));
172
+ console.log(chalk.gray(' Abrir: railway open'));
173
+ } else {
174
+ console.log(chalk.red('\n ✖ Error desplegando backend'));
175
+ console.log(chalk.gray(' Ver logs: railway logs'));
168
176
  }
169
- console.log(chalk.white('\n Desplegando backend en Railway...'));
170
- execSync('railway up', { cwd: path.join(root, 'backend'), stdio: 'inherit' });
171
177
  }
172
178
 
179
+ // ── RESUMEN ─────────────────────────────────────────────────────────────────
180
+ console.log();
181
+ console.log(boxen(
182
+ chalk.bold.white(' GUÍA RÁPIDA\n\n') +
183
+ chalk.white(' Vercel (Frontend)\n') +
184
+ chalk.gray(' 1. vercel login\n') +
185
+ chalk.gray(' 2. cd frontend && vercel --prod\n') +
186
+ chalk.gray(' 3. Variables de entorno → vercel.com/dashboard\n\n') +
187
+ chalk.white(' Railway (Backend)\n') +
188
+ chalk.gray(' 1. railway login\n') +
189
+ chalk.gray(' 2. cd backend && railway init\n') +
190
+ chalk.gray(' 3. railway up\n') +
191
+ chalk.gray(' 4. Variables de entorno → railway.app/dashboard'),
192
+ { padding: 1, margin: { left: 2 }, borderStyle: 'single', borderColor: 'gray', dimBorder: true }
193
+ ));
173
194
  console.log();
174
195
  }
@@ -1,9 +1,9 @@
1
1
  import path from 'path';
2
2
  import chalk from 'chalk';
3
3
  import fse from 'fs-extra';
4
+ import gradient from 'gradient-string';
4
5
  import { execSync } from 'child_process';
5
6
 
6
- // Comandos de scaffolding por framework
7
7
  const SCAFFOLD = {
8
8
  react: 'npm create vite@latest frontend -- --template react',
9
9
  vue: 'npm create vite@latest frontend -- --template vue',
@@ -17,11 +17,26 @@ const SCAFFOLD = {
17
17
  qwik: 'npm create qwik@latest frontend -- --no-install',
18
18
  };
19
19
 
20
+ const COLORS = {
21
+ react: ['#61DAFB', '#21D4FD'],
22
+ next: ['#ffffff', '#888888'],
23
+ vue: ['#42b883', '#35495e'],
24
+ nuxt: ['#00DC82', '#003543'],
25
+ astro: ['#FF5D01', '#BC52EE'],
26
+ svelte: ['#FF3E00', '#FF8C00'],
27
+ solid: ['#2C4F7C', '#446B9E'],
28
+ angular: ['#DD0031', '#C3002F'],
29
+ remix: ['#818CF8', '#6366F1'],
30
+ qwik: ['#18B6F6', '#AC7EF4'],
31
+ };
32
+
20
33
  export async function generateFrontend(config, projectPath) {
21
34
  const cmd = SCAFFOLD[config.frontend];
22
35
  if (!cmd) throw new Error(`Frontend no soportado: ${config.frontend}`);
23
36
 
24
- console.log(chalk.blue(`\n ⚛ Generando frontend (${chalk.bold(config.frontend)})...`));
37
+ const [c1, c2] = COLORS[config.frontend] || ['#ffffff', '#aaaaaa'];
38
+ const label = gradient(c1, c2)(` ◆ ${config.frontend.toUpperCase()} `);
39
+ console.log(chalk.blue(`\n ${label} ${chalk.gray('Generando frontend...')}`));
25
40
 
26
41
  try {
27
42
  execSync(cmd, { cwd: projectPath, stdio: 'pipe' });
package/lib/prompts.js CHANGED
@@ -1,54 +1,88 @@
1
1
  import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
+ import gradient from 'gradient-string';
3
4
  import fse from 'fs-extra';
4
5
  import path from 'path';
5
6
  import boxen from 'boxen';
6
7
 
8
+ // Colores reales de cada framework/DB
9
+ const g = {
10
+ react: gradient('#61DAFB', '#21D4FD'),
11
+ next: gradient('#ffffff', '#888888'),
12
+ vue: gradient('#42b883', '#35495e'),
13
+ nuxt: gradient('#00DC82', '#003543'),
14
+ astro: gradient('#FF5D01', '#BC52EE'),
15
+ svelte: gradient('#FF3E00', '#FF8C00'),
16
+ solid: gradient('#2C4F7C', '#446B9E'),
17
+ angular: gradient('#DD0031', '#C3002F'),
18
+ remix: gradient('#121212', '#818CF8'),
19
+ qwik: gradient('#18B6F6', '#AC7EF4'),
20
+ express: gradient('#ffffff', '#aaaaaa'),
21
+ nestjs: gradient('#E0234E', '#FF6B6B'),
22
+ fastify: gradient('#000000', '#888888'),
23
+ hono: gradient('#FF5733', '#FF8C00'),
24
+ fastapi: gradient('#009688', '#26A69A'),
25
+ django: gradient('#092E20', '#44B78B'),
26
+ flask: gradient('#ffffff', '#aaaaaa'),
27
+ spring: gradient('#6DB33F', '#77BC1F'),
28
+ deno: gradient('#ffffff', '#aaaaaa'),
29
+ gin: gradient('#00ACD7', '#5DC9E2'),
30
+ postgres: gradient('#336791', '#4A90D9'),
31
+ mysql: gradient('#4479A1', '#00758F'),
32
+ mongo: gradient('#47A248', '#00ED64'),
33
+ sqlite: gradient('#003B57', '#0F80CC'),
34
+ supabase: gradient('#3ECF8E', '#1C1C1C'),
35
+ };
36
+
37
+ function logo(key, text) {
38
+ return g[key] ? g[key](text) : chalk.white(text);
39
+ }
40
+
7
41
  export const FRONTEND_CHOICES = [
8
- { name: 'React — Vite + React 18', value: 'react' },
9
- { name: 'Next.js — SSR/SSG App Router', value: 'next' },
10
- { name: 'Vue 3 — Vite + Composition API', value: 'vue' },
11
- { name: 'Nuxt 3 — Vue SSR/SSG', value: 'nuxt' },
12
- { name: 'Astro — MPA ultra-rápido', value: 'astro' },
13
- { name: 'SvelteKit — Svelte SSR/SSG', value: 'svelte' },
14
- { name: 'SolidJS — Sin Virtual DOM', value: 'solid' },
15
- { name: 'Angular — Framework empresarial', value: 'angular' },
16
- { name: 'Remix — Full-stack React', value: 'remix' },
17
- { name: 'Qwik — Resumability', value: 'qwik' },
42
+ { name: logo('react', ' ◆ React ') + chalk.gray(' — Vite + React 18'), value: 'react' },
43
+ { name: logo('next', ' ▲ Next.js ') + chalk.gray(' — SSR/SSG App Router'), value: 'next' },
44
+ { name: logo('vue', ' ◈ Vue 3 ') + chalk.gray(' — Vite + Composition API'), value: 'vue' },
45
+ { name: logo('nuxt', ' ◈ Nuxt 3 ') + chalk.gray(' — Vue SSR/SSG'), value: 'nuxt' },
46
+ { name: logo('astro', ' ✦ Astro ') + chalk.gray(' — MPA ultra-rápido'), value: 'astro' },
47
+ { name: logo('svelte', ' ◆ SvelteKit ') + chalk.gray(' — Svelte SSR/SSG'), value: 'svelte' },
48
+ { name: logo('solid', ' ◆ SolidJS ') + chalk.gray(' — Sin Virtual DOM'), value: 'solid' },
49
+ { name: logo('angular', ' ◆ Angular ') + chalk.gray(' — Framework empresarial'), value: 'angular' },
50
+ { name: logo('remix', ' ◆ Remix ') + chalk.gray(' — Full-stack React'), value: 'remix' },
51
+ { name: logo('qwik', ' ⚡ Qwik ') + chalk.gray(' — Resumability'), value: 'qwik' },
18
52
  ];
19
53
 
20
54
  export const BACKEND_CHOICES = [
21
- { name: 'Express — Minimalista Node.js', value: 'express' },
22
- { name: 'NestJS — Modular TypeScript', value: 'nestjs' },
23
- { name: 'Fastify — Ultra-rápido Node', value: 'fastify' },
24
- { name: 'Hono — Edge-ready ligero', value: 'hono' },
25
- { name: 'FastAPI — Async Python', value: 'fastapi' },
26
- { name: 'Django — Baterías incluidas', value: 'django' },
27
- { name: 'Flask — Micro-framework Python', value: 'flask' },
28
- { name: 'Spring Boot — Empresarial Java', value: 'spring' },
29
- { name: 'Deno/Oak — TypeScript seguro', value: 'deno' },
30
- { name: 'Go/Gin — Rendimiento extremo', value: 'gin' },
55
+ { name: logo('express', ' ◆ Express ') + chalk.gray(' — Minimalista Node.js'), value: 'express' },
56
+ { name: logo('nestjs', ' ◆ NestJS ') + chalk.gray(' — Modular TypeScript'), value: 'nestjs' },
57
+ { name: logo('fastify', ' ⚡ Fastify ') + chalk.gray(' — Ultra-rápido Node'), value: 'fastify' },
58
+ { name: logo('hono', ' ◆ Hono ') + chalk.gray(' — Edge-ready ligero'), value: 'hono' },
59
+ { name: logo('fastapi', ' ◆ FastAPI ') + chalk.gray(' — Async Python'), value: 'fastapi' },
60
+ { name: logo('django', ' ◆ Django ') + chalk.gray(' — Baterías incluidas'), value: 'django' },
61
+ { name: logo('flask', ' ◆ Flask ') + chalk.gray(' — Micro-framework Python'), value: 'flask' },
62
+ { name: logo('spring', ' ◆ Spring Boot') + chalk.gray(' — Empresarial Java'), value: 'spring' },
63
+ { name: logo('deno', ' ◆ Deno/Oak ') + chalk.gray(' — TypeScript seguro'), value: 'deno' },
64
+ { name: logo('gin', ' ◆ Go/Gin ') + chalk.gray(' — Rendimiento extremo'), value: 'gin' },
31
65
  ];
32
66
 
33
67
  export const DB_CHOICES = [
34
- { name: 'PostgreSQL — Relacional robusto', value: 'postgres' },
35
- { name: 'MySQL — Relacional popular', value: 'mysql' },
36
- { name: 'MongoDB — NoSQL documentos', value: 'mongo' },
37
- { name: 'SQLite — Embebido ligero', value: 'sqlite' },
38
- { name: 'Supabase — BaaS con Auth incluida', value: 'supabase' },
39
- { name: 'Ninguna', value: 'none' },
68
+ { name: logo('postgres', ' ◆ PostgreSQL ') + chalk.gray(' — Relacional robusto'), value: 'postgres' },
69
+ { name: logo('mysql', ' ◆ MySQL ') + chalk.gray(' — Relacional popular'), value: 'mysql' },
70
+ { name: logo('mongo', ' ◆ MongoDB ') + chalk.gray(' — NoSQL documentos'), value: 'mongo' },
71
+ { name: logo('sqlite', ' ◆ SQLite ') + chalk.gray(' — Embebido ligero'), value: 'sqlite' },
72
+ { name: logo('supabase', ' ◆ Supabase ') + chalk.gray(' — BaaS con Auth incluida'), value: 'supabase' },
73
+ { name: chalk.gray('Ninguna'), value: 'none' },
40
74
  ];
41
75
 
42
76
  export const ARCH_CHOICES = [
43
- { name: 'MVC — Model-View-Controller (clásico)', value: 'mvc' },
44
- { name: 'Hexagonal — Ports & Adapters', value: 'hexagonal' },
45
- { name: 'Clean — Clean Architecture (capas)', value: 'clean' },
77
+ { name: chalk.white('MVC ') + chalk.gray(' — Model-View-Controller (clásico)'), value: 'mvc' },
78
+ { name: chalk.white('Hexagonal ') + chalk.gray(' — Ports & Adapters'), value: 'hexagonal' },
79
+ { name: chalk.white('Clean ') + chalk.gray(' — Clean Architecture (capas)'), value: 'clean' },
46
80
  ];
47
81
 
48
82
  export const PM_CHOICES = [
49
- { name: 'npm', value: 'npm' },
50
- { name: 'pnpm', value: 'pnpm' },
51
- { name: 'yarn', value: 'yarn' },
83
+ { name: chalk.white('npm'), value: 'npm' },
84
+ { name: chalk.white('pnpm'), value: 'pnpm' },
85
+ { name: chalk.white('yarn'), value: 'yarn' },
52
86
  ];
53
87
 
54
88
  export async function askProjectOptions(nameArg, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novatec-cli",
3
- "version": "3.0.6",
3
+ "version": "3.0.8",
4
4
  "description": "🚀 NOVATEC FULLSTACK CLI — Generador profesional de proyectos full stack | React, Next.js, Vue, Express, NestJS, FastAPI y más",
5
5
  "type": "module",
6
6
  "main": "./lib/create.js",