novatec-cli 1.0.2 → 2.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 +145 -63
- package/lib/create.js +312 -211
- package/lib/prompts.js +187 -260
- package/package.json +1 -1
package/lib/prompts.js
CHANGED
|
@@ -1,273 +1,166 @@
|
|
|
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
|
+
// ── PRESETS ───────────────────────────────────────────────────────────────────
|
|
8
|
+
export const PRESETS = {
|
|
9
|
+
'react-express': { frontend: 'react', backend: 'express', db: 'postgres', typescript: false },
|
|
10
|
+
'next-nestjs': { frontend: 'next', backend: 'nestjs', db: 'postgres', typescript: true },
|
|
11
|
+
'vue-fastapi': { frontend: 'vue', backend: 'fastapi', db: 'postgres', typescript: false },
|
|
12
|
+
'nuxt-express': { frontend: 'nuxt', backend: 'express', db: 'mongo', typescript: false },
|
|
13
|
+
'astro-hono': { frontend: 'astro', backend: 'hono', db: 'sqlite', typescript: true },
|
|
14
|
+
};
|
|
3
15
|
|
|
4
16
|
// ── FRONTEND FRAMEWORKS ───────────────────────────────────────────────────────
|
|
5
17
|
export const FRONTEND_CHOICES = [
|
|
6
|
-
{ name: '
|
|
7
|
-
{ name: '
|
|
8
|
-
{ name: '
|
|
9
|
-
{ name: '
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
18
|
+
{ name: 'React — Vite + React 18', value: 'react' },
|
|
19
|
+
{ name: 'Next.js — SSR/SSG App Router', value: 'next' },
|
|
20
|
+
{ name: 'Vue 3 — Vite + Composition API', value: 'vue' },
|
|
21
|
+
{ name: 'Nuxt 3 — Vue con SSR/SSG', value: 'nuxt' },
|
|
22
|
+
{ name: 'Astro — MPA ultra-rápido', value: 'astro' },
|
|
23
|
+
{ name: 'SvelteKit — Svelte con SSR/SSG', value: 'svelte' },
|
|
24
|
+
{ name: 'SolidJS — Sin Virtual DOM', value: 'solid' },
|
|
25
|
+
{ name: 'Angular — Framework empresarial', value: 'angular' },
|
|
26
|
+
{ name: 'Remix — Full-stack React', value: 'remix' },
|
|
27
|
+
{ name: 'Qwik — Resumability', value: 'qwik' },
|
|
16
28
|
];
|
|
17
29
|
|
|
18
30
|
// ── 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 — El más 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' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// ── BASES DE DATOS ────────────────────────────────────────────────────────────
|
|
45
|
+
export const DB_CHOICES = [
|
|
46
|
+
{ name: 'PostgreSQL — Relacional robusto', value: 'postgres' },
|
|
47
|
+
{ name: 'MySQL — Relacional popular', value: 'mysql' },
|
|
48
|
+
{ name: 'MongoDB — NoSQL documentos', value: 'mongo' },
|
|
49
|
+
{ name: 'SQLite — Embebido ligero', value: 'sqlite' },
|
|
50
|
+
{ name: 'Ninguna — Sin base de datos', value: 'none' },
|
|
30
51
|
];
|
|
31
52
|
|
|
32
|
-
// ── DEPENDENCIAS
|
|
53
|
+
// ── DEPENDENCIAS FRONTEND ─────────────────────────────────────────────────────
|
|
33
54
|
export const FRONTEND_DEPS = {
|
|
34
|
-
react:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
],
|
|
55
|
+
react: ['axios','react-router-dom','zustand','@tanstack/react-query','tailwindcss','framer-motion','react-hook-form','zod','date-fns','recharts'],
|
|
56
|
+
next: ['axios','next-auth','zustand','tailwindcss','prisma','@tanstack/react-query','zod','react-hook-form','stripe','date-fns'],
|
|
57
|
+
vue: ['axios','vue-router','pinia','tailwindcss','vee-validate','zod','@vueuse/core','date-fns','vue-i18n'],
|
|
58
|
+
nuxt: ['ofetch','@pinia/nuxt','@nuxtjs/tailwindcss','@nuxt/image','nuxt-auth-utils','zod','@vueuse/nuxt'],
|
|
59
|
+
astro: ['@astrojs/tailwind','@astrojs/react','@astrojs/mdx','nanostores'],
|
|
60
|
+
svelte: ['axios','tailwindcss','zod','@tanstack/svelte-query','@skeletonlabs/skeleton'],
|
|
61
|
+
solid: ['axios','@solidjs/router','tailwindcss','@tanstack/solid-query','zod'],
|
|
62
|
+
angular: ['axios','@angular/material','tailwindcss','@ngrx/store','zod','date-fns'],
|
|
63
|
+
remix: ['tailwindcss','zod','prisma','react-hook-form','date-fns'],
|
|
64
|
+
qwik: ['tailwindcss','zod','axios'],
|
|
129
65
|
};
|
|
130
66
|
|
|
131
|
-
// ── DEPENDENCIAS
|
|
67
|
+
// ── DEPENDENCIAS BACKEND ──────────────────────────────────────────────────────
|
|
132
68
|
export const BACKEND_DEPS = {
|
|
133
|
-
express:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
],
|
|
69
|
+
express: ['cors','dotenv','mongoose','prisma','jsonwebtoken','bcryptjs','express-validator','multer','helmet','morgan','express-rate-limit','swagger-ui-express','socket.io','nodemailer','nodemon'],
|
|
70
|
+
nestjs: ['prisma','@nestjs/jwt','@nestjs/config','class-validator','class-transformer','mongoose','@nestjs/swagger','@nestjs/websockets','@nestjs/bull','@nestjs/passport'],
|
|
71
|
+
fastify: ['@fastify/cors','@fastify/jwt','@fastify/swagger','@fastify/multipart','@fastify/rate-limit','prisma','mongoose','dotenv','nodemon'],
|
|
72
|
+
hono: ['zod','drizzle-orm','prisma','dotenv'],
|
|
73
|
+
fastapi: ['sqlalchemy','alembic','python-jose[cryptography]','passlib[bcrypt]','python-dotenv','httpx','celery','redis','pydantic-settings'],
|
|
74
|
+
django: ['djangorestframework','django-cors-headers','djangorestframework-simplejwt','celery','redis','Pillow','python-dotenv','psycopg2-binary','drf-spectacular'],
|
|
75
|
+
flask: ['flask-restful','flask-sqlalchemy','flask-jwt-extended','flask-cors','flask-migrate','marshmallow','python-dotenv','celery'],
|
|
76
|
+
spring: ['spring-data-jpa','spring-security','spring-web','lombok','postgresql','h2'],
|
|
77
|
+
deno: ['cors','djwt','zod'],
|
|
78
|
+
gin: ['gorm','jwt-go','godotenv','validator'],
|
|
235
79
|
};
|
|
236
80
|
|
|
237
|
-
// ── EXTRAS
|
|
81
|
+
// ── EXTRAS ────────────────────────────────────────────────────────────────────
|
|
238
82
|
export const EXTRAS_CHOICES = [
|
|
239
|
-
{ name: '
|
|
240
|
-
{ name: '
|
|
241
|
-
{ name: '
|
|
242
|
-
{ name: '
|
|
243
|
-
{ name: '
|
|
244
|
-
{ name: '
|
|
245
|
-
{ name: '
|
|
246
|
-
{ name: '
|
|
83
|
+
{ name: 'Git init automático', value: 'git', checked: true },
|
|
84
|
+
{ name: 'ESLint + Prettier', value: 'eslint', checked: false },
|
|
85
|
+
{ name: 'Husky + lint-staged (pre-commit)', value: 'husky', checked: false },
|
|
86
|
+
{ name: 'Vitest (testing)', value: 'testing', checked: false },
|
|
87
|
+
{ name: 'Docker + docker-compose', value: 'docker', checked: false },
|
|
88
|
+
{ name: 'Archivos .env.example', value: 'env', checked: true },
|
|
89
|
+
{ name: '.editorconfig', value: 'editorconfig', checked: false },
|
|
90
|
+
{ name: 'GitHub Actions CI/CD', value: 'ci', checked: false },
|
|
91
|
+
{ name: 'Auth JWT lista (rutas + middleware)', value: 'auth', checked: false },
|
|
92
|
+
{ name: 'Tailwind CSS auto-configurado', value: 'tailwind', checked: false },
|
|
247
93
|
];
|
|
248
94
|
|
|
249
95
|
// ── FLUJO PRINCIPAL ───────────────────────────────────────────────────────────
|
|
250
96
|
export async function askProjectOptions(nameArg, options) {
|
|
97
|
+
// Modo preset
|
|
98
|
+
if (options.preset) {
|
|
99
|
+
const preset = PRESETS[options.preset];
|
|
100
|
+
if (!preset) {
|
|
101
|
+
console.log(chalk.red(`\n Preset "${options.preset}" no existe.`));
|
|
102
|
+
console.log(chalk.gray(' Presets disponibles: ' + Object.keys(PRESETS).join(', ') + '\n'));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const name = nameArg || options.preset + '-app';
|
|
106
|
+
console.log(chalk.gray(`\n Usando preset: ${chalk.white(options.preset)}\n`));
|
|
107
|
+
return { name, ...preset, frontendDeps: [], backendDeps: [], extras: ['git', 'env'] };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Modo --yes (defaults)
|
|
111
|
+
if (options.yes) {
|
|
112
|
+
const name = nameArg || 'my-app';
|
|
113
|
+
await checkFolderConflict(name, true);
|
|
114
|
+
return {
|
|
115
|
+
name,
|
|
116
|
+
frontend: options.frontend || 'react',
|
|
117
|
+
backend: options.backend || 'express',
|
|
118
|
+
db: options.db || 'postgres',
|
|
119
|
+
typescript: options.typescript || false,
|
|
120
|
+
frontendDeps: [],
|
|
121
|
+
backendDeps: [],
|
|
122
|
+
extras: ['git', 'env'],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
251
126
|
const answers = {};
|
|
252
127
|
|
|
128
|
+
// Nombre
|
|
253
129
|
if (!nameArg) {
|
|
254
130
|
const { name } = await inquirer.prompt([{
|
|
255
131
|
type: 'input',
|
|
256
132
|
name: 'name',
|
|
257
|
-
message: chalk.
|
|
133
|
+
message: chalk.white('Nombre del proyecto:'),
|
|
258
134
|
default: 'my-app',
|
|
259
|
-
validate: v =>
|
|
135
|
+
validate: v => {
|
|
136
|
+
if (!/^[a-z0-9-_]+$/i.test(v.trim())) return 'Solo letras, números, guiones y guiones bajos';
|
|
137
|
+
if (v.trim().length < 2) return 'Mínimo 2 caracteres';
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
260
140
|
}]);
|
|
261
141
|
answers.name = name.trim();
|
|
262
142
|
} else {
|
|
263
143
|
answers.name = nameArg;
|
|
264
144
|
}
|
|
265
145
|
|
|
146
|
+
// Verificar carpeta existente
|
|
147
|
+
await checkFolderConflict(answers.name, false);
|
|
148
|
+
|
|
149
|
+
// TypeScript
|
|
150
|
+
const { typescript } = await inquirer.prompt([{
|
|
151
|
+
type: 'confirm',
|
|
152
|
+
name: 'typescript',
|
|
153
|
+
message: chalk.white('¿Usar TypeScript?'),
|
|
154
|
+
default: options.typescript || false,
|
|
155
|
+
}]);
|
|
156
|
+
answers.typescript = typescript;
|
|
157
|
+
|
|
158
|
+
// Frontend
|
|
266
159
|
if (!options.frontend) {
|
|
267
160
|
const { frontend } = await inquirer.prompt([{
|
|
268
161
|
type: 'list',
|
|
269
162
|
name: 'frontend',
|
|
270
|
-
message: chalk.
|
|
163
|
+
message: chalk.white('Framework Frontend:'),
|
|
271
164
|
choices: FRONTEND_CHOICES,
|
|
272
165
|
pageSize: 12,
|
|
273
166
|
}]);
|
|
@@ -276,11 +169,12 @@ export async function askProjectOptions(nameArg, options) {
|
|
|
276
169
|
answers.frontend = options.frontend;
|
|
277
170
|
}
|
|
278
171
|
|
|
172
|
+
// Backend
|
|
279
173
|
if (!options.backend) {
|
|
280
174
|
const { backend } = await inquirer.prompt([{
|
|
281
175
|
type: 'list',
|
|
282
176
|
name: 'backend',
|
|
283
|
-
message: chalk.
|
|
177
|
+
message: chalk.white('Framework Backend:'),
|
|
284
178
|
choices: BACKEND_CHOICES,
|
|
285
179
|
pageSize: 12,
|
|
286
180
|
}]);
|
|
@@ -289,60 +183,93 @@ export async function askProjectOptions(nameArg, options) {
|
|
|
289
183
|
answers.backend = options.backend;
|
|
290
184
|
}
|
|
291
185
|
|
|
292
|
-
//
|
|
293
|
-
|
|
186
|
+
// Base de datos
|
|
187
|
+
if (!options.db) {
|
|
188
|
+
const { db } = await inquirer.prompt([{
|
|
189
|
+
type: 'list',
|
|
190
|
+
name: 'db',
|
|
191
|
+
message: chalk.white('Base de datos:'),
|
|
192
|
+
choices: DB_CHOICES,
|
|
193
|
+
pageSize: 6,
|
|
194
|
+
}]);
|
|
195
|
+
answers.db = db;
|
|
196
|
+
} else {
|
|
197
|
+
answers.db = options.db;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Deps frontend
|
|
294
201
|
const { frontendDeps } = await inquirer.prompt([{
|
|
295
202
|
type: 'checkbox',
|
|
296
203
|
name: 'frontendDeps',
|
|
297
|
-
message: chalk.
|
|
298
|
-
choices: FRONTEND_DEPS[answers.frontend] || [],
|
|
299
|
-
pageSize:
|
|
204
|
+
message: chalk.white(`Dependencias Frontend (${answers.frontend}):`),
|
|
205
|
+
choices: (FRONTEND_DEPS[answers.frontend] || []).map(d => ({ name: d, value: d })),
|
|
206
|
+
pageSize: 12,
|
|
300
207
|
}]);
|
|
301
208
|
|
|
302
|
-
//
|
|
303
|
-
console.log(chalk.gray(`\n 💡 Dependencias recomendadas para ${chalk.bold(answers.backend)}:`));
|
|
209
|
+
// Deps backend
|
|
304
210
|
const { backendDeps } = await inquirer.prompt([{
|
|
305
211
|
type: 'checkbox',
|
|
306
212
|
name: 'backendDeps',
|
|
307
|
-
message: chalk.
|
|
308
|
-
choices: BACKEND_DEPS[answers.backend] || [],
|
|
309
|
-
pageSize:
|
|
213
|
+
message: chalk.white(`Dependencias Backend (${answers.backend}):`),
|
|
214
|
+
choices: (BACKEND_DEPS[answers.backend] || []).map(d => ({ name: d, value: d })),
|
|
215
|
+
pageSize: 12,
|
|
310
216
|
}]);
|
|
311
217
|
|
|
312
218
|
// Extras
|
|
313
219
|
const { extras } = await inquirer.prompt([{
|
|
314
220
|
type: 'checkbox',
|
|
315
221
|
name: 'extras',
|
|
316
|
-
message: chalk.
|
|
222
|
+
message: chalk.white('Extras del proyecto:'),
|
|
317
223
|
choices: EXTRAS_CHOICES,
|
|
318
|
-
pageSize:
|
|
224
|
+
pageSize: 12,
|
|
319
225
|
}]);
|
|
320
226
|
|
|
321
227
|
// Resumen + confirmación
|
|
322
228
|
console.log();
|
|
323
|
-
console.log(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
229
|
+
console.log(
|
|
230
|
+
boxen(
|
|
231
|
+
chalk.bold.white(' RESUMEN\n\n') +
|
|
232
|
+
chalk.gray(' Nombre │ ') + chalk.white(answers.name) + '\n' +
|
|
233
|
+
chalk.gray(' Frontend │ ') + chalk.white(answers.frontend) + (answers.typescript ? chalk.gray(' + TypeScript') : '') + '\n' +
|
|
234
|
+
chalk.gray(' Backend │ ') + chalk.white(answers.backend) + '\n' +
|
|
235
|
+
chalk.gray(' Base datos │ ') + chalk.white(answers.db) + '\n' +
|
|
236
|
+
chalk.gray(' FE deps │ ') + chalk.white(frontendDeps.length ? frontendDeps.length + ' paquetes' : 'ninguno') + '\n' +
|
|
237
|
+
chalk.gray(' BE deps │ ') + chalk.white(backendDeps.length ? backendDeps.length + ' paquetes' : 'ninguno') + '\n' +
|
|
238
|
+
chalk.gray(' Extras │ ') + chalk.white(extras.length ? extras.join(', ') : 'ninguno'),
|
|
239
|
+
{ padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
|
|
240
|
+
)
|
|
241
|
+
);
|
|
333
242
|
console.log();
|
|
334
243
|
|
|
335
244
|
const { confirm } = await inquirer.prompt([{
|
|
336
245
|
type: 'confirm',
|
|
337
246
|
name: 'confirm',
|
|
338
|
-
message: chalk.
|
|
247
|
+
message: chalk.white('¿Crear el proyecto?'),
|
|
339
248
|
default: true,
|
|
340
249
|
}]);
|
|
341
250
|
|
|
342
|
-
if (!confirm) {
|
|
343
|
-
console.log(chalk.yellow('\n Operación cancelada.\n'));
|
|
344
|
-
process.exit(0);
|
|
345
|
-
}
|
|
251
|
+
if (!confirm) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
|
|
346
252
|
|
|
347
|
-
return {
|
|
253
|
+
return { ...answers, frontendDeps, backendDeps, extras };
|
|
348
254
|
}
|
|
255
|
+
|
|
256
|
+
// ── HELPERS ───────────────────────────────────────────────────────────────────
|
|
257
|
+
async function checkFolderConflict(name, autoSkip) {
|
|
258
|
+
const projectPath = path.resolve(process.cwd(), name);
|
|
259
|
+
if (await fse.pathExists(projectPath)) {
|
|
260
|
+
if (autoSkip) {
|
|
261
|
+
console.log(chalk.yellow(`\n La carpeta "${name}" ya existe. Se sobreescribirá.\n`));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const { overwrite } = await inquirer.prompt([{
|
|
265
|
+
type: 'confirm',
|
|
266
|
+
name: 'overwrite',
|
|
267
|
+
message: chalk.yellow(`La carpeta "${name}" ya existe. ¿Sobreescribir?`),
|
|
268
|
+
default: false,
|
|
269
|
+
}]);
|
|
270
|
+
if (!overwrite) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
|
|
271
|
+
await fse.remove(projectPath);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
package/package.json
CHANGED