novatec-cli 2.0.0 → 3.0.1

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
@@ -4,272 +4,243 @@ import fse from 'fs-extra';
4
4
  import path from 'path';
5
5
  import boxen from 'boxen';
6
6
 
7
- // ── PRESETS ───────────────────────────────────────────────────────────────────
8
7
  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 },
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' },
14
16
  };
15
17
 
16
- // ── FRONTEND FRAMEWORKS ───────────────────────────────────────────────────────
17
18
  export const FRONTEND_CHOICES = [
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' },
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' },
28
29
  ];
29
30
 
30
- // ── BACKEND FRAMEWORKS ────────────────────────────────────────────────────────
31
31
  export const BACKEND_CHOICES = [
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' },
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' },
42
42
  ];
43
43
 
44
- // ── BASES DE DATOS ────────────────────────────────────────────────────────────
45
44
  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' },
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
51
  ];
52
52
 
53
- // ── DEPENDENCIAS FRONTEND ─────────────────────────────────────────────────────
54
- export const FRONTEND_DEPS = {
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'],
65
- };
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
+ ];
66
58
 
67
- // ── DEPENDENCIAS BACKEND ──────────────────────────────────────────────────────
68
- export const BACKEND_DEPS = {
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'],
79
- };
59
+ export const PM_CHOICES = [
60
+ { name: 'npm', value: 'npm' },
61
+ { name: 'pnpm', value: 'pnpm' },
62
+ { name: 'yarn', value: 'yarn' },
63
+ ];
80
64
 
81
- // ── EXTRAS ────────────────────────────────────────────────────────────────────
82
65
  export const EXTRAS_CHOICES = [
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 },
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 },
93
84
  ];
94
85
 
95
- // ── FLUJO PRINCIPAL ───────────────────────────────────────────────────────────
96
86
  export async function askProjectOptions(nameArg, options) {
97
- // Modo preset
87
+ // Preset
98
88
  if (options.preset) {
99
89
  const preset = PRESETS[options.preset];
100
90
  if (!preset) {
101
91
  console.log(chalk.red(`\n Preset "${options.preset}" no existe.`));
102
- console.log(chalk.gray(' Presets disponibles: ' + Object.keys(PRESETS).join(', ') + '\n'));
92
+ console.log(chalk.gray(' Disponibles: ' + Object.keys(PRESETS).join(', ') + '\n'));
103
93
  process.exit(1);
104
94
  }
105
95
  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'] };
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'] };
108
98
  }
109
99
 
110
- // Modo --yes (defaults)
100
+ // --yes mode
111
101
  if (options.yes) {
112
102
  const name = nameArg || 'my-app';
113
- await checkFolderConflict(name, true);
103
+ await checkFolder(name);
114
104
  return {
115
105
  name,
116
- frontend: options.frontend || 'react',
117
- backend: options.backend || 'express',
118
- db: options.db || 'postgres',
119
- typescript: options.typescript || false,
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,
120
115
  frontendDeps: [],
121
116
  backendDeps: [],
122
117
  extras: ['git', 'env'],
123
118
  };
124
119
  }
125
120
 
126
- const answers = {};
121
+ const a = {};
127
122
 
128
123
  // Nombre
129
124
  if (!nameArg) {
130
125
  const { name } = await inquirer.prompt([{
131
- type: 'input',
132
- name: 'name',
126
+ type: 'input', name: 'name',
133
127
  message: chalk.white('Nombre del proyecto:'),
134
128
  default: 'my-app',
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
- },
129
+ validate: v => /^[a-z0-9-_]+$/i.test(v.trim()) && v.trim().length >= 2 || 'Solo letras, números, guiones. Mínimo 2 chars.',
140
130
  }]);
141
- answers.name = name.trim();
142
- } else {
143
- answers.name = nameArg;
144
- }
145
-
146
- // Verificar carpeta existente
147
- await checkFolderConflict(answers.name, false);
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;
148
147
 
149
148
  // TypeScript
150
149
  const { typescript } = await inquirer.prompt([{
151
- type: 'confirm',
152
- name: 'typescript',
150
+ type: 'confirm', name: 'typescript',
153
151
  message: chalk.white('¿Usar TypeScript?'),
154
152
  default: options.typescript || false,
155
153
  }]);
156
- answers.typescript = typescript;
154
+ a.typescript = typescript;
157
155
 
158
156
  // Frontend
159
157
  if (!options.frontend) {
160
- const { frontend } = await inquirer.prompt([{
161
- type: 'list',
162
- name: 'frontend',
163
- message: chalk.white('Framework Frontend:'),
164
- choices: FRONTEND_CHOICES,
165
- pageSize: 12,
166
- }]);
167
- answers.frontend = frontend;
168
- } else {
169
- answers.frontend = options.frontend;
170
- }
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; }
171
161
 
172
162
  // Backend
173
163
  if (!options.backend) {
174
- const { backend } = await inquirer.prompt([{
175
- type: 'list',
176
- name: 'backend',
177
- message: chalk.white('Framework Backend:'),
178
- choices: BACKEND_CHOICES,
179
- pageSize: 12,
180
- }]);
181
- answers.backend = backend;
182
- } else {
183
- answers.backend = options.backend;
184
- }
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; }
185
167
 
186
- // Base de datos
168
+ // DB
187
169
  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;
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;
198
183
  }
199
184
 
200
- // Deps frontend
201
- const { frontendDeps } = await inquirer.prompt([{
202
- type: 'checkbox',
203
- name: 'frontendDeps',
204
- message: chalk.white(`Dependencias Frontend (${answers.frontend}):`),
205
- choices: (FRONTEND_DEPS[answers.frontend] || []).map(d => ({ name: d, value: d })),
206
- pageSize: 12,
207
- }]);
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; }
208
190
 
209
- // Deps backend
210
- const { backendDeps } = await inquirer.prompt([{
211
- type: 'checkbox',
212
- name: 'backendDeps',
213
- message: chalk.white(`Dependencias Backend (${answers.backend}):`),
214
- choices: (BACKEND_DEPS[answers.backend] || []).map(d => ({ name: d, value: d })),
215
- pageSize: 12,
216
- }]);
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; }
217
196
 
218
197
  // Extras
219
198
  const { extras } = await inquirer.prompt([{
220
- type: 'checkbox',
221
- name: 'extras',
199
+ type: 'checkbox', name: 'extras',
222
200
  message: chalk.white('Extras del proyecto:'),
223
- choices: EXTRAS_CHOICES,
224
- pageSize: 12,
201
+ choices: EXTRAS_CHOICES, pageSize: 14,
202
+ }]);
203
+
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,
225
209
  }]);
210
+ a.install = install;
226
211
 
227
- // Resumen + confirmación
212
+ // Resumen
228
213
  console.log();
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
- );
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
+ ));
242
227
  console.log();
243
228
 
244
- const { confirm } = await inquirer.prompt([{
245
- type: 'confirm',
246
- name: 'confirm',
247
- message: chalk.white('¿Crear el proyecto?'),
248
- default: true,
249
- }]);
250
-
229
+ const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: chalk.white('¿Crear el proyecto?'), default: true }]);
251
230
  if (!confirm) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
252
231
 
253
- return { ...answers, frontendDeps, backendDeps, extras };
232
+ return { ...a, frontendDeps: [], backendDeps: [], extras };
254
233
  }
255
234
 
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
- }
235
+ async function checkFolder(name) {
236
+ const p = path.resolve(process.cwd(), name);
237
+ if (await fse.pathExists(p)) {
264
238
  const { overwrite } = await inquirer.prompt([{
265
- type: 'confirm',
266
- name: 'overwrite',
239
+ type: 'confirm', name: 'overwrite',
267
240
  message: chalk.yellow(`La carpeta "${name}" ya existe. ¿Sobreescribir?`),
268
241
  default: false,
269
242
  }]);
270
243
  if (!overwrite) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
271
- await fse.remove(projectPath);
244
+ await fse.remove(p);
272
245
  }
273
246
  }
274
-
275
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novatec-cli",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
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",