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/bin/index.js +140 -118
- package/lib/add.js +122 -0
- package/lib/create.js +248 -237
- package/lib/doctor.js +167 -0
- package/lib/generators/business.js +262 -0
- package/lib/generators/deploy.js +166 -0
- package/lib/generators/docker.js +97 -0
- package/lib/generators/security.js +132 -0
- package/lib/prompts.js +208 -310
- package/package.json +1 -1
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: '
|
|
7
|
-
{ name: '
|
|
8
|
-
{ name: '
|
|
9
|
-
{ name: '
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
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: '
|
|
21
|
-
{ name: '
|
|
22
|
-
{ name: '
|
|
23
|
-
{ name: '
|
|
24
|
-
{ name: '
|
|
25
|
-
{ name: '
|
|
26
|
-
{ name: '
|
|
27
|
-
{ name: '
|
|
28
|
-
{ name: '
|
|
29
|
-
{ name: '
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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: '
|
|
240
|
-
{ name: '
|
|
241
|
-
{ name: '
|
|
242
|
-
{ name: '
|
|
243
|
-
{ name: '
|
|
244
|
-
{ name: '
|
|
245
|
-
{ name: '
|
|
246
|
-
{ name: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
262
|
-
} else {
|
|
263
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}]);
|
|
287
|
-
|
|
288
|
-
} else {
|
|
289
|
-
|
|
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
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
316
|
-
|
|
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
|
-
//
|
|
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(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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 ? 'sí' : '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
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
process.exit(0);
|
|
345
|
-
}
|
|
232
|
+
return { ...a, frontendDeps: [], backendDeps: [], extras };
|
|
233
|
+
}
|
|
346
234
|
|
|
347
|
-
|
|
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