novatec-cli 3.0.2 → 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 +104 -193
- package/lib/generators/backend.js +198 -139
- package/lib/generators/frontend.js +8 -57
- package/lib/prompts.js +54 -130
- package/package.json +1 -1
package/lib/create.js
CHANGED
|
@@ -6,150 +6,95 @@ import { execSync } from 'child_process';
|
|
|
6
6
|
import { askProjectOptions } from './prompts.js';
|
|
7
7
|
import { generateFrontend } from './generators/frontend.js';
|
|
8
8
|
import { generateBackend } from './generators/backend.js';
|
|
9
|
-
import { generateBusiness } from './generators/business.js';
|
|
10
|
-
import { generateDocker } from './generators/docker.js';
|
|
11
|
-
import { generateSecurity } from './generators/security.js';
|
|
12
|
-
import { generateDeploy } from './generators/deploy.js';
|
|
13
9
|
import { isInstalled } from './utils.js';
|
|
14
10
|
|
|
15
|
-
function bar(cur, tot, label) {
|
|
16
|
-
const w = 28, f = Math.round((cur / tot) * w);
|
|
17
|
-
const pct = Math.round((cur / tot) * 100);
|
|
18
|
-
process.stdout.write(`\r [${'█'.repeat(f)}${'░'.repeat(w - f)}] ${chalk.white(pct + '%')} ${chalk.gray(label.substring(0, 30))} `);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
11
|
export async function runCreate(nameArg, options) {
|
|
22
12
|
const t0 = Date.now();
|
|
23
13
|
const config = await askProjectOptions(nameArg, options);
|
|
24
14
|
const root = path.resolve(process.cwd(), config.name);
|
|
25
15
|
await fse.ensureDir(root);
|
|
26
16
|
|
|
27
|
-
const extras = config.extras || [];
|
|
28
17
|
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
29
18
|
const pm = config.pm || 'npm';
|
|
30
19
|
|
|
31
|
-
// Calcular pasos
|
|
32
|
-
const steps = ['Frontend', 'Backend', 'README', 'Env files',
|
|
33
|
-
config.mode === 'business' && 'Business mode',
|
|
34
|
-
extras.includes('docker') && 'Docker',
|
|
35
|
-
extras.includes('security') && 'Seguridad',
|
|
36
|
-
extras.includes('ci') && 'GitHub Actions',
|
|
37
|
-
extras.includes('eslint') && 'ESLint',
|
|
38
|
-
extras.includes('testing') && 'Testing',
|
|
39
|
-
extras.includes('swagger') && 'Swagger',
|
|
40
|
-
extras.includes('logs') && 'Logs',
|
|
41
|
-
extras.includes('seo') && 'SEO',
|
|
42
|
-
extras.includes('git') && 'Git',
|
|
43
|
-
config.install && 'Instalando deps',
|
|
44
|
-
].filter(Boolean);
|
|
45
|
-
|
|
46
20
|
console.log();
|
|
47
|
-
let step = 0;
|
|
48
|
-
const tick = (label) => { step++; bar(step, steps.length, label); };
|
|
49
21
|
|
|
50
22
|
// 1. Frontend
|
|
51
|
-
|
|
23
|
+
console.log(chalk.white(' [1/4] Generando frontend...'));
|
|
52
24
|
await generateFrontend(config, root);
|
|
53
|
-
await connectFrontendToBackend(config, root);
|
|
54
25
|
|
|
55
|
-
// 2. Backend
|
|
56
|
-
|
|
26
|
+
// 2. Backend
|
|
27
|
+
console.log(chalk.white(' [2/4] Generando backend...'));
|
|
57
28
|
await generateBackend(config, root);
|
|
58
|
-
await applyArchitecture(config, root);
|
|
59
29
|
|
|
60
|
-
// 3.
|
|
61
|
-
|
|
62
|
-
await
|
|
30
|
+
// 3. Arquitectura
|
|
31
|
+
console.log(chalk.white(' [3/4] Aplicando arquitectura...'));
|
|
32
|
+
await applyArchitecture(config, root);
|
|
63
33
|
|
|
64
|
-
// 4. .env
|
|
65
|
-
|
|
34
|
+
// 4. Archivos base (.env, README)
|
|
35
|
+
console.log(chalk.white(' [4/4] Generando archivos base...'));
|
|
66
36
|
await generateEnv(config, root);
|
|
37
|
+
await generateReadme(config, root);
|
|
67
38
|
|
|
68
|
-
// 5.
|
|
69
|
-
if (config.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (extras.includes('docker')) {
|
|
76
|
-
tick('Docker...');
|
|
77
|
-
await generateDocker(config, root);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 7. Seguridad
|
|
81
|
-
if (extras.includes('security')) {
|
|
82
|
-
tick('Seguridad...');
|
|
83
|
-
await generateSecurity(config, root);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 8. GitHub Actions
|
|
87
|
-
if (extras.includes('ci')) {
|
|
88
|
-
tick('GitHub Actions...');
|
|
89
|
-
await generateDeploy(config, root);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 9. ESLint
|
|
93
|
-
if (extras.includes('eslint')) {
|
|
94
|
-
tick('ESLint...');
|
|
95
|
-
await setupEslint(root);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 10. Testing
|
|
99
|
-
if (extras.includes('testing')) {
|
|
100
|
-
tick('Testing...');
|
|
101
|
-
await setupTesting(root);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 11. Swagger (ya incluido en security/backend, solo notificar)
|
|
105
|
-
if (extras.includes('swagger')) {
|
|
106
|
-
tick('Swagger...');
|
|
107
|
-
// generado en generateSecurity
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 12. Logs
|
|
111
|
-
if (extras.includes('logs')) {
|
|
112
|
-
tick('Logs...');
|
|
113
|
-
await setupLogs(config, root);
|
|
114
|
-
}
|
|
39
|
+
// 5. Instalar dependencias
|
|
40
|
+
if (config.install) {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(chalk.white(' Instalando dependencias...'));
|
|
43
|
+
const feDir = path.join(root, 'frontend');
|
|
44
|
+
const beDir = path.join(root, 'backend');
|
|
45
|
+
const installCmd = pm === 'yarn' ? 'yarn' : `${pm} install`;
|
|
115
46
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
47
|
+
if (await fse.pathExists(path.join(feDir, 'package.json'))) {
|
|
48
|
+
try {
|
|
49
|
+
console.log(chalk.gray(` → frontend (${pm} install)`));
|
|
50
|
+
execSync(installCmd, { cwd: feDir, stdio: 'inherit' });
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.log(chalk.yellow(' ⚠ Error instalando frontend: ' + e.message));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
121
55
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
56
|
+
if (!isPython && await fse.pathExists(path.join(beDir, 'package.json'))) {
|
|
57
|
+
try {
|
|
58
|
+
console.log(chalk.gray(` → backend (${pm} install)`));
|
|
59
|
+
execSync(installCmd, { cwd: beDir, stdio: 'inherit' });
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.log(chalk.yellow(' ⚠ Error instalando backend: ' + e.message));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
130
64
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
65
|
+
if (isPython && await fse.pathExists(path.join(beDir, 'requirements.txt'))) {
|
|
66
|
+
if (isInstalled('pip')) {
|
|
67
|
+
try {
|
|
68
|
+
console.log(chalk.gray(' → backend (pip install -r requirements.txt)'));
|
|
69
|
+
execSync('pip install -r requirements.txt', { cwd: beDir, stdio: 'inherit' });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.log(chalk.yellow(' ⚠ Error instalando backend Python: ' + e.message));
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
console.log(chalk.yellow(' ⚠ pip no encontrado. Ejecuta manualmente: pip install -r requirements.txt'));
|
|
75
|
+
}
|
|
140
76
|
}
|
|
141
77
|
}
|
|
142
78
|
|
|
143
|
-
bar(steps.length, steps.length, 'Completado');
|
|
144
|
-
console.log('\n');
|
|
145
|
-
|
|
146
79
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
147
|
-
const beCmd = { express:'npm run dev', nestjs:'npm run start:dev', fastify:'npm run dev', hono:'npm run dev', fastapi:'uvicorn main:app --reload', django:'python manage.py runserver', flask:'python run.py', spring:'mvn spring-boot:run', deno:'deno task dev', gin:'go run main.go' };
|
|
148
80
|
|
|
81
|
+
const beCmd = {
|
|
82
|
+
express: 'npm run dev',
|
|
83
|
+
nestjs: 'npm run start:dev',
|
|
84
|
+
fastify: 'npm run dev',
|
|
85
|
+
hono: 'npm run dev',
|
|
86
|
+
fastapi: 'uvicorn main:app --reload',
|
|
87
|
+
django: 'python manage.py runserver',
|
|
88
|
+
flask: 'python run.py',
|
|
89
|
+
spring: 'mvn spring-boot:run',
|
|
90
|
+
deno: 'deno task dev',
|
|
91
|
+
gin: 'go run main.go',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
console.log();
|
|
149
95
|
console.log(boxen(
|
|
150
96
|
chalk.bold.white(' ✔ PROYECTO LISTO — NovaTec CLI\n\n') +
|
|
151
97
|
chalk.gray(' Nombre │ ') + chalk.white(config.name) + '\n' +
|
|
152
|
-
chalk.gray(' Modo │ ') + chalk.white(config.mode || 'estándar') + '\n' +
|
|
153
98
|
chalk.gray(' Frontend │ ') + chalk.white(config.frontend) + (config.typescript ? chalk.gray(' + TypeScript') : '') + '\n' +
|
|
154
99
|
chalk.gray(' Backend │ ') + chalk.white(config.backend) + '\n' +
|
|
155
100
|
chalk.gray(' Base datos │ ') + chalk.white(config.db || 'ninguna') + '\n' +
|
|
@@ -157,12 +102,9 @@ export async function runCreate(nameArg, options) {
|
|
|
157
102
|
chalk.gray(' Tiempo │ ') + chalk.white(elapsed + 's') + '\n\n' +
|
|
158
103
|
chalk.gray(' ─────────────────────────────────────────────\n\n') +
|
|
159
104
|
chalk.white(' # Frontend\n') +
|
|
160
|
-
chalk.gray(` cd ${config.name}/frontend\n`) +
|
|
161
|
-
chalk.gray(` ${pm} ${pm === 'yarn' ? '' : 'run '}dev\n\n`) +
|
|
105
|
+
chalk.gray(` cd ${config.name}/frontend && ${pm} run dev\n\n`) +
|
|
162
106
|
chalk.white(' # Backend\n') +
|
|
163
|
-
chalk.gray(` cd ${config.name}/backend
|
|
164
|
-
chalk.gray(` ${beCmd[config.backend] || pm + ' run dev'}\n`) +
|
|
165
|
-
(config.mode === 'business' ? '\n' + chalk.gray(' # Dashboard → http://localhost:5173/dashboard\n') + chalk.gray(' # API Docs → http://localhost:3001/api-docs') : ''),
|
|
107
|
+
chalk.gray(` cd ${config.name}/backend && ${beCmd[config.backend] || pm + ' run dev'}`),
|
|
166
108
|
{ padding: 1, margin: { top: 1, left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
|
|
167
109
|
));
|
|
168
110
|
|
|
@@ -174,32 +116,6 @@ export async function runCreate(nameArg, options) {
|
|
|
174
116
|
console.log();
|
|
175
117
|
}
|
|
176
118
|
|
|
177
|
-
// ── CONEXIÓN FE → BE ──────────────────────────────────────────────────────────
|
|
178
|
-
async function connectFrontendToBackend(config, root) {
|
|
179
|
-
const feDir = path.join(root, 'frontend');
|
|
180
|
-
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
181
|
-
const bePort = isPython ? '8000' : '3001';
|
|
182
|
-
|
|
183
|
-
// Archivo de API client
|
|
184
|
-
const apiClient = config.db === 'supabase'
|
|
185
|
-
? `import { createClient } from '@supabase/supabase-js';\n\nexport const supabase = createClient(\n import.meta.env.VITE_SUPABASE_URL,\n import.meta.env.VITE_SUPABASE_ANON_KEY\n);\n\nexport const api = {\n get: (url) => fetch(import.meta.env.VITE_API_URL + url).then(r => r.json()),\n post: (url, data) => fetch(import.meta.env.VITE_API_URL + url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()),\n put: (url, data) => fetch(import.meta.env.VITE_API_URL + url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()),\n delete: (url) => fetch(import.meta.env.VITE_API_URL + url, { method: 'DELETE' }).then(r => r.json()),\n};\n`
|
|
186
|
-
: `const BASE = import.meta.env.VITE_API_URL || 'http://localhost:${bePort}';\n\nconst headers = () => ({\n 'Content-Type': 'application/json',\n ...(localStorage.getItem('token') ? { Authorization: \`Bearer \${localStorage.getItem('token')}\` } : {}),\n});\n\nexport const api = {\n get: (url) => fetch(BASE + url, { headers: headers() }).then(r => r.json()),\n post: (url, data) => fetch(BASE + url, { method: 'POST', headers: headers(), body: JSON.stringify(data) }).then(r => r.json()),\n put: (url, data) => fetch(BASE + url, { method: 'PUT', headers: headers(), body: JSON.stringify(data) }).then(r => r.json()),\n delete: (url) => fetch(BASE + url, { method: 'DELETE', headers: headers() }).then(r => r.json()),\n};\n`;
|
|
187
|
-
|
|
188
|
-
const srcDir = path.join(feDir, 'src', 'lib');
|
|
189
|
-
await fse.ensureDir(srcDir);
|
|
190
|
-
await fse.writeFile(path.join(srcDir, 'api.js'), apiClient);
|
|
191
|
-
|
|
192
|
-
// Proxy en vite.config si aplica
|
|
193
|
-
const viteConfig = path.join(feDir, 'vite.config.js');
|
|
194
|
-
if (await fse.pathExists(viteConfig)) {
|
|
195
|
-
const content = await fse.readFile(viteConfig, 'utf8');
|
|
196
|
-
if (!content.includes('proxy')) {
|
|
197
|
-
const proxy = `\n server: {\n proxy: {\n '/api': {\n target: 'http://localhost:${bePort}',\n changeOrigin: true,\n },\n },\n },`;
|
|
198
|
-
await fse.writeFile(viteConfig, content.replace('plugins:', proxy + '\n plugins:'));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
119
|
// ── ARQUITECTURA ──────────────────────────────────────────────────────────────
|
|
204
120
|
async function applyArchitecture(config, root) {
|
|
205
121
|
const beDir = path.join(root, 'backend', 'src');
|
|
@@ -217,72 +133,67 @@ async function applyArchitecture(config, root) {
|
|
|
217
133
|
}
|
|
218
134
|
}
|
|
219
135
|
|
|
220
|
-
// ── ENV
|
|
136
|
+
// ── .ENV ──────────────────────────────────────────────────────────────────────
|
|
221
137
|
async function generateEnv(config, root) {
|
|
222
138
|
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
139
|
+
const bePort = isPython ? '8000' : '3001';
|
|
140
|
+
|
|
223
141
|
const dbUrl = {
|
|
224
142
|
postgres: 'postgresql://user:password@localhost:5432/mydb',
|
|
225
143
|
mysql: 'mysql://user:password@localhost:3306/mydb',
|
|
226
144
|
mongo: 'mongodb://localhost:27017/mydb',
|
|
227
145
|
sqlite: 'file:./dev.db',
|
|
228
|
-
supabase:
|
|
146
|
+
supabase: 'https://your-project.supabase.co',
|
|
229
147
|
}[config.db] || '';
|
|
230
148
|
|
|
231
|
-
const feEnv = `VITE_API_URL=http://localhost:${
|
|
232
|
-
|
|
149
|
+
const feEnv = `VITE_API_URL=http://localhost:${bePort}\n`;
|
|
150
|
+
const beEnv = isPython
|
|
151
|
+
? `DATABASE_URL=${dbUrl}\nSECRET_KEY=change_me_in_production\nDEBUG=True\n`
|
|
152
|
+
: `PORT=3001\nNODE_ENV=development\nJWT_SECRET=change_me_in_production\n${dbUrl ? 'DATABASE_URL=' + dbUrl + '\n' : ''}`;
|
|
233
153
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
(config.db === 'supabase' ? `SUPABASE_URL=${config.supabaseUrl || ''}\nSUPABASE_KEY=${config.supabaseKey || ''}\n` : '') +
|
|
237
|
-
(config.extras?.includes('stripe') ? 'STRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\n' : '');
|
|
154
|
+
const feDir = path.join(root, 'frontend');
|
|
155
|
+
const beDir = path.join(root, 'backend');
|
|
238
156
|
|
|
239
|
-
await fse.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
157
|
+
if (await fse.pathExists(feDir)) {
|
|
158
|
+
await fse.writeFile(path.join(feDir, '.env'), feEnv);
|
|
159
|
+
await fse.writeFile(path.join(feDir, '.env.example'), feEnv);
|
|
160
|
+
}
|
|
161
|
+
if (await fse.pathExists(beDir)) {
|
|
162
|
+
await fse.writeFile(path.join(beDir, '.env'), beEnv);
|
|
163
|
+
await fse.writeFile(path.join(beDir, '.env.example'), beEnv);
|
|
244
164
|
}
|
|
245
165
|
}
|
|
246
166
|
|
|
247
167
|
// ── README ────────────────────────────────────────────────────────────────────
|
|
248
168
|
async function generateReadme(config, root) {
|
|
249
169
|
const ts = config.typescript ? ' + TypeScript' : '';
|
|
250
|
-
const content =
|
|
170
|
+
const content =
|
|
171
|
+
`# ${config.name}
|
|
172
|
+
|
|
173
|
+
> Generado con [NovaTec CLI](https://www.npmjs.com/package/novatec-cli)
|
|
174
|
+
|
|
175
|
+
## Stack
|
|
176
|
+
|
|
177
|
+
| Capa | Tecnología |
|
|
178
|
+
|------|------------|
|
|
179
|
+
| Frontend | **${config.frontend}**${ts} |
|
|
180
|
+
| Backend | **${config.backend}** |
|
|
181
|
+
| Base de datos | **${config.db || 'N/A'}** |
|
|
182
|
+
| Arquitectura | **${config.architecture || 'mvc'}** |
|
|
183
|
+
|
|
184
|
+
## Inicio rápido
|
|
185
|
+
|
|
186
|
+
\`\`\`bash
|
|
187
|
+
# Frontend
|
|
188
|
+
cd ${config.name}/frontend
|
|
189
|
+
npm install
|
|
190
|
+
npm run dev
|
|
191
|
+
|
|
192
|
+
# Backend
|
|
193
|
+
cd ${config.name}/backend
|
|
194
|
+
npm install
|
|
195
|
+
npm run dev
|
|
196
|
+
\`\`\`
|
|
197
|
+
`;
|
|
251
198
|
await fse.writeFile(path.join(root, 'README.md'), content);
|
|
252
199
|
}
|
|
253
|
-
|
|
254
|
-
// ── ESLINT ────────────────────────────────────────────────────────────────────
|
|
255
|
-
async function setupEslint(root) {
|
|
256
|
-
const fe = path.join(root, 'frontend');
|
|
257
|
-
await fse.writeFile(path.join(fe, '.eslintrc.json'), JSON.stringify({ env: { browser: true, es2021: true }, extends: ['eslint:recommended'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, rules: { 'no-unused-vars': 'warn' } }, null, 2));
|
|
258
|
-
await fse.writeFile(path.join(fe, '.prettierrc'), JSON.stringify({ semi: true, singleQuote: true, tabWidth: 2, trailingComma: 'es5' }, null, 2));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ── TESTING ───────────────────────────────────────────────────────────────────
|
|
262
|
-
async function setupTesting(root) {
|
|
263
|
-
const testDir = path.join(root, 'frontend', 'src', '__tests__');
|
|
264
|
-
await fse.ensureDir(testDir);
|
|
265
|
-
await fse.writeFile(path.join(testDir, 'app.test.js'), `import { describe, it, expect } from 'vitest';\ndescribe('App', () => { it('works', () => expect(1 + 1).toBe(2)); });\n`);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ── LOGS ──────────────────────────────────────────────────────────────────────
|
|
269
|
-
async function setupLogs(config, root) {
|
|
270
|
-
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
271
|
-
if (isPython) return;
|
|
272
|
-
const loggerFile = `import winston from 'winston';\n\nexport const logger = winston.createLogger({\n level: process.env.LOG_LEVEL || 'info',\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.colorize(),\n winston.format.printf(({ timestamp, level, message }) => \`\${timestamp} [\${level}]: \${message}\`)\n ),\n transports: [\n new winston.transports.Console(),\n new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),\n new winston.transports.File({ filename: 'logs/combined.log' }),\n ],\n});\n`;
|
|
273
|
-
const logDir = path.join(root, 'backend', 'src', 'utils');
|
|
274
|
-
await fse.ensureDir(logDir);
|
|
275
|
-
await fse.writeFile(path.join(logDir, 'logger.js'), loggerFile);
|
|
276
|
-
await fse.ensureDir(path.join(root, 'backend', 'logs'));
|
|
277
|
-
await fse.writeFile(path.join(root, 'backend', 'logs', '.gitkeep'), '');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// ── SEO ───────────────────────────────────────────────────────────────────────
|
|
281
|
-
async function setupSEO(config, root) {
|
|
282
|
-
const publicDir = path.join(root, 'frontend', 'public');
|
|
283
|
-
await fse.ensureDir(publicDir);
|
|
284
|
-
await fse.writeFile(path.join(publicDir, 'robots.txt'), `User-agent: *\nAllow: /\nSitemap: https://yourdomain.com/sitemap.xml\n`);
|
|
285
|
-
await fse.writeFile(path.join(publicDir, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>https://yourdomain.com/</loc><priority>1.0</priority></url>\n</urlset>\n`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const GITIGNORE = `node_modules/\ndist/\n.next/\n.nuxt/\n.svelte-kit/\n.env\n*.log\nlogs/\n__pycache__/\n.venv/\n*.pyc\ntarget/\n*.class\n.DS_Store\n`;
|
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import fse from 'fs-extra';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { isInstalled } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
// DB packages por backend type
|
|
8
|
+
const NODE_DB_DEPS = {
|
|
9
|
+
postgres: ['pg', 'pg-hstore'],
|
|
10
|
+
mysql: ['mysql2'],
|
|
11
|
+
mongo: ['mongoose'],
|
|
12
|
+
sqlite: ['better-sqlite3'],
|
|
13
|
+
supabase: ['@supabase/supabase-js'],
|
|
14
|
+
none: [],
|
|
15
|
+
};
|
|
6
16
|
|
|
7
|
-
function installNodeDeps(prod, dev, cwd
|
|
17
|
+
function installNodeDeps(prod, dev, cwd) {
|
|
8
18
|
if (prod.length) {
|
|
9
|
-
|
|
10
|
-
if (r.status !== 0) throw new Error((r.stderr || r.stdout || '').trim() || `Error instalando: ${prod.join(', ')}`);
|
|
19
|
+
execSync(`npm install ${prod.join(' ')}`, { cwd, stdio: 'pipe' });
|
|
11
20
|
}
|
|
12
|
-
if (dev.length) {
|
|
13
|
-
|
|
14
|
-
if (r.status !== 0) throw new Error((r.stderr || r.stdout || '').trim() || `Error instalando dev: ${dev.join(', ')}`);
|
|
21
|
+
if (dev && dev.length) {
|
|
22
|
+
execSync(`npm install -D ${dev.join(' ')}`, { cwd, stdio: 'pipe' });
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
function backendPath(projectPath) {
|
|
27
|
+
return path.join(projectPath, 'backend');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function generateBackend(config, projectPath) {
|
|
31
|
+
console.log(chalk.blue(`\n ⚙ Generando backend (${chalk.bold(config.backend)})...`));
|
|
20
32
|
const handlers = { express, nestjs, fastify, hono, fastapi, django, flask, spring, deno, gin };
|
|
21
33
|
const fn = handlers[config.backend];
|
|
22
34
|
if (!fn) throw new Error(`Backend no soportado: ${config.backend}`);
|
|
23
35
|
await fn(config, projectPath);
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
// ── HELPERS ───────────────────────────────────────────────────────────────────
|
|
27
|
-
function backendPath(projectPath) { return path.join(projectPath, 'backend'); }
|
|
28
|
-
|
|
29
|
-
async function writeDotenv(bPath, content) {
|
|
30
|
-
await fse.writeFile(path.join(bPath, '.env'), content);
|
|
31
|
-
await fse.writeFile(path.join(bPath, '.env.example'), content.replace(/=.+/g, '='));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
38
|
// ── EXPRESS ───────────────────────────────────────────────────────────────────
|
|
35
39
|
async function express(config, projectPath) {
|
|
36
40
|
const bp = backendPath(projectPath);
|
|
@@ -38,38 +42,24 @@ async function express(config, projectPath) {
|
|
|
38
42
|
await fse.ensureDir(path.join(bp, 'src', 'middlewares'));
|
|
39
43
|
await fse.ensureDir(path.join(bp, 'src', 'controllers'));
|
|
40
44
|
|
|
41
|
-
const
|
|
42
|
-
const hasDotenv = deps.includes('dotenv');
|
|
43
|
-
const hasCors = deps.includes('cors');
|
|
44
|
-
const hasHelmet = deps.includes('helmet');
|
|
45
|
-
const hasMorgan = deps.includes('morgan');
|
|
46
|
-
const hasSwagger = deps.includes('swagger-ui-express');
|
|
47
|
-
const hasSocket = deps.includes('socket.io');
|
|
45
|
+
const dbDeps = NODE_DB_DEPS[config.db] || [];
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
await fse.writeJSON(path.join(bp, 'package.json'), {
|
|
50
48
|
name: 'backend', version: '1.0.0', main: 'src/server.js',
|
|
51
49
|
scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
|
|
52
|
-
};
|
|
53
|
-
await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
|
|
50
|
+
}, { spaces: 2 });
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
${hasSwagger ? "const swaggerUi = require('swagger-ui-express');\nconst swaggerDoc = require('./swagger.json');" : ''}
|
|
60
|
-
${hasSocket ? "const { createServer } = require('http');\nconst { Server } = require('socket.io');" : ''}
|
|
52
|
+
await fse.writeFile(path.join(bp, 'src', 'server.js'),
|
|
53
|
+
`require('dotenv').config();
|
|
54
|
+
const express = require('express');
|
|
55
|
+
const cors = require('cors');
|
|
61
56
|
|
|
62
57
|
const app = express();
|
|
63
|
-
|
|
64
|
-
${hasCors ? 'app.use(cors());' : ''}
|
|
65
|
-
${hasHelmet ? 'app.use(helmet());' : ''}
|
|
66
|
-
${hasMorgan ? "app.use(morgan('dev'));" : ''}
|
|
58
|
+
app.use(cors());
|
|
67
59
|
app.use(express.json());
|
|
68
60
|
app.use(express.urlencoded({ extended: true }));
|
|
69
61
|
|
|
70
62
|
app.use('/api', require('./routes/index'));
|
|
71
|
-
${hasSwagger ? "app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc));" : ''}
|
|
72
|
-
${hasSocket ? "\nio.on('connection', (socket) => {\n console.log('Cliente conectado:', socket.id);\n socket.on('disconnect', () => console.log('Cliente desconectado'));\n});" : ''}
|
|
73
63
|
|
|
74
64
|
app.get('/', (req, res) => res.json({ status: 'ok', message: 'API funcionando ✅' }));
|
|
75
65
|
|
|
@@ -79,49 +69,60 @@ app.use((err, req, res, next) => {
|
|
|
79
69
|
});
|
|
80
70
|
|
|
81
71
|
const PORT = process.env.PORT || 3001;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
85
|
-
`;
|
|
72
|
+
app.listen(PORT, () => console.log(\`🚀 Servidor en http://localhost:\${PORT}\`));
|
|
73
|
+
`);
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
await fse.writeFile(path.join(bp, 'src', 'routes', 'index.js'),
|
|
76
|
+
`const router = require('express').Router();
|
|
88
77
|
router.get('/health', (req, res) => res.json({ status: 'healthy', timestamp: new Date() }));
|
|
89
78
|
module.exports = router;
|
|
90
|
-
|
|
79
|
+
`);
|
|
91
80
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
81
|
+
console.log(chalk.gray(' → Instalando dependencias Express...'));
|
|
82
|
+
try {
|
|
83
|
+
installNodeDeps(['express', 'cors', 'dotenv', ...dbDeps], ['nodemon'], bp);
|
|
84
|
+
console.log(chalk.green(' ✔ Dependencias instaladas'));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.log(chalk.yellow(' ⚠ Error instalando deps: ' + e.message));
|
|
87
|
+
}
|
|
97
88
|
}
|
|
98
89
|
|
|
99
90
|
// ── NESTJS ────────────────────────────────────────────────────────────────────
|
|
100
91
|
async function nestjs(config, projectPath) {
|
|
101
|
-
run('npx @nestjs/cli@latest new backend --skip-git --package-manager npm', projectPath, 'Creando proyecto NestJS');
|
|
102
92
|
const bp = backendPath(projectPath);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
console.log(chalk.gray(' → Creando proyecto NestJS (puede tardar)...'));
|
|
94
|
+
try {
|
|
95
|
+
execSync('npx @nestjs/cli@latest new backend --skip-git --package-manager npm', {
|
|
96
|
+
cwd: projectPath, stdio: 'pipe',
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {
|
|
99
|
+
throw new Error('Error creando NestJS: ' + e.message);
|
|
100
|
+
}
|
|
101
|
+
const dbDeps = NODE_DB_DEPS[config.db] || [];
|
|
102
|
+
if (dbDeps.length) {
|
|
103
|
+
try {
|
|
104
|
+
execSync(`npm install ${dbDeps.join(' ')}`, { cwd: bp, stdio: 'pipe' });
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// ── FASTIFY ───────────────────────────────────────────────────────────────────
|
|
109
110
|
async function fastify(config, projectPath) {
|
|
110
111
|
const bp = backendPath(projectPath);
|
|
111
|
-
await fse.ensureDir(path.join(bp, 'src'
|
|
112
|
+
await fse.ensureDir(path.join(bp, 'src'));
|
|
113
|
+
|
|
114
|
+
const dbDeps = NODE_DB_DEPS[config.db] || [];
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
await fse.writeJSON(path.join(bp, 'package.json'), {
|
|
114
117
|
name: 'backend', version: '1.0.0', main: 'src/server.js',
|
|
115
118
|
scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
|
|
116
|
-
};
|
|
117
|
-
await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
|
|
119
|
+
}, { spaces: 2 });
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
await fse.writeFile(path.join(bp, 'src', 'server.js'),
|
|
122
|
+
`require('dotenv').config();
|
|
123
|
+
const fastify = require('fastify')({ logger: true });
|
|
121
124
|
|
|
122
|
-
|
|
123
|
-
${deps.includes('@fastify/cors') ? "fastify.register(require('@fastify/cors'));" : ''}
|
|
124
|
-
${deps.includes('@fastify/rate-limit') ? "fastify.register(require('@fastify/rate-limit'), { max: 100, timeWindow: '1 minute' });" : ''}
|
|
125
|
+
fastify.register(require('@fastify/cors'));
|
|
125
126
|
|
|
126
127
|
fastify.get('/', async () => ({ status: 'ok', message: 'API funcionando ✅' }));
|
|
127
128
|
fastify.get('/api/health', async () => ({ status: 'healthy', timestamp: new Date() }));
|
|
@@ -135,25 +136,32 @@ const start = async () => {
|
|
|
135
136
|
}
|
|
136
137
|
};
|
|
137
138
|
start();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
`);
|
|
140
|
+
|
|
141
|
+
console.log(chalk.gray(' → Instalando dependencias Fastify...'));
|
|
142
|
+
try {
|
|
143
|
+
installNodeDeps(['fastify', '@fastify/cors', 'dotenv', ...dbDeps], ['nodemon'], bp);
|
|
144
|
+
console.log(chalk.green(' ✔ Dependencias instaladas'));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
console.log(chalk.yellow(' ⚠ Error instalando deps: ' + e.message));
|
|
147
|
+
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
// ── HONO ──────────────────────────────────────────────────────────────────────
|
|
145
151
|
async function hono(config, projectPath) {
|
|
146
152
|
const bp = backendPath(projectPath);
|
|
147
|
-
await fse.ensureDir(bp);
|
|
153
|
+
await fse.ensureDir(path.join(bp, 'src'));
|
|
148
154
|
|
|
149
|
-
const
|
|
155
|
+
const dbDeps = NODE_DB_DEPS[config.db] || [];
|
|
156
|
+
|
|
157
|
+
await fse.writeJSON(path.join(bp, 'package.json'), {
|
|
150
158
|
name: 'backend', version: '1.0.0', main: 'src/index.js',
|
|
151
159
|
scripts: { start: 'node src/index.js', dev: 'nodemon src/index.js' },
|
|
152
|
-
};
|
|
153
|
-
await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
|
|
160
|
+
}, { spaces: 2 });
|
|
154
161
|
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
await fse.writeFile(path.join(bp, 'src', 'index.js'),
|
|
163
|
+
`require('dotenv').config();
|
|
164
|
+
const { Hono } = require('hono');
|
|
157
165
|
const { serve } = require('@hono/node-server');
|
|
158
166
|
|
|
159
167
|
const app = new Hono();
|
|
@@ -164,11 +172,15 @@ app.get('/api/health', (c) => c.json({ status: 'healthy', timestamp: new Date()
|
|
|
164
172
|
serve({ fetch: app.fetch, port: process.env.PORT || 3001 }, (info) =>
|
|
165
173
|
console.log(\`🚀 Servidor en http://localhost:\${info.port}\`)
|
|
166
174
|
);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
`);
|
|
176
|
+
|
|
177
|
+
console.log(chalk.gray(' → Instalando dependencias Hono...'));
|
|
178
|
+
try {
|
|
179
|
+
installNodeDeps(['hono', '@hono/node-server', 'dotenv', ...dbDeps], ['nodemon'], bp);
|
|
180
|
+
console.log(chalk.green(' ✔ Dependencias instaladas'));
|
|
181
|
+
} catch (e) {
|
|
182
|
+
console.log(chalk.yellow(' ⚠ Error instalando deps: ' + e.message));
|
|
183
|
+
}
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
// ── FASTAPI ───────────────────────────────────────────────────────────────────
|
|
@@ -178,10 +190,20 @@ async function fastapi(config, projectPath) {
|
|
|
178
190
|
await fse.ensureDir(path.join(bp, 'app', 'models'));
|
|
179
191
|
await fse.ensureDir(path.join(bp, 'app', 'schemas'));
|
|
180
192
|
|
|
181
|
-
const
|
|
182
|
-
|
|
193
|
+
const pyDbDeps = {
|
|
194
|
+
postgres: ['psycopg2-binary', 'sqlalchemy'],
|
|
195
|
+
mysql: ['mysqlclient', 'sqlalchemy'],
|
|
196
|
+
mongo: ['motor', 'beanie'],
|
|
197
|
+
sqlite: ['sqlalchemy'],
|
|
198
|
+
supabase: ['supabase'],
|
|
199
|
+
none: [],
|
|
200
|
+
}[config.db] || [];
|
|
201
|
+
|
|
202
|
+
await fse.writeFile(path.join(bp, 'main.py'),
|
|
203
|
+
`from dotenv import load_dotenv
|
|
204
|
+
load_dotenv()
|
|
183
205
|
|
|
184
|
-
|
|
206
|
+
from fastapi import FastAPI
|
|
185
207
|
from fastapi.middleware.cors import CORSMiddleware
|
|
186
208
|
from app.routers import health
|
|
187
209
|
|
|
@@ -193,27 +215,23 @@ app.include_router(health.router, prefix="/api", tags=["health"])
|
|
|
193
215
|
@app.get("/")
|
|
194
216
|
def root():
|
|
195
217
|
return {"status": "ok", "message": "API funcionando ✅"}
|
|
196
|
-
|
|
197
|
-
|
|
218
|
+
`);
|
|
219
|
+
|
|
220
|
+
await fse.writeFile(path.join(bp, 'app', '__init__.py'), '');
|
|
221
|
+
await fse.writeFile(path.join(bp, 'app', 'routers', '__init__.py'), '');
|
|
222
|
+
await fse.writeFile(path.join(bp, 'app', 'routers', 'health.py'),
|
|
223
|
+
`from fastapi import APIRouter
|
|
198
224
|
router = APIRouter()
|
|
199
225
|
|
|
200
226
|
@router.get("/health")
|
|
201
227
|
def health_check():
|
|
202
228
|
return {"status": "healthy"}
|
|
203
|
-
|
|
204
|
-
const requirements = ['fastapi', 'uvicorn[standard]', ...deps].join('\n') + '\n';
|
|
205
|
-
|
|
206
|
-
await fse.writeFile(path.join(bp, 'main.py'), main);
|
|
207
|
-
await fse.writeFile(path.join(bp, 'app', '__init__.py'), '');
|
|
208
|
-
await fse.writeFile(path.join(bp, 'app', 'routers', '__init__.py'), '');
|
|
209
|
-
await fse.writeFile(path.join(bp, 'app', 'routers', 'health.py'), healthRouter);
|
|
229
|
+
`);
|
|
210
230
|
await fse.writeFile(path.join(bp, 'app', 'models', '__init__.py'), '');
|
|
211
231
|
await fse.writeFile(path.join(bp, 'app', 'schemas', '__init__.py'), '');
|
|
212
|
-
await fse.writeFile(path.join(bp, 'requirements.txt'),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (isInstalled('pip')) run('pip install -r requirements.txt', bp, 'Instalando dependencias FastAPI');
|
|
216
|
-
else console.log(chalk.yellow('\n ⚠ pip no encontrado. Ejecuta: pip install -r requirements.txt'));
|
|
232
|
+
await fse.writeFile(path.join(bp, 'requirements.txt'),
|
|
233
|
+
['fastapi', 'uvicorn[standard]', 'python-dotenv', ...pyDbDeps].join('\n') + '\n'
|
|
234
|
+
);
|
|
217
235
|
}
|
|
218
236
|
|
|
219
237
|
// ── DJANGO ────────────────────────────────────────────────────────────────────
|
|
@@ -221,17 +239,32 @@ async function django(config, projectPath) {
|
|
|
221
239
|
const bp = backendPath(projectPath);
|
|
222
240
|
await fse.ensureDir(bp);
|
|
223
241
|
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
const pyDbDeps = {
|
|
243
|
+
postgres: ['psycopg2-binary'],
|
|
244
|
+
mysql: ['mysqlclient'],
|
|
245
|
+
mongo: ['djongo'],
|
|
246
|
+
sqlite: [],
|
|
247
|
+
supabase: [],
|
|
248
|
+
none: [],
|
|
249
|
+
}[config.db] || [];
|
|
250
|
+
|
|
251
|
+
await fse.writeFile(path.join(bp, 'requirements.txt'),
|
|
252
|
+
['django', 'gunicorn', 'django-cors-headers', 'python-dotenv', ...pyDbDeps].join('\n') + '\n'
|
|
229
253
|
);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
254
|
+
await fse.writeFile(path.join(bp, 'README_SETUP.md'),
|
|
255
|
+
`# Django Backend
|
|
256
|
+
|
|
257
|
+
\`\`\`bash
|
|
258
|
+
pip install -r requirements.txt
|
|
259
|
+
django-admin startproject core .
|
|
260
|
+
python manage.py migrate
|
|
261
|
+
python manage.py runserver
|
|
262
|
+
\`\`\`
|
|
263
|
+
`);
|
|
264
|
+
if (isInstalled('django-admin') || isInstalled('python')) {
|
|
265
|
+
try {
|
|
266
|
+
execSync('django-admin startproject core .', { cwd: bp, stdio: 'pipe' });
|
|
267
|
+
} catch {}
|
|
235
268
|
}
|
|
236
269
|
}
|
|
237
270
|
|
|
@@ -240,10 +273,17 @@ async function flask(config, projectPath) {
|
|
|
240
273
|
const bp = backendPath(projectPath);
|
|
241
274
|
await fse.ensureDir(path.join(bp, 'app'));
|
|
242
275
|
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
276
|
+
const pyDbDeps = {
|
|
277
|
+
postgres: ['psycopg2-binary', 'flask-sqlalchemy'],
|
|
278
|
+
mysql: ['mysqlclient', 'flask-sqlalchemy'],
|
|
279
|
+
mongo: ['flask-pymongo'],
|
|
280
|
+
sqlite: ['flask-sqlalchemy'],
|
|
281
|
+
supabase: ['supabase'],
|
|
282
|
+
none: [],
|
|
283
|
+
}[config.db] || [];
|
|
284
|
+
|
|
285
|
+
await fse.writeFile(path.join(bp, 'app', '__init__.py'),
|
|
286
|
+
`from flask import Flask
|
|
247
287
|
from flask_cors import CORS
|
|
248
288
|
|
|
249
289
|
def create_app():
|
|
@@ -259,22 +299,21 @@ def create_app():
|
|
|
259
299
|
return {'status': 'healthy'}
|
|
260
300
|
|
|
261
301
|
return app
|
|
262
|
-
|
|
263
|
-
|
|
302
|
+
`);
|
|
303
|
+
|
|
304
|
+
await fse.writeFile(path.join(bp, 'run.py'),
|
|
305
|
+
`from dotenv import load_dotenv
|
|
306
|
+
load_dotenv()
|
|
307
|
+
from app import create_app
|
|
264
308
|
app = create_app()
|
|
265
309
|
|
|
266
310
|
if __name__ == '__main__':
|
|
267
311
|
app.run(debug=True, port=5000)
|
|
268
|
-
|
|
269
|
-
const requirements = ['flask', 'flask-cors', ...deps].join('\n') + '\n';
|
|
270
|
-
|
|
271
|
-
await fse.writeFile(path.join(bp, 'app', '__init__.py'), appInit);
|
|
272
|
-
await fse.writeFile(path.join(bp, 'run.py'), run_py);
|
|
273
|
-
await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
|
|
274
|
-
if (hasDotenv) await writeDotenv(bp, 'FLASK_ENV=development\nSECRET_KEY=change_me\nDATABASE_URL=\n');
|
|
312
|
+
`);
|
|
275
313
|
|
|
276
|
-
|
|
277
|
-
|
|
314
|
+
await fse.writeFile(path.join(bp, 'requirements.txt'),
|
|
315
|
+
['flask', 'flask-cors', 'python-dotenv', ...pyDbDeps].join('\n') + '\n'
|
|
316
|
+
);
|
|
278
317
|
}
|
|
279
318
|
|
|
280
319
|
// ── SPRING BOOT ───────────────────────────────────────────────────────────────
|
|
@@ -282,10 +321,18 @@ async function spring(config, projectPath) {
|
|
|
282
321
|
const bp = backendPath(projectPath);
|
|
283
322
|
await fse.ensureDir(bp);
|
|
284
323
|
await fse.writeFile(path.join(bp, 'README_SETUP.md'),
|
|
285
|
-
|
|
286
|
-
|
|
324
|
+
`# Spring Boot Backend
|
|
325
|
+
|
|
326
|
+
Genera el proyecto en: https://start.spring.io
|
|
327
|
+
|
|
328
|
+
Dependencias sugeridas: Spring Web, Spring Data JPA, ${config.db === 'postgres' ? 'PostgreSQL Driver' : config.db === 'mysql' ? 'MySQL Driver' : config.db === 'mongo' ? 'Spring Data MongoDB' : 'H2 Database'}
|
|
329
|
+
|
|
330
|
+
\`\`\`bash
|
|
331
|
+
# Con Spring CLI
|
|
332
|
+
spring init --dependencies=web,data-jpa backend
|
|
333
|
+
\`\`\`
|
|
334
|
+
`);
|
|
287
335
|
console.log(chalk.yellow('\n ℹ Spring Boot: genera el proyecto en https://start.spring.io'));
|
|
288
|
-
console.log(chalk.gray(' Se creó un README_SETUP.md con instrucciones.\n'));
|
|
289
336
|
}
|
|
290
337
|
|
|
291
338
|
// ── DENO/OAK ──────────────────────────────────────────────────────────────────
|
|
@@ -293,7 +340,8 @@ async function deno(config, projectPath) {
|
|
|
293
340
|
const bp = backendPath(projectPath);
|
|
294
341
|
await fse.ensureDir(bp);
|
|
295
342
|
|
|
296
|
-
|
|
343
|
+
await fse.writeFile(path.join(bp, 'main.ts'),
|
|
344
|
+
`import { Application, Router } from "https://deno.land/x/oak/mod.ts";
|
|
297
345
|
|
|
298
346
|
const app = new Application();
|
|
299
347
|
const router = new Router();
|
|
@@ -306,11 +354,15 @@ app.use(router.allowedMethods());
|
|
|
306
354
|
|
|
307
355
|
console.log("🚀 Servidor en http://localhost:8000");
|
|
308
356
|
await app.listen({ port: 8000 });
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
await fse.
|
|
312
|
-
|
|
313
|
-
|
|
357
|
+
`);
|
|
358
|
+
|
|
359
|
+
await fse.writeJSON(path.join(bp, 'deno.json'), {
|
|
360
|
+
tasks: { dev: 'deno run --allow-net --allow-env main.ts' },
|
|
361
|
+
}, { spaces: 2 });
|
|
362
|
+
|
|
363
|
+
if (!isInstalled('deno')) {
|
|
364
|
+
console.log(chalk.yellow(' ⚠ Deno no instalado. Instala desde https://deno.land'));
|
|
365
|
+
}
|
|
314
366
|
}
|
|
315
367
|
|
|
316
368
|
// ── GO/GIN ────────────────────────────────────────────────────────────────────
|
|
@@ -318,7 +370,8 @@ async function gin(config, projectPath) {
|
|
|
318
370
|
const bp = backendPath(projectPath);
|
|
319
371
|
await fse.ensureDir(bp);
|
|
320
372
|
|
|
321
|
-
|
|
373
|
+
await fse.writeFile(path.join(bp, 'main.go'),
|
|
374
|
+
`package main
|
|
322
375
|
|
|
323
376
|
import (
|
|
324
377
|
"net/http"
|
|
@@ -337,13 +390,19 @@ func main() {
|
|
|
337
390
|
|
|
338
391
|
r.Run(":8080")
|
|
339
392
|
}
|
|
340
|
-
|
|
341
|
-
|
|
393
|
+
`);
|
|
394
|
+
|
|
342
395
|
await fse.writeFile(path.join(bp, 'README_SETUP.md'),
|
|
343
|
-
|
|
344
|
-
);
|
|
345
|
-
console.log(chalk.green('✔ Backend Go/Gin generado'));
|
|
346
|
-
if (!isInstalled('go')) console.log(chalk.yellow(' ⚠ Go no instalado. Instala desde https://go.dev'));
|
|
347
|
-
}
|
|
396
|
+
`# Go/Gin Backend
|
|
348
397
|
|
|
349
|
-
|
|
398
|
+
\`\`\`bash
|
|
399
|
+
go mod init backend
|
|
400
|
+
go get github.com/gin-gonic/gin
|
|
401
|
+
go run main.go
|
|
402
|
+
\`\`\`
|
|
403
|
+
`);
|
|
404
|
+
|
|
405
|
+
if (!isInstalled('go')) {
|
|
406
|
+
console.log(chalk.yellow(' ⚠ Go no instalado. Instala desde https://go.dev'));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import fse from 'fs-extra';
|
|
4
|
-
import {
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
5
|
|
|
6
6
|
// Comandos de scaffolding por framework
|
|
7
7
|
const SCAFFOLD = {
|
|
8
8
|
react: 'npm create vite@latest frontend -- --template react',
|
|
9
9
|
vue: 'npm create vite@latest frontend -- --template vue',
|
|
10
10
|
solid: 'npm create vite@latest frontend -- --template solid',
|
|
11
|
-
svelte: '
|
|
11
|
+
svelte: 'npx sv create frontend --template minimal --no-add-ons',
|
|
12
12
|
astro: 'npm create astro@latest frontend -- --template minimal --no-install --no-git',
|
|
13
13
|
next: 'npx create-next-app@latest frontend --yes --no-git',
|
|
14
14
|
nuxt: 'npx nuxi@latest init frontend --no-install',
|
|
@@ -17,65 +17,16 @@ const SCAFFOLD = {
|
|
|
17
17
|
qwik: 'npm create qwik@latest frontend -- --no-install',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
async function generateFrontend(config, projectPath) {
|
|
20
|
+
export async function generateFrontend(config, projectPath) {
|
|
21
21
|
const cmd = SCAFFOLD[config.frontend];
|
|
22
22
|
if (!cmd) throw new Error(`Frontend no soportado: ${config.frontend}`);
|
|
23
23
|
|
|
24
|
-
console.log(chalk.blue(`\n⚛ Generando frontend (${chalk.bold(config.frontend)})...`));
|
|
25
|
-
run(cmd, projectPath, `Scaffolding ${config.frontend}`);
|
|
26
|
-
|
|
27
|
-
const frontendPath = path.join(projectPath, 'frontend');
|
|
28
|
-
|
|
29
|
-
// Instalar dependencias base
|
|
30
|
-
run('npm install', frontendPath, 'Instalando dependencias base del frontend');
|
|
31
|
-
|
|
32
|
-
// Separar tailwind del resto (va como devDependency)
|
|
33
|
-
const tailwindPkgs = ['tailwindcss', '@astrojs/tailwind', '@nuxtjs/tailwindcss'];
|
|
34
|
-
const devDeps = (config.frontendDeps || []).filter(d => tailwindPkgs.includes(d));
|
|
35
|
-
const prodDeps = (config.frontendDeps || []).filter(d => !tailwindPkgs.includes(d) && d !== '@shadcn/ui');
|
|
36
|
-
|
|
37
|
-
if (prodDeps.length) {
|
|
38
|
-
run(`npm install ${prodDeps.join(' ')}`, frontendPath, `Instalando: ${prodDeps.join(', ')}`);
|
|
39
|
-
}
|
|
40
|
-
if (devDeps.length) {
|
|
41
|
-
run(`npm install -D ${devDeps.join(' ')}`, frontendPath, `Instalando dev: ${devDeps.join(', ')}`);
|
|
42
|
-
if (devDeps.some(d => tailwindPkgs.includes(d))) {
|
|
43
|
-
await setupTailwind(config.frontend, frontendPath);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function setupTailwind(framework, frontendPath) {
|
|
49
|
-
// Astro y Nuxt tienen su propia integración, no necesitan init manual
|
|
50
|
-
if (['astro', 'nuxt'].includes(framework)) return;
|
|
24
|
+
console.log(chalk.blue(`\n ⚛ Generando frontend (${chalk.bold(config.frontend)})...`));
|
|
51
25
|
|
|
52
26
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
module.exports = {
|
|
58
|
-
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue,svelte}'],
|
|
59
|
-
theme: { extend: {} },
|
|
60
|
-
plugins: [],
|
|
61
|
-
};
|
|
62
|
-
`;
|
|
63
|
-
await fse.writeFile(path.join(frontendPath, 'tailwind.config.js'), tailwindConfig);
|
|
64
|
-
|
|
65
|
-
const cssPath = path.join(frontendPath, 'src', 'index.css');
|
|
66
|
-
const directives = `@tailwind base;
|
|
67
|
-
@tailwind components;
|
|
68
|
-
@tailwind utilities;
|
|
69
|
-
|
|
70
|
-
`;
|
|
71
|
-
if (await fse.pathExists(cssPath)) {
|
|
72
|
-
const existing = await fse.readFile(cssPath, 'utf8');
|
|
73
|
-
await fse.writeFile(cssPath, directives + existing);
|
|
74
|
-
} else {
|
|
75
|
-
await fse.ensureDir(path.join(frontendPath, 'src'));
|
|
76
|
-
await fse.writeFile(cssPath, directives);
|
|
27
|
+
execSync(cmd, { cwd: projectPath, stdio: 'pipe' });
|
|
28
|
+
console.log(chalk.green(` ✔ Scaffolding ${config.frontend} completado`));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
throw new Error(`Error generando frontend ${config.frontend}: ${e.message}`);
|
|
77
31
|
}
|
|
78
|
-
console.log(chalk.green('✔ Tailwind CSS configurado'));
|
|
79
32
|
}
|
|
80
|
-
|
|
81
|
-
export { generateFrontend };
|
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/package.json
CHANGED