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/create.js +106 -195
- package/lib/doctor.js +1 -7
- package/lib/generators/backend.js +203 -132
- package/lib/generators/deploy.js +17 -9
- package/lib/generators/frontend.js +8 -57
- package/lib/prompts.js +54 -130
- package/lib/utils.js +12 -10
- package/package.json +1 -1
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 (
|
|
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
|
|
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
|
-
}
|
|
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:
|
|
77
|
+
default: false,
|
|
153
78
|
}]);
|
|
154
79
|
a.typescript = typescript;
|
|
155
80
|
|
|
156
81
|
// Frontend
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
//
|
|
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:
|
|
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')
|
|
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([{
|
|
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
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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