novatec-cli 1.0.2 → 3.0.0

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/lib/prompts.js CHANGED
@@ -1,348 +1,246 @@
1
1
  import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
+ import fse from 'fs-extra';
4
+ import path from 'path';
5
+ import boxen from 'boxen';
6
+
7
+ export const PRESETS = {
8
+ 'ecommerce': { frontend: 'next', backend: 'express', db: 'postgres', typescript: true, mode: 'business', architecture: 'mvc' },
9
+ 'reservas': { frontend: 'next', backend: 'express', db: 'postgres', typescript: true, mode: 'business', architecture: 'mvc' },
10
+ 'aula-virtual': { frontend: 'react', backend: 'nestjs', db: 'postgres', typescript: true, mode: 'business', architecture: 'clean' },
11
+ 'empresa-web': { frontend: 'next', backend: 'express', db: 'postgres', typescript: false, mode: 'business', architecture: 'mvc' },
12
+ 'react-express': { frontend: 'react', backend: 'express', db: 'postgres', typescript: false, mode: null, architecture: 'mvc' },
13
+ 'next-nestjs': { frontend: 'next', backend: 'nestjs', db: 'postgres', typescript: true, mode: null, architecture: 'clean' },
14
+ 'vue-fastapi': { frontend: 'vue', backend: 'fastapi', db: 'postgres', typescript: false, mode: null, architecture: 'mvc' },
15
+ 'astro-hono': { frontend: 'astro', backend: 'hono', db: 'sqlite', typescript: true, mode: null, architecture: 'mvc' },
16
+ };
3
17
 
4
- // ── FRONTEND FRAMEWORKS ───────────────────────────────────────────────────────
5
18
  export const FRONTEND_CHOICES = [
6
- { name: 'React (Vite) — SPA rápida con React 18', value: 'react' },
7
- { name: '🔺 Next.js React SSR/SSG/App Router', value: 'next' },
8
- { name: '💚 Vue 3 — Vite + Composition API', value: 'vue' },
9
- { name: '🔷 Nuxt 3 — Vue con SSR/SSG', value: 'nuxt' },
10
- { name: '🚀 Astro — MPA ultra-rápido', value: 'astro' },
11
- { name: '🔥 SvelteKit — Svelte con SSR/SSG', value: 'svelte' },
12
- { name: '💎 SolidJS Reactividad sin Virtual DOM', value: 'solid' },
13
- { name: '🅰 Angular — Framework empresarial', value: 'angular' },
14
- { name: '🌐 Remix — Full-stack React con loaders', value: 'remix' },
15
- { name: 'Qwik — Resumability, carga instantánea', value: 'qwik' },
19
+ { name: 'React Vite + React 18', value: 'react' },
20
+ { name: 'Next.js — SSR/SSG App Router', value: 'next' },
21
+ { name: 'Vue 3 — Vite + Composition API', value: 'vue' },
22
+ { name: 'Nuxt 3 — Vue SSR/SSG', value: 'nuxt' },
23
+ { name: 'Astro — MPA ultra-rápido', value: 'astro' },
24
+ { name: 'SvelteKit — Svelte SSR/SSG', value: 'svelte' },
25
+ { name: 'SolidJS Sin Virtual DOM', value: 'solid' },
26
+ { name: 'Angular — Framework empresarial', value: 'angular' },
27
+ { name: 'Remix — Full-stack React', value: 'remix' },
28
+ { name: 'Qwik — Resumability', value: 'qwik' },
16
29
  ];
17
30
 
18
- // ── BACKEND FRAMEWORKS ────────────────────────────────────────────────────────
19
31
  export const BACKEND_CHOICES = [
20
- { name: '🟢 Express — Minimalista (Node.js)', value: 'express' },
21
- { name: '🔵 NestJS Arquitectura modular', value: 'nestjs' },
22
- { name: 'Fastify El más rápido (Node)', value: 'fastify' },
23
- { name: '🔥 Hono — Edge-ready ligero', value: 'hono' },
24
- { name: '🐍 FastAPI — Async Python alto rend', value: 'fastapi' },
25
- { name: '🌶 Django — Baterías incluidas', value: 'django' },
26
- { name: '🍶 Flask — Micro-framework Python', value: 'flask' },
27
- { name: 'Spring Boot — Empresarial (Java)', value: 'spring' },
28
- { name: '🦕 Deno/Oak Seguro (TypeScript)', value: 'deno' },
29
- { name: '🐹 Go/Gin — Rendimiento extremo', value: 'gin' },
32
+ { name: 'Express — Minimalista Node.js', value: 'express' },
33
+ { name: 'NestJS Modular TypeScript', value: 'nestjs' },
34
+ { name: 'Fastify Ultra-rápido Node', value: 'fastify' },
35
+ { name: 'Hono — Edge-ready ligero', value: 'hono' },
36
+ { name: 'FastAPI — Async Python', value: 'fastapi' },
37
+ { name: 'Django — Baterías incluidas', value: 'django' },
38
+ { name: 'Flask — Micro-framework Python', value: 'flask' },
39
+ { name: 'Spring Boot — Empresarial Java', value: 'spring' },
40
+ { name: 'Deno/Oak TypeScript seguro', value: 'deno' },
41
+ { name: 'Go/Gin — Rendimiento extremo', value: 'gin' },
30
42
  ];
31
43
 
32
- // ── DEPENDENCIAS POR FRONTEND ─────────────────────────────────────────────────
33
- export const FRONTEND_DEPS = {
34
- react: [
35
- { name: 'axios HTTP client', value: 'axios' },
36
- { name: 'react-router-dom Enrutamiento SPA', value: 'react-router-dom' },
37
- { name: 'zustand Estado global ligero', value: 'zustand' },
38
- { name: 'jotai — Estado atómico', value: 'jotai' },
39
- { name: '@tanstack/react-query — Server state / fetching', value: '@tanstack/react-query' },
40
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
41
- { name: 'shadcn/ui — Componentes accesibles', value: '@shadcn/ui' },
42
- { name: 'framer-motion — Animaciones fluidas', value: 'framer-motion' },
43
- { name: 'react-hook-form — Formularios performantes', value: 'react-hook-form' },
44
- { name: 'zod — Validación de esquemas', value: 'zod' },
45
- { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
46
- { name: 'recharts — Gráficas y dashboards', value: 'recharts' },
47
- { name: 'i18next — Internacionalización', value: 'i18next' },
48
- { name: 'dotenv — Variables de entorno', value: 'dotenv' },
49
- ],
50
- next: [
51
- { name: 'axios — HTTP client', value: 'axios' },
52
- { name: 'next-auth — Autenticación completa', value: 'next-auth' },
53
- { name: 'zustand — Estado global ligero', value: 'zustand' },
54
- { name: 'tailwindcss — CSS utility-first (incluido)', value: 'tailwindcss' },
55
- { name: 'prisma — ORM moderno', value: 'prisma' },
56
- { name: '@tanstack/react-query — Server state', value: '@tanstack/react-query' },
57
- { name: 'zod — Validación de esquemas', value: 'zod' },
58
- { name: 'react-hook-form — Formularios performantes', value: 'react-hook-form' },
59
- { name: 'shadcn/ui — Componentes accesibles', value: '@shadcn/ui' },
60
- { name: 'stripe — Pagos online', value: 'stripe' },
61
- { name: 'resend — Emails transaccionales', value: 'resend' },
62
- { name: 'uploadthing — Subida de archivos', value: 'uploadthing' },
63
- { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
64
- ],
65
- vue: [
66
- { name: 'axios — HTTP client', value: 'axios' },
67
- { name: 'vue-router — Enrutamiento oficial', value: 'vue-router' },
68
- { name: 'pinia — Estado global oficial', value: 'pinia' },
69
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
70
- { name: 'vee-validate — Validación de formularios', value: 'vee-validate' },
71
- { name: 'zod — Validación de esquemas', value: 'zod' },
72
- { name: 'vueuse — Composables utilitarios', value: '@vueuse/core' },
73
- { name: 'naive-ui — Componentes UI', value: 'naive-ui' },
74
- { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
75
- { name: 'i18n — Internacionalización', value: 'vue-i18n' },
76
- ],
77
- nuxt: [
78
- { name: 'axios / ofetch — HTTP client', value: 'ofetch' },
79
- { name: '@pinia/nuxt — Estado global', value: '@pinia/nuxt' },
80
- { name: '@nuxtjs/tailwindcss — Tailwind integrado', value: '@nuxtjs/tailwindcss' },
81
- { name: '@nuxt/image — Optimización de imágenes', value: '@nuxt/image' },
82
- { name: 'nuxt-auth-utils — Autenticación', value: 'nuxt-auth-utils' },
83
- { name: 'zod — Validación', value: 'zod' },
84
- { name: '@vueuse/nuxt — Composables', value: '@vueuse/nuxt' },
85
- ],
86
- astro: [
87
- { name: 'tailwindcss — CSS utility-first', value: '@astrojs/tailwind' },
88
- { name: '@astrojs/react — Integración React', value: '@astrojs/react' },
89
- { name: '@astrojs/vue — Integración Vue', value: '@astrojs/vue' },
90
- { name: '@astrojs/mdx — Soporte MDX', value: '@astrojs/mdx' },
91
- { name: '@astrojs/image — Optimización imágenes', value: '@astrojs/image' },
92
- { name: 'nanostores — Estado global ligero', value: 'nanostores' },
93
- ],
94
- svelte: [
95
- { name: 'axios — HTTP client', value: 'axios' },
96
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
97
- { name: 'zod — Validación', value: 'zod' },
98
- { name: 'svelte-query — Server state', value: '@tanstack/svelte-query' },
99
- { name: 'skeleton — UI components', value: '@skeletonlabs/skeleton' },
100
- { name: 'lucia — Autenticación', value: 'lucia' },
101
- ],
102
- solid: [
103
- { name: 'axios — HTTP client', value: 'axios' },
104
- { name: '@solidjs/router — Enrutamiento', value: '@solidjs/router' },
105
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
106
- { name: 'solid-query — Server state', value: '@tanstack/solid-query' },
107
- { name: 'zod — Validación', value: 'zod' },
108
- ],
109
- angular: [
110
- { name: 'axios — HTTP client', value: 'axios' },
111
- { name: '@angular/material — Material Design UI', value: '@angular/material' },
112
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
113
- { name: 'ngrx/store — Estado global Redux-like', value: '@ngrx/store' },
114
- { name: 'zod — Validación', value: 'zod' },
115
- { name: 'date-fns — Manejo de fechas', value: 'date-fns' },
116
- ],
117
- remix: [
118
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
119
- { name: 'zod — Validación', value: 'zod' },
120
- { name: 'prisma — ORM moderno', value: 'prisma' },
121
- { name: 'react-hook-form — Formularios', value: 'react-hook-form' },
122
- { name: 'date-fns — Fechas', value: 'date-fns' },
123
- ],
124
- qwik: [
125
- { name: 'tailwindcss — CSS utility-first', value: 'tailwindcss' },
126
- { name: 'zod — Validación', value: 'zod' },
127
- { name: 'axios — HTTP client', value: 'axios' },
128
- ],
129
- };
44
+ export const DB_CHOICES = [
45
+ { name: 'PostgreSQL — Relacional robusto', value: 'postgres' },
46
+ { name: 'MySQL — Relacional popular', value: 'mysql' },
47
+ { name: 'MongoDB NoSQL documentos', value: 'mongo' },
48
+ { name: 'SQLite Embebido ligero', value: 'sqlite' },
49
+ { name: 'Supabase BaaS con Auth incluida', value: 'supabase' },
50
+ { name: 'Ninguna', value: 'none' },
51
+ ];
130
52
 
131
- // ── DEPENDENCIAS POR BACKEND ──────────────────────────────────────────────────
132
- export const BACKEND_DEPS = {
133
- express: [
134
- { name: 'cors Habilitar CORS', value: 'cors' },
135
- { name: 'dotenv — Variables de entorno', value: 'dotenv' },
136
- { name: 'mongoose — ODM para MongoDB', value: 'mongoose' },
137
- { name: 'prisma — ORM moderno (SQL)', value: 'prisma' },
138
- { name: 'jsonwebtoken — Autenticación JWT', value: 'jsonwebtoken' },
139
- { name: 'bcryptjs — Hash de contraseñas', value: 'bcryptjs' },
140
- { name: 'express-validator — Validación de inputs', value: 'express-validator' },
141
- { name: 'multer — Subida de archivos', value: 'multer' },
142
- { name: 'helmet — Seguridad HTTP headers', value: 'helmet' },
143
- { name: 'morgan — Logger HTTP', value: 'morgan' },
144
- { name: 'rate-limiter — Limitar peticiones', value: 'express-rate-limit' },
145
- { name: 'swagger-ui — Documentación API', value: 'swagger-ui-express' },
146
- { name: 'socket.io — WebSockets en tiempo real', value: 'socket.io' },
147
- { name: 'bull — Colas de trabajo', value: 'bull' },
148
- { name: 'nodemailer — Envío de emails', value: 'nodemailer' },
149
- { name: 'nodemon — Auto-reload en dev', value: 'nodemon' },
150
- ],
151
- nestjs: [
152
- { name: 'prisma — ORM moderno (SQL)', value: 'prisma' },
153
- { name: '@nestjs/jwt — Módulo JWT oficial', value: '@nestjs/jwt' },
154
- { name: '@nestjs/config — Variables de entorno', value: '@nestjs/config' },
155
- { name: 'class-validator — Validación de DTOs', value: 'class-validator' },
156
- { name: 'class-transformer — Transformación de objetos', value: 'class-transformer' },
157
- { name: 'mongoose — ODM para MongoDB', value: 'mongoose' },
158
- { name: '@nestjs/swagger — Documentación API', value: '@nestjs/swagger' },
159
- { name: '@nestjs/websockets — WebSockets', value: '@nestjs/websockets' },
160
- { name: '@nestjs/bull — Colas de trabajo', value: '@nestjs/bull' },
161
- { name: '@nestjs/mailer — Envío de emails', value: '@nestjs-modules/mailer' },
162
- { name: 'passport — Autenticación estrategias', value: '@nestjs/passport' },
163
- ],
164
- fastify: [
165
- { name: '@fastify/cors — CORS', value: '@fastify/cors' },
166
- { name: '@fastify/jwt — JWT', value: '@fastify/jwt' },
167
- { name: '@fastify/swagger — Documentación API', value: '@fastify/swagger' },
168
- { name: '@fastify/multipart — Subida de archivos', value: '@fastify/multipart' },
169
- { name: '@fastify/rate-limit — Rate limiting', value: '@fastify/rate-limit' },
170
- { name: 'prisma — ORM moderno', value: 'prisma' },
171
- { name: 'mongoose — ODM MongoDB', value: 'mongoose' },
172
- { name: 'dotenv — Variables de entorno', value: 'dotenv' },
173
- { name: 'nodemon — Auto-reload', value: 'nodemon' },
174
- ],
175
- hono: [
176
- { name: 'hono/jwt — JWT middleware', value: '@hono/jwt' },
177
- { name: 'zod — Validación', value: 'zod' },
178
- { name: 'drizzle-orm — ORM ligero', value: 'drizzle-orm' },
179
- { name: 'prisma — ORM moderno', value: 'prisma' },
180
- { name: 'dotenv — Variables de entorno', value: 'dotenv' },
181
- ],
182
- fastapi: [
183
- { name: 'sqlalchemy — ORM para Python', value: 'sqlalchemy' },
184
- { name: 'alembic — Migraciones de BD', value: 'alembic' },
185
- { name: 'python-jose — JWT para Python', value: 'python-jose[cryptography]' },
186
- { name: 'passlib — Hash de contraseñas', value: 'passlib[bcrypt]' },
187
- { name: 'python-dotenv — Variables de entorno', value: 'python-dotenv' },
188
- { name: 'httpx — HTTP client async', value: 'httpx' },
189
- { name: 'celery — Colas de tareas', value: 'celery' },
190
- { name: 'redis — Cache / broker', value: 'redis' },
191
- { name: 'pydantic-settings — Config tipada', value: 'pydantic-settings' },
192
- { name: 'tortoise-orm — ORM async', value: 'tortoise-orm' },
193
- { name: 'aiofiles — Archivos async', value: 'aiofiles' },
194
- ],
195
- django: [
196
- { name: 'djangorestframework — API REST', value: 'djangorestframework' },
197
- { name: 'django-cors-headers — CORS', value: 'django-cors-headers' },
198
- { name: 'djangorestframework-simplejwt — JWT', value: 'djangorestframework-simplejwt' },
199
- { name: 'celery — Colas de tareas', value: 'celery' },
200
- { name: 'redis — Cache / broker', value: 'redis' },
201
- { name: 'pillow — Procesamiento de imágenes', value: 'Pillow' },
202
- { name: 'python-dotenv — Variables de entorno', value: 'python-dotenv' },
203
- { name: 'psycopg2 — PostgreSQL driver', value: 'psycopg2-binary' },
204
- { name: 'drf-spectacular — Documentación OpenAPI', value: 'drf-spectacular' },
205
- ],
206
- flask: [
207
- { name: 'flask-restful — API REST', value: 'flask-restful' },
208
- { name: 'flask-sqlalchemy — ORM integrado', value: 'flask-sqlalchemy' },
209
- { name: 'flask-jwt-extended — JWT', value: 'flask-jwt-extended' },
210
- { name: 'flask-cors — CORS', value: 'flask-cors' },
211
- { name: 'flask-migrate — Migraciones', value: 'flask-migrate' },
212
- { name: 'marshmallow — Serialización', value: 'marshmallow' },
213
- { name: 'python-dotenv — Variables de entorno', value: 'python-dotenv' },
214
- { name: 'celery — Colas de tareas', value: 'celery' },
215
- ],
216
- spring: [
217
- { name: 'spring-data-jpa — ORM / JPA', value: 'spring-data-jpa' },
218
- { name: 'spring-security — Seguridad y auth', value: 'spring-security' },
219
- { name: 'spring-web — REST controllers', value: 'spring-web' },
220
- { name: 'lombok — Reduce boilerplate', value: 'lombok' },
221
- { name: 'postgresql-driver — Driver PostgreSQL', value: 'postgresql' },
222
- { name: 'h2-database — BD en memoria (dev)', value: 'h2' },
223
- ],
224
- deno: [
225
- { name: 'oak/cors — CORS middleware', value: 'cors' },
226
- { name: 'djwt — JWT para Deno', value: 'djwt' },
227
- { name: 'zod — Validación', value: 'zod' },
228
- ],
229
- gin: [
230
- { name: 'gorm — ORM para Go', value: 'gorm' },
231
- { name: 'jwt-go — JWT', value: 'jwt-go' },
232
- { name: 'godotenv — Variables de entorno', value: 'godotenv' },
233
- { name: 'validator — Validación de structs', value: 'validator' },
234
- ],
235
- };
53
+ export const ARCH_CHOICES = [
54
+ { name: 'MVC — Model-View-Controller (clásico)', value: 'mvc' },
55
+ { name: 'Hexagonal — Ports & Adapters', value: 'hexagonal' },
56
+ { name: 'Clean Clean Architecture (capas)', value: 'clean' },
57
+ ];
58
+
59
+ export const PM_CHOICES = [
60
+ { name: 'npm', value: 'npm' },
61
+ { name: 'pnpm', value: 'pnpm' },
62
+ { name: 'yarn', value: 'yarn' },
63
+ ];
236
64
 
237
- // ── EXTRAS GLOBALES ───────────────────────────────────────────────────────────
238
65
  export const EXTRAS_CHOICES = [
239
- { name: '🔀 Git init automático', value: 'git', checked: true },
240
- { name: '🔍 ESLint + Prettier (linting/formato)', value: 'eslint' },
241
- { name: '🐶 Husky + lint-staged (pre-commit hooks)', value: 'husky' },
242
- { name: '🧪 Vitest / Jest (testing)', value: 'testing' },
243
- { name: '🐳 docker-compose.yml', value: 'docker' },
244
- { name: '🔒 Archivos .env.example', value: 'env' },
245
- { name: '📋 .editorconfig', value: 'editorconfig' },
246
- { name: '🚀 GitHub Actions CI/CD básico', value: 'ci' },
66
+ { name: 'Git init automático', value: 'git', checked: true },
67
+ { name: 'ESLint + Prettier', value: 'eslint', checked: false },
68
+ { name: 'Husky + lint-staged', value: 'husky', checked: false },
69
+ { name: 'Vitest / Jest', value: 'testing', checked: false },
70
+ { name: 'Docker profesional', value: 'docker', checked: false },
71
+ { name: 'Archivos .env.example', value: 'env', checked: true },
72
+ { name: '.editorconfig', value: 'editorconfig', checked: false },
73
+ { name: 'GitHub Actions CI/CD', value: 'ci', checked: false },
74
+ { name: 'Auth JWT completa', value: 'auth', checked: false },
75
+ { name: 'Tailwind CSS auto', value: 'tailwind', checked: false },
76
+ { name: 'Swagger / OpenAPI', value: 'swagger', checked: false },
77
+ { name: 'Seguridad (helmet+cors+ratelimit)', value: 'security', checked: false },
78
+ { name: 'Sistema de logs (winston)', value: 'logs', checked: false },
79
+ { name: 'Stripe (pagos)', value: 'stripe', checked: false },
80
+ { name: 'Notificaciones toast (frontend)', value: 'notifications',checked: false },
81
+ { name: 'SEO base (meta+OG+sitemap)', value: 'seo', checked: false },
82
+ { name: 'Dark mode toggle', value: 'darkmode', checked: false },
83
+ { name: 'WhatsApp botón de contacto', value: 'whatsapp', checked: false },
247
84
  ];
248
85
 
249
- // ── FLUJO PRINCIPAL ───────────────────────────────────────────────────────────
250
86
  export async function askProjectOptions(nameArg, options) {
251
- const answers = {};
87
+ // Preset
88
+ if (options.preset) {
89
+ const preset = PRESETS[options.preset];
90
+ if (!preset) {
91
+ console.log(chalk.red(`\n Preset "${options.preset}" no existe.`));
92
+ console.log(chalk.gray(' Disponibles: ' + Object.keys(PRESETS).join(', ') + '\n'));
93
+ process.exit(1);
94
+ }
95
+ const name = nameArg || options.preset + '-app';
96
+ console.log(chalk.gray(`\n Preset: ${chalk.white(options.preset)}\n`));
97
+ return { name, ...preset, pm: 'npm', frontendDeps: [], backendDeps: [], extras: ['git', 'env', 'security', 'swagger'] };
98
+ }
99
+
100
+ // --yes mode
101
+ if (options.yes) {
102
+ const name = nameArg || 'my-app';
103
+ await checkFolder(name);
104
+ return {
105
+ name,
106
+ frontend: options.frontend || 'react',
107
+ backend: options.backend || 'express',
108
+ db: options.db || 'postgres',
109
+ mode: options.mode || null,
110
+ architecture: options.architecture || 'mvc',
111
+ typescript: options.typescript || false,
112
+ pm: options.packageManager || 'npm',
113
+ install: options.install || false,
114
+ git: options.git || false,
115
+ frontendDeps: [],
116
+ backendDeps: [],
117
+ extras: ['git', 'env'],
118
+ };
119
+ }
120
+
121
+ const a = {};
252
122
 
123
+ // Nombre
253
124
  if (!nameArg) {
254
125
  const { name } = await inquirer.prompt([{
255
- type: 'input',
256
- name: 'name',
257
- message: chalk.cyan('📦 Nombre del proyecto:'),
126
+ type: 'input', name: 'name',
127
+ message: chalk.white('Nombre del proyecto:'),
258
128
  default: 'my-app',
259
- validate: v => /^[a-z0-9-_]+$/i.test(v.trim()) || 'Solo letras, números, guiones y guiones bajos',
129
+ validate: v => /^[a-z0-9-_]+$/i.test(v.trim()) && v.trim().length >= 2 || 'Solo letras, números, guiones. Mínimo 2 chars.',
260
130
  }]);
261
- answers.name = name.trim();
262
- } else {
263
- answers.name = nameArg;
264
- }
131
+ a.name = name.trim();
132
+ } else { a.name = nameArg; }
133
+
134
+ await checkFolder(a.name);
135
+
136
+ // Modo business
137
+ const { mode } = await inquirer.prompt([{
138
+ type: 'list', name: 'mode',
139
+ message: chalk.white('Modo de proyecto:'),
140
+ choices: [
141
+ { name: 'Estándar — Solo estructura base', value: null },
142
+ { name: 'Business — Landing + Auth + Dashboard + CRUD (sistema completo)', value: 'business' },
143
+ ],
144
+ default: options.mode || null,
145
+ }]);
146
+ a.mode = mode;
265
147
 
148
+ // TypeScript
149
+ const { typescript } = await inquirer.prompt([{
150
+ type: 'confirm', name: 'typescript',
151
+ message: chalk.white('¿Usar TypeScript?'),
152
+ default: options.typescript || false,
153
+ }]);
154
+ a.typescript = typescript;
155
+
156
+ // Frontend
266
157
  if (!options.frontend) {
267
- const { frontend } = await inquirer.prompt([{
268
- type: 'list',
269
- name: 'frontend',
270
- message: chalk.cyan('⚛ Elige el framework de Frontend:'),
271
- choices: FRONTEND_CHOICES,
272
- pageSize: 12,
273
- }]);
274
- answers.frontend = frontend;
275
- } else {
276
- answers.frontend = options.frontend;
277
- }
158
+ const { frontend } = await inquirer.prompt([{ type: 'list', name: 'frontend', message: chalk.white('Framework Frontend:'), choices: FRONTEND_CHOICES, pageSize: 12 }]);
159
+ a.frontend = frontend;
160
+ } else { a.frontend = options.frontend; }
278
161
 
162
+ // Backend
279
163
  if (!options.backend) {
280
- const { backend } = await inquirer.prompt([{
281
- type: 'list',
282
- name: 'backend',
283
- message: chalk.cyan('🔧 Elige el framework de Backend:'),
284
- choices: BACKEND_CHOICES,
285
- pageSize: 12,
286
- }]);
287
- answers.backend = backend;
288
- } else {
289
- answers.backend = options.backend;
164
+ const { backend } = await inquirer.prompt([{ type: 'list', name: 'backend', message: chalk.white('Framework Backend:'), choices: BACKEND_CHOICES, pageSize: 12 }]);
165
+ a.backend = backend;
166
+ } else { a.backend = options.backend; }
167
+
168
+ // DB
169
+ if (!options.db) {
170
+ const { db } = await inquirer.prompt([{ type: 'list', name: 'db', message: chalk.white('Base de datos:'), choices: DB_CHOICES, pageSize: 7 }]);
171
+ a.db = db;
172
+ } else { a.db = options.db; }
173
+
174
+ // Supabase config
175
+ if (a.db === 'supabase') {
176
+ console.log(chalk.gray('\n Configura Supabase (puedes dejarlo vacío y editar .env después)\n'));
177
+ const { supabaseUrl, supabaseKey } = await inquirer.prompt([
178
+ { type: 'input', name: 'supabaseUrl', message: chalk.white(' Supabase URL:'), default: 'https://your-project.supabase.co' },
179
+ { type: 'input', name: 'supabaseKey', message: chalk.white(' Supabase Anon Key:'), default: 'your-anon-key' },
180
+ ]);
181
+ a.supabaseUrl = supabaseUrl;
182
+ a.supabaseKey = supabaseKey;
290
183
  }
291
184
 
292
- // Dependencias contextuales frontend
293
- console.log(chalk.gray(`\n 💡 Dependencias recomendadas para ${chalk.bold(answers.frontend)}:`));
294
- const { frontendDeps } = await inquirer.prompt([{
295
- type: 'checkbox',
296
- name: 'frontendDeps',
297
- message: chalk.cyan('📦 Dependencias para el Frontend (espacio = seleccionar):'),
298
- choices: FRONTEND_DEPS[answers.frontend] || [],
299
- pageSize: 14,
300
- }]);
185
+ // Arquitectura
186
+ if (!options.architecture) {
187
+ const { architecture } = await inquirer.prompt([{ type: 'list', name: 'architecture', message: chalk.white('Arquitectura del backend:'), choices: ARCH_CHOICES }]);
188
+ a.architecture = architecture;
189
+ } else { a.architecture = options.architecture; }
301
190
 
302
- // Dependencias contextuales backend
303
- console.log(chalk.gray(`\n 💡 Dependencias recomendadas para ${chalk.bold(answers.backend)}:`));
304
- const { backendDeps } = await inquirer.prompt([{
305
- type: 'checkbox',
306
- name: 'backendDeps',
307
- message: chalk.cyan('📦 Dependencias para el Backend (espacio = seleccionar):'),
308
- choices: BACKEND_DEPS[answers.backend] || [],
309
- pageSize: 14,
310
- }]);
191
+ // Package manager
192
+ if (!options.packageManager) {
193
+ const { pm } = await inquirer.prompt([{ type: 'list', name: 'pm', message: chalk.white('Package manager:'), choices: PM_CHOICES }]);
194
+ a.pm = pm;
195
+ } else { a.pm = options.packageManager; }
311
196
 
312
197
  // Extras
313
198
  const { extras } = await inquirer.prompt([{
314
- type: 'checkbox',
315
- name: 'extras',
316
- message: chalk.cyan('⚙ Opciones adicionales del proyecto:'),
317
- choices: EXTRAS_CHOICES,
318
- pageSize: 10,
199
+ type: 'checkbox', name: 'extras',
200
+ message: chalk.white('Extras del proyecto:'),
201
+ choices: EXTRAS_CHOICES, pageSize: 14,
319
202
  }]);
320
203
 
321
- // Resumen + confirmación
204
+ // Auto-install
205
+ const { install } = await inquirer.prompt([{
206
+ type: 'confirm', name: 'install',
207
+ message: chalk.white('¿Instalar dependencias automáticamente?'),
208
+ default: options.install || false,
209
+ }]);
210
+ a.install = install;
211
+
212
+ // Resumen
322
213
  console.log();
323
- console.log(chalk.cyan(' ╔══════════════════════════════════════════════════════╗'));
324
- console.log(chalk.cyan(' ║') + chalk.bold.white(' 📋 RESUMEN DEL PROYECTO ') + chalk.cyan('║'));
325
- console.log(chalk.cyan(' ╠══════════════════════════════════════════════════════╣'));
326
- console.log(chalk.cyan(' ') + chalk.white(` 📦 Nombre : `) + chalk.cyan.bold(answers.name.padEnd(36)) + chalk.cyan('║'));
327
- console.log(chalk.cyan(' ') + chalk.white(` ⚛ Frontend : `) + chalk.cyan.bold(answers.frontend.padEnd(36)) + chalk.cyan(''));
328
- console.log(chalk.cyan(' ') + chalk.white(` 🔧 Backend : `) + chalk.cyan.bold(answers.backend.padEnd(36)) + chalk.cyan(''));
329
- console.log(chalk.cyan(' ') + chalk.white(` 📦 FE deps : `) + chalk.gray((frontendDeps.length ? frontendDeps.length + ' seleccionadas' : 'ninguna').padEnd(36)) + chalk.cyan('║'));
330
- console.log(chalk.cyan(' ') + chalk.white(` 📦 BE deps : `) + chalk.gray((backendDeps.length ? backendDeps.length + ' seleccionadas' : 'ninguna').padEnd(36)) + chalk.cyan('║'));
331
- console.log(chalk.cyan(' ') + chalk.white(` ⚙ Extras : `) + chalk.gray((extras.length ? extras.join(', ') : 'ninguno').substring(0, 36).padEnd(36)) + chalk.cyan('║'));
332
- console.log(chalk.cyan(' ╚══════════════════════════════════════════════════════╝'));
214
+ console.log(boxen(
215
+ chalk.bold.white(' RESUMEN\n\n') +
216
+ chalk.gray(' Nombre │ ') + chalk.white(a.name) + '\n' +
217
+ chalk.gray(' Modo │ ') + chalk.white(a.mode || 'estándar') + '\n' +
218
+ chalk.gray(' Frontend │ ') + chalk.white(a.frontend) + (a.typescript ? chalk.gray(' + TS') : '') + '\n' +
219
+ chalk.gray(' Backend │ ') + chalk.white(a.backend) + '\n' +
220
+ chalk.gray(' Base datos │ ') + chalk.white(a.db) + '\n' +
221
+ chalk.gray(' Arquitectura │ ') + chalk.white(a.architecture) + '\n' +
222
+ chalk.gray(' PM │ ') + chalk.white(a.pm) + '\n' +
223
+ chalk.gray(' Instalar │ ') + chalk.white(a.install ? '' : 'no') + '\n' +
224
+ chalk.gray(' Extras │ ') + chalk.white(extras.length ? extras.join(', ') : 'ninguno'),
225
+ { padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
226
+ ));
333
227
  console.log();
334
228
 
335
- const { confirm } = await inquirer.prompt([{
336
- type: 'confirm',
337
- name: 'confirm',
338
- message: chalk.yellow('¿Crear el proyecto con esta configuración?'),
339
- default: true,
340
- }]);
229
+ const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: chalk.white('¿Crear el proyecto?'), default: true }]);
230
+ if (!confirm) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
341
231
 
342
- if (!confirm) {
343
- console.log(chalk.yellow('\n Operación cancelada.\n'));
344
- process.exit(0);
345
- }
232
+ return { ...a, frontendDeps: [], backendDeps: [], extras };
233
+ }
346
234
 
347
- return { name: answers.name, frontend: answers.frontend, backend: answers.backend, frontendDeps, backendDeps, extras };
235
+ async function checkFolder(name) {
236
+ const p = path.resolve(process.cwd(), name);
237
+ if (await fse.pathExists(p)) {
238
+ const { overwrite } = await inquirer.prompt([{
239
+ type: 'confirm', name: 'overwrite',
240
+ message: chalk.yellow(`La carpeta "${name}" ya existe. ¿Sobreescribir?`),
241
+ default: false,
242
+ }]);
243
+ if (!overwrite) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
244
+ await fse.remove(p);
245
+ }
348
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novatec-cli",
3
- "version": "1.0.2",
3
+ "version": "3.0.0",
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",