novatec-cli 3.0.1 → 3.0.3

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,17 +4,6 @@ import fse from 'fs-extra';
4
4
  import path from 'path';
5
5
  import boxen from 'boxen';
6
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
- };
17
-
18
7
  export const FRONTEND_CHOICES = [
19
8
  { name: 'React — Vite + React 18', value: 'react' },
20
9
  { name: 'Next.js — SSR/SSG App Router', value: 'next' },
@@ -51,9 +40,9 @@ export const DB_CHOICES = [
51
40
  ];
52
41
 
53
42
  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' },
43
+ { name: 'MVC — Model-View-Controller (clásico)', value: 'mvc' },
44
+ { name: 'Hexagonal — Ports & Adapters', value: 'hexagonal' },
45
+ { name: 'Clean — Clean Architecture (capas)', value: 'clean' },
57
46
  ];
58
47
 
59
48
  export const PM_CHOICES = [
@@ -62,150 +51,83 @@ export const PM_CHOICES = [
62
51
  { name: 'yarn', value: 'yarn' },
63
52
  ];
64
53
 
65
- export const EXTRAS_CHOICES = [
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 },
84
- ];
85
-
86
54
  export async function askProjectOptions(nameArg, options) {
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
55
  const a = {};
122
56
 
123
57
  // Nombre
124
- if (!nameArg) {
58
+ if (nameArg) {
59
+ a.name = nameArg;
60
+ } else {
125
61
  const { name } = await inquirer.prompt([{
126
62
  type: 'input', name: 'name',
127
63
  message: chalk.white('Nombre del proyecto:'),
128
64
  default: 'my-app',
129
- validate: v => /^[a-z0-9-_]+$/i.test(v.trim()) && v.trim().length >= 2 || 'Solo letras, números, guiones. Mínimo 2 chars.',
65
+ validate: v => /^[a-z0-9-_]+$/i.test(v.trim()) && v.trim().length >= 2
66
+ || 'Solo letras, números, guiones. Mínimo 2 chars.',
130
67
  }]);
131
68
  a.name = name.trim();
132
- } else { a.name = nameArg; }
69
+ }
133
70
 
134
71
  await checkFolder(a.name);
135
72
 
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;
147
-
148
73
  // TypeScript
149
74
  const { typescript } = await inquirer.prompt([{
150
75
  type: 'confirm', name: 'typescript',
151
76
  message: chalk.white('¿Usar TypeScript?'),
152
- default: options.typescript || false,
77
+ default: false,
153
78
  }]);
154
79
  a.typescript = typescript;
155
80
 
156
81
  // Frontend
157
- if (!options.frontend) {
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; }
82
+ const { frontend } = await inquirer.prompt([{
83
+ type: 'list', name: 'frontend',
84
+ message: chalk.white('Framework Frontend:'),
85
+ choices: FRONTEND_CHOICES, pageSize: 12,
86
+ default: options.frontend,
87
+ }]);
88
+ a.frontend = frontend;
161
89
 
162
90
  // Backend
163
- if (!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; }
91
+ const { backend } = await inquirer.prompt([{
92
+ type: 'list', name: 'backend',
93
+ message: chalk.white('Framework Backend:'),
94
+ choices: BACKEND_CHOICES, pageSize: 12,
95
+ default: options.backend,
96
+ }]);
97
+ a.backend = backend;
167
98
 
168
99
  // 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;
183
- }
100
+ const { db } = await inquirer.prompt([{
101
+ type: 'list', name: 'db',
102
+ message: chalk.white('Base de datos:'),
103
+ choices: DB_CHOICES, pageSize: 7,
104
+ default: options.db,
105
+ }]);
106
+ a.db = db;
184
107
 
185
108
  // 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; }
109
+ const { architecture } = await inquirer.prompt([{
110
+ type: 'list', name: 'architecture',
111
+ message: chalk.white('Arquitectura del backend:'),
112
+ choices: ARCH_CHOICES,
113
+ default: options.architecture,
114
+ }]);
115
+ a.architecture = architecture;
190
116
 
191
117
  // 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; }
196
-
197
- // Extras
198
- const { extras } = await inquirer.prompt([{
199
- type: 'checkbox', name: 'extras',
200
- message: chalk.white('Extras del proyecto:'),
201
- choices: EXTRAS_CHOICES, pageSize: 14,
118
+ const { pm } = await inquirer.prompt([{
119
+ type: 'list', name: 'pm',
120
+ message: chalk.white('Package manager:'),
121
+ choices: PM_CHOICES,
122
+ default: options.packageManager || 'npm',
202
123
  }]);
124
+ a.pm = pm;
203
125
 
204
- // Auto-install
126
+ // Instalar dependencias
205
127
  const { install } = await inquirer.prompt([{
206
128
  type: 'confirm', name: 'install',
207
- message: chalk.white('¿Instalar dependencias automáticamente?'),
208
- default: options.install || false,
129
+ message: chalk.white('¿Instalar dependencias automáticamente al final?'),
130
+ default: true,
209
131
  }]);
210
132
  a.install = install;
211
133
 
@@ -214,22 +136,24 @@ export async function askProjectOptions(nameArg, options) {
214
136
  console.log(boxen(
215
137
  chalk.bold.white(' RESUMEN\n\n') +
216
138
  chalk.gray(' Nombre │ ') + chalk.white(a.name) + '\n' +
217
- chalk.gray(' Modo │ ') + chalk.white(a.mode || 'estándar') + '\n' +
218
139
  chalk.gray(' Frontend │ ') + chalk.white(a.frontend) + (a.typescript ? chalk.gray(' + TS') : '') + '\n' +
219
140
  chalk.gray(' Backend │ ') + chalk.white(a.backend) + '\n' +
220
141
  chalk.gray(' Base datos │ ') + chalk.white(a.db) + '\n' +
221
142
  chalk.gray(' Arquitectura │ ') + chalk.white(a.architecture) + '\n' +
222
143
  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'),
144
+ chalk.gray(' Instalar │ ') + chalk.white(a.install ? 'sí' : 'no'),
225
145
  { padding: 1, margin: { left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
226
146
  ));
227
147
  console.log();
228
148
 
229
- const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: chalk.white('¿Crear el proyecto?'), default: true }]);
149
+ const { confirm } = await inquirer.prompt([{
150
+ type: 'confirm', name: 'confirm',
151
+ message: chalk.white('¿Crear el proyecto?'),
152
+ default: true,
153
+ }]);
230
154
  if (!confirm) { console.log(chalk.gray('\n Cancelado.\n')); process.exit(0); }
231
155
 
232
- return { ...a, frontendDeps: [], backendDeps: [], extras };
156
+ return a;
233
157
  }
234
158
 
235
159
  async function checkFolder(name) {
package/lib/utils.js CHANGED
@@ -1,10 +1,14 @@
1
- import { execSync, spawnSync } from 'child_process';
1
+ import { execSync } from 'child_process';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
 
5
5
  export function isInstalled(cmd) {
6
- const r = spawnSync(cmd, ['--version'], { stdio: 'pipe', shell: true });
7
- return r.status === 0;
6
+ try {
7
+ execSync(`${cmd} --version`, { stdio: 'pipe' });
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
8
12
  }
9
13
 
10
14
  export async function checkRequirements() {
@@ -41,13 +45,11 @@ export function run(cmd, cwd, label) {
41
45
  color: 'white',
42
46
  }).start();
43
47
 
44
- const result = spawnSync(cmd, { cwd, shell: true, stdio: 'pipe', encoding: 'utf8' });
45
-
46
- if (result.status !== 0) {
48
+ try {
49
+ execSync(cmd, { cwd, stdio: 'pipe', encoding: 'utf8' });
50
+ spinner.succeed(chalk.white('✔ ' + label));
51
+ } catch (err) {
47
52
  spinner.fail(chalk.red('✖ ' + label));
48
- const msg = result.stderr || result.stdout || 'Error desconocido';
49
- throw new Error(msg.trim());
53
+ throw new Error((err.stderr || err.stdout || err.message || 'Error desconocido').trim());
50
54
  }
51
-
52
- spinner.succeed(chalk.white('✔ ' + label));
53
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novatec-cli",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
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",