novatec-cli 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/create.js CHANGED
@@ -1,277 +1,378 @@
1
1
  import path from 'path';
2
2
  import chalk from 'chalk';
3
3
  import fse from 'fs-extra';
4
+ import ora from 'ora';
5
+ import { execSync, spawnSync } from 'child_process';
4
6
  import { askProjectOptions } from './prompts.js';
5
7
  import { generateFrontend } from './generators/frontend.js';
6
8
  import { generateBackend } from './generators/backend.js';
7
9
  import { generateReadme } from './generators/readme.js';
8
10
  import { run, isInstalled } from './utils.js';
11
+ import boxen from 'boxen';
9
12
 
10
- const boxen = await import('boxen');
13
+ // ── PROGRESO ──────────────────────────────────────────────────────────────────
14
+ function progressBar(current, total, label) {
15
+ const width = 30;
16
+ const filled = Math.round((current / total) * width);
17
+ const empty = width - filled;
18
+ const pct = Math.round((current / total) * 100);
19
+ const bar = chalk.white('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
20
+ process.stdout.write(`\r [${bar}] ${chalk.white(pct + '%')} ${chalk.gray(label)} `);
21
+ }
11
22
 
12
- async function runCreate(nameArg, options) {
23
+ // ── MAIN ──────────────────────────────────────────────────────────────────────
24
+ export async function runCreate(nameArg, options) {
25
+ const startTime = Date.now();
13
26
  const config = await askProjectOptions(nameArg, options);
14
27
  const projectPath = path.resolve(process.cwd(), config.name);
15
28
 
16
- if (await fse.pathExists(projectPath)) {
17
- throw new Error(`La carpeta "${config.name}" ya existe en este directorio.`);
18
- }
19
-
20
- console.log(chalk.cyan(`\n📁 Creando proyecto en: ${chalk.bold(projectPath)}\n`));
29
+ // Asegurar carpeta
21
30
  await fse.ensureDir(projectPath);
22
31
 
23
- await generateFrontend(config, projectPath);
24
- await generateBackend(config, projectPath);
25
- await generateReadme(config, projectPath);
32
+ const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
33
+ const isNode = !isPython && config.backend !== 'spring' && config.backend !== 'gin';
26
34
 
35
+ // Calcular pasos totales
27
36
  const extras = config.extras || [];
37
+ const steps = [
38
+ 'Generando frontend',
39
+ 'Generando backend',
40
+ 'Generando README',
41
+ extras.includes('env') && 'Archivos .env',
42
+ extras.includes('editorconfig') && '.editorconfig',
43
+ extras.includes('eslint') && 'ESLint + Prettier',
44
+ extras.includes('husky') && 'Husky',
45
+ extras.includes('testing') && 'Vitest',
46
+ extras.includes('ci') && 'GitHub Actions',
47
+ extras.includes('docker') && 'Docker',
48
+ extras.includes('auth') && 'Auth JWT',
49
+ extras.includes('tailwind') && 'Tailwind CSS',
50
+ extras.includes('git') && 'Git init',
51
+ config.frontendDeps?.length && 'Instalando deps frontend',
52
+ config.backendDeps?.length && 'Instalando deps backend',
53
+ ].filter(Boolean);
54
+
55
+ console.log();
56
+ let step = 0;
57
+
58
+ const tick = (label) => {
59
+ step++;
60
+ progressBar(step, steps.length, label);
61
+ };
62
+
63
+ // 1. Frontend
64
+ tick('Generando frontend...');
65
+ await generateFrontend(config, projectPath);
28
66
 
29
- if (extras.includes('env')) await generateEnvFiles(config, projectPath);
30
- if (extras.includes('editorconfig')) await fse.writeFile(path.join(projectPath, '.editorconfig'), EDITORCONFIG);
31
- if (extras.includes('eslint')) await setupEslint(config, projectPath);
32
- if (extras.includes('husky')) await setupHusky(config, projectPath);
33
- if (extras.includes('testing')) await setupTesting(config, projectPath);
34
- if (extras.includes('ci')) await setupCI(config, projectPath);
35
- if (extras.includes('docker')) await generateDocker(config, projectPath);
67
+ // 2. Backend
68
+ tick('Generando backend...');
69
+ await generateBackend(config, projectPath);
70
+
71
+ // 3. README personalizado
72
+ tick('Generando README...');
73
+ await generateCustomReadme(config, projectPath);
36
74
 
75
+ // 4. Extras
76
+ if (extras.includes('env')) {
77
+ tick('Archivos .env...');
78
+ await generateEnvFiles(config, projectPath);
79
+ }
80
+ if (extras.includes('editorconfig')) {
81
+ tick('.editorconfig...');
82
+ await fse.writeFile(path.join(projectPath, '.editorconfig'), EDITORCONFIG);
83
+ }
84
+ if (extras.includes('eslint')) {
85
+ tick('ESLint + Prettier...');
86
+ await setupEslint(config, projectPath);
87
+ }
88
+ if (extras.includes('husky')) {
89
+ tick('Husky...');
90
+ await setupHusky(config, projectPath);
91
+ }
92
+ if (extras.includes('testing')) {
93
+ tick('Vitest...');
94
+ await setupTesting(config, projectPath);
95
+ }
96
+ if (extras.includes('ci')) {
97
+ tick('GitHub Actions...');
98
+ await setupCI(config, projectPath);
99
+ }
100
+ if (extras.includes('docker')) {
101
+ tick('Docker...');
102
+ await generateDocker(config, projectPath);
103
+ }
104
+ if (extras.includes('auth')) {
105
+ tick('Auth JWT...');
106
+ await setupAuth(config, projectPath);
107
+ }
108
+ if (extras.includes('tailwind')) {
109
+ tick('Tailwind CSS...');
110
+ await setupTailwind(config, projectPath);
111
+ }
37
112
  if (extras.includes('git') && isInstalled('git')) {
38
- try { run('git init', projectPath, 'Inicializando repositorio git'); } catch {}
113
+ tick('Git init...');
114
+ try { run('git init', projectPath, 'git init'); } catch {}
39
115
  await fse.writeFile(path.join(projectPath, '.gitignore'), GITIGNORE);
40
116
  }
41
117
 
118
+ // 5. Instalar dependencias automáticamente
119
+ if (config.frontendDeps?.length && isInstalled('npm')) {
120
+ tick('Instalando deps frontend...');
121
+ const frontendPath = path.join(projectPath, 'frontend');
122
+ try {
123
+ spawnSync('npm', ['install', ...config.frontendDeps], { cwd: frontendPath, shell: true, stdio: 'pipe' });
124
+ } catch {}
125
+ }
126
+ if (config.backendDeps?.length && isNode && isInstalled('npm')) {
127
+ tick('Instalando deps backend...');
128
+ const backendPath = path.join(projectPath, 'backend');
129
+ try {
130
+ spawnSync('npm', ['install', ...config.backendDeps], { cwd: backendPath, shell: true, stdio: 'pipe' });
131
+ } catch {}
132
+ }
133
+
134
+ // Barra al 100%
135
+ progressBar(steps.length, steps.length, 'Completado');
136
+ console.log('\n');
137
+
138
+ // Tiempo total
139
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
140
+
42
141
  // Resumen final
43
142
  const backendCmd = {
44
- express: 'npm run dev',
45
- nestjs: 'npm run start:dev',
46
- fastify: 'npm run dev',
47
- hono: 'npm run dev',
48
- fastapi: 'uvicorn main:app --reload',
49
- django: 'python manage.py runserver',
50
- flask: 'python run.py',
51
- spring: 'mvn spring-boot:run',
52
- deno: 'deno task dev',
53
- gin: 'go run main.go'
143
+ express: 'npm run dev', nestjs: 'npm run start:dev', fastify: 'npm run dev',
144
+ hono: 'npm run dev', fastapi: 'uvicorn main:app --reload', django: 'python manage.py runserver',
145
+ flask: 'python run.py', spring: 'mvn spring-boot:run', deno: 'deno task dev', gin: 'go run main.go',
54
146
  };
55
147
 
56
- // Resumen final
57
148
  console.log(
58
- boxen.default(
59
- chalk.bold.white(' ✔ PROYECTO CREADO EXITOSAMENTE\n\n') +
60
- chalk.white(' Nombre ') + chalk.gray('│ ') + chalk.bold.white(config.name) + '\n' +
61
- chalk.white(' Frontend ') + chalk.gray('│ ') + chalk.bold.white(config.frontend) + '\n' +
62
- chalk.white(' Backend ') + chalk.gray('│ ') + chalk.bold.white(config.backend) + '\n\n' +
63
- chalk.gray(' ─────────────────────────────────────────\n\n') +
64
- chalk.white(' Iniciar Frontend\n') +
65
- chalk.gray(` cd ${config.name}/frontend\n`) +
66
- chalk.white(' npm run dev\n\n') +
67
- chalk.white(' Iniciar Backend\n') +
68
- chalk.gray(` cd ${config.name}/backend\n`) +
69
- chalk.white(` ${backendCmd[config.backend] || 'npm start'}`),
70
- {
71
- padding: 1,
72
- margin: { top: 1, left: 2 },
73
- borderStyle: 'round',
74
- borderColor: 'white',
75
- dimBorder: true,
76
- }
149
+ boxen(
150
+ chalk.bold.white(' ✔ PROYECTO LISTO\n\n') +
151
+ chalk.gray(' Nombre │ ') + chalk.white(config.name) + '\n' +
152
+ chalk.gray(' Frontend ') + chalk.white(config.frontend) + (config.typescript ? chalk.gray(' + TypeScript') : '') + '\n' +
153
+ chalk.gray(' Backend │ ') + chalk.white(config.backend) + '\n' +
154
+ chalk.gray(' Base datos │ ') + chalk.white(config.db || 'ninguna') + '\n' +
155
+ chalk.gray(' Tiempo │ ') + chalk.white(elapsed + 's') + '\n\n' +
156
+ chalk.gray(' ─────────────────────────────────────\n\n') +
157
+ chalk.white(' cd ' + config.name + '/frontend → npm run dev\n') +
158
+ chalk.white(' cd ' + config.name + '/backend → ' + (backendCmd[config.backend] || 'npm start')),
159
+ { padding: 1, margin: { top: 1, left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
77
160
  )
78
161
  );
162
+
163
+ // Abrir VS Code si está instalado
164
+ if (isInstalled('code')) {
165
+ try {
166
+ spawnSync('code', [projectPath], { shell: true, stdio: 'ignore', detached: true });
167
+ console.log(' ' + chalk.gray('VS Code abierto automáticamente ✔'));
168
+ } catch {}
169
+ }
170
+
171
+ console.log();
172
+ }
173
+
174
+ // ── README PERSONALIZADO ──────────────────────────────────────────────────────
175
+ async function generateCustomReadme(config, projectPath) {
176
+ const ts = config.typescript ? ' + TypeScript' : '';
177
+ const dbSection = config.db && config.db !== 'none'
178
+ ? `\n## Base de datos\n\n**${config.db}** — Configura la conexión en \`backend/.env\`\n` : '';
179
+
180
+ const content = `# ${config.name}
181
+
182
+ > Proyecto generado con [NOVATEC CLI](https://www.npmjs.com/package/novatec-cli)
183
+
184
+ ## Stack
185
+
186
+ | Capa | Tecnología |
187
+ |------|-----------|
188
+ | Frontend | **${config.frontend}**${ts} |
189
+ | Backend | **${config.backend}**${ts} |
190
+ | Base de datos | **${config.db || 'No configurada'}** |
191
+ ${dbSection}
192
+ ## Inicio rápido
193
+
194
+ \`\`\`bash
195
+ # Frontend
196
+ cd ${config.name}/frontend
197
+ npm install
198
+ npm run dev
199
+
200
+ # Backend
201
+ cd ${config.name}/backend
202
+ ${['fastapi','django','flask'].includes(config.backend) ? 'pip install -r requirements.txt\n' : 'npm install\n'}\
203
+ ${config.backend === 'fastapi' ? 'uvicorn main:app --reload' :
204
+ config.backend === 'django' ? 'python manage.py runserver' :
205
+ config.backend === 'flask' ? 'python run.py' :
206
+ config.backend === 'spring' ? 'mvn spring-boot:run' :
207
+ config.backend === 'gin' ? 'go run main.go' : 'npm run dev'}
208
+ \`\`\`
209
+ ${config.extras?.includes('docker') ? '\n## Docker\n\n```bash\ndocker-compose up --build\n```\n' : ''}
210
+ ## Generado con
211
+
212
+ \`\`\`
213
+ novatec create ${config.name}
214
+ \`\`\`
215
+
216
+ ---
217
+ *Generado el ${new Date().toLocaleDateString('es-MX')} con NOVATEC CLI*
218
+ `;
219
+ await fse.writeFile(path.join(projectPath, 'README.md'), content);
79
220
  }
80
221
 
81
- // ── ESLint + Prettier ─────────────────────────────────────────────────────────
222
+ // ── AUTH JWT ──────────────────────────────────────────────────────────────────
223
+ async function setupAuth(config, projectPath) {
224
+ const backendPath = path.join(projectPath, 'backend');
225
+ const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
226
+
227
+ if (isPython) return; // Django/FastAPI ya tienen su propio auth
228
+
229
+ const authDir = path.join(backendPath, 'src', 'auth');
230
+ await fse.ensureDir(authDir);
231
+
232
+ const middleware = `import jwt from 'jsonwebtoken';
233
+
234
+ export function authMiddleware(req, res, next) {
235
+ const token = req.headers.authorization?.split(' ')[1];
236
+ if (!token) return res.status(401).json({ message: 'Token requerido' });
237
+ try {
238
+ req.user = jwt.verify(token, process.env.JWT_SECRET || 'change_me');
239
+ next();
240
+ } catch {
241
+ res.status(401).json({ message: 'Token inválido' });
242
+ }
243
+ }
244
+ `;
245
+ const authRoutes = `import { Router } from 'express';
246
+ import jwt from 'jsonwebtoken';
247
+ import bcrypt from 'bcryptjs';
248
+
249
+ const router = Router();
250
+
251
+ // POST /api/auth/register
252
+ router.post('/register', async (req, res) => {
253
+ const { email, password } = req.body;
254
+ const hash = await bcrypt.hash(password, 10);
255
+ // TODO: guardar usuario en BD
256
+ const token = jwt.sign({ email }, process.env.JWT_SECRET || 'change_me', { expiresIn: '7d' });
257
+ res.json({ token });
258
+ });
259
+
260
+ // POST /api/auth/login
261
+ router.post('/login', async (req, res) => {
262
+ const { email, password } = req.body;
263
+ // TODO: buscar usuario en BD y verificar hash
264
+ const token = jwt.sign({ email }, process.env.JWT_SECRET || 'change_me', { expiresIn: '7d' });
265
+ res.json({ token });
266
+ });
267
+
268
+ export default router;
269
+ `;
270
+
271
+ await fse.writeFile(path.join(authDir, 'middleware.js'), middleware);
272
+ await fse.writeFile(path.join(authDir, 'routes.js'), authRoutes);
273
+
274
+ // Instalar deps de auth
275
+ try {
276
+ spawnSync('npm', ['install', 'jsonwebtoken', 'bcryptjs'], { cwd: backendPath, shell: true, stdio: 'pipe' });
277
+ } catch {}
278
+ }
279
+
280
+ // ── TAILWIND AUTO ─────────────────────────────────────────────────────────────
281
+ async function setupTailwind(config, projectPath) {
282
+ const frontendPath = path.join(projectPath, 'frontend');
283
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
284
+ export default {
285
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue,svelte}'],
286
+ theme: { extend: {} },
287
+ plugins: [],
288
+ }
289
+ `;
290
+ const postcssConfig = `export default {
291
+ plugins: { tailwindcss: {}, autoprefixer: {} },
292
+ }
293
+ `;
294
+ await fse.writeFile(path.join(frontendPath, 'tailwind.config.js'), tailwindConfig);
295
+ await fse.writeFile(path.join(frontendPath, 'postcss.config.js'), postcssConfig);
296
+
297
+ // Agregar imports al CSS principal
298
+ const cssPath = path.join(frontendPath, 'src', 'index.css');
299
+ const tailwindImports = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n`;
300
+ if (await fse.pathExists(cssPath)) {
301
+ const existing = await fse.readFile(cssPath, 'utf8');
302
+ await fse.writeFile(cssPath, tailwindImports + existing);
303
+ } else {
304
+ await fse.ensureDir(path.join(frontendPath, 'src'));
305
+ await fse.writeFile(cssPath, tailwindImports);
306
+ }
307
+
308
+ try {
309
+ spawnSync('npm', ['install', '-D', 'tailwindcss', 'postcss', 'autoprefixer'], { cwd: frontendPath, shell: true, stdio: 'pipe' });
310
+ } catch {}
311
+ }
312
+
313
+ // ── ESLint ────────────────────────────────────────────────────────────────────
82
314
  async function setupEslint(config, projectPath) {
83
315
  const frontendPath = path.join(projectPath, 'frontend');
84
- const eslintConfig = `{
85
- "env": { "browser": true, "es2021": true, "node": true },
86
- "extends": ["eslint:recommended"],
87
- "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
88
- "rules": { "no-unused-vars": "warn", "no-console": "off" }
89
- }\n`;
90
- const prettierConfig = `{
91
- "semi": true,
92
- "singleQuote": true,
93
- "tabWidth": 2,
94
- "trailingComma": "es5"
95
- }\n`;
96
-
97
- await fse.writeFile(path.join(frontendPath, '.eslintrc.json'), eslintConfig);
98
- await fse.writeFile(path.join(frontendPath, '.prettierrc'), prettierConfig);
99
- run('npm install -D eslint prettier eslint-config-prettier', frontendPath, 'Instalando ESLint + Prettier');
100
- console.log(chalk.green('✔ ESLint + Prettier configurados'));
316
+ await fse.writeFile(path.join(frontendPath, '.eslintrc.json'), `{"env":{"browser":true,"es2021":true},"extends":["eslint:recommended"],"parserOptions":{"ecmaVersion":"latest","sourceType":"module"},"rules":{"no-unused-vars":"warn"}}\n`);
317
+ await fse.writeFile(path.join(frontendPath, '.prettierrc'), `{"semi":true,"singleQuote":true,"tabWidth":2,"trailingComma":"es5"}\n`);
318
+ try { spawnSync('npm', ['install', '-D', 'eslint', 'prettier'], { cwd: frontendPath, shell: true, stdio: 'pipe' }); } catch {}
101
319
  }
102
320
 
103
- // ── Husky + lint-staged ───────────────────────────────────────────────────────
321
+ // ── Husky ─────────────────────────────────────────────────────────────────────
104
322
  async function setupHusky(config, projectPath) {
105
323
  const frontendPath = path.join(projectPath, 'frontend');
106
324
  try {
107
- run('npm install -D husky lint-staged', frontendPath, 'Instalando Husky + lint-staged');
108
- run('npx husky init', frontendPath, 'Inicializando Husky');
325
+ spawnSync('npm', ['install', '-D', 'husky', 'lint-staged'], { cwd: frontendPath, shell: true, stdio: 'pipe' });
326
+ spawnSync('npx', ['husky', 'init'], { cwd: frontendPath, shell: true, stdio: 'pipe' });
327
+ await fse.ensureDir(path.join(frontendPath, '.husky'));
109
328
  await fse.writeFile(path.join(frontendPath, '.husky', 'pre-commit'), '#!/bin/sh\nnpx lint-staged\n');
110
- const pkgPath = path.join(frontendPath, 'package.json');
111
- const pkg = await fse.readJSON(pkgPath);
112
- pkg['lint-staged'] = { '*.{js,jsx,ts,tsx,vue}': ['eslint --fix', 'prettier --write'] };
113
- await fse.writeJSON(pkgPath, pkg, { spaces: 2 });
114
- console.log(chalk.green('✔ Husky + lint-staged configurados'));
115
- } catch { console.log(chalk.yellow('⚠ Husky requiere git init primero')); }
329
+ } catch {}
116
330
  }
117
331
 
118
332
  // ── Testing ───────────────────────────────────────────────────────────────────
119
333
  async function setupTesting(config, projectPath) {
120
334
  const frontendPath = path.join(projectPath, 'frontend');
121
- const isReactLike = ['react', 'next', 'solid', 'remix', 'qwik'].includes(config.frontend);
122
-
123
- run('npm install -D vitest @vitest/ui', frontendPath, 'Instalando Vitest');
124
-
125
335
  const testDir = path.join(frontendPath, 'src', '__tests__');
126
336
  await fse.ensureDir(testDir);
127
-
128
- const sampleTest = isReactLike
129
- ? `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('should work', () => {\n expect(1 + 1).toBe(2);\n });\n});\n`
130
- : `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('should work', () => {\n expect(true).toBe(true);\n });\n});\n`;
131
-
132
- await fse.writeFile(path.join(testDir, 'app.test.js'), sampleTest);
133
-
134
- const pkgPath = path.join(frontendPath, 'package.json');
135
- const pkg = await fse.readJSON(pkgPath);
136
- pkg.scripts = { ...pkg.scripts, test: 'vitest', 'test:ui': 'vitest --ui' };
137
- await fse.writeJSON(pkgPath, pkg, { spaces: 2 });
138
- console.log(chalk.green('✔ Vitest configurado con test de ejemplo'));
337
+ await fse.writeFile(path.join(testDir, 'app.test.js'), `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('works', () => expect(1 + 1).toBe(2));\n});\n`);
338
+ try { spawnSync('npm', ['install', '-D', 'vitest'], { cwd: frontendPath, shell: true, stdio: 'pipe' }); } catch {}
139
339
  }
140
340
 
141
- // ── GitHub Actions CI ─────────────────────────────────────────────────────────
341
+ // ── GitHub Actions ────────────────────────────────────────────────────────────
142
342
  async function setupCI(config, projectPath) {
143
343
  const ciDir = path.join(projectPath, '.github', 'workflows');
144
344
  await fse.ensureDir(ciDir);
145
-
146
- const ci = `name: CI
147
-
148
- on:
149
- push:
150
- branches: [main, develop]
151
- pull_request:
152
- branches: [main]
153
-
154
- jobs:
155
- frontend:
156
- runs-on: ubuntu-latest
157
- defaults:
158
- run:
159
- working-directory: frontend
160
- steps:
161
- - uses: actions/checkout@v4
162
- - uses: actions/setup-node@v4
163
- with:
164
- node-version: 20
165
- cache: npm
166
- cache-dependency-path: frontend/package-lock.json
167
- - run: npm ci
168
- - run: npm run build
169
- ${config.extras.includes('testing') ? ' - run: npm test\n' : ''}
170
- backend:
171
- runs-on: ubuntu-latest
172
- defaults:
173
- run:
174
- working-directory: backend
175
- steps:
176
- - uses: actions/checkout@v4
177
- ${['fastapi', 'django', 'flask'].includes(config.backend) ? ` - uses: actions/setup-python@v5
178
- with:
179
- python-version: '3.11'
180
- - run: pip install -r requirements.txt` : ` - uses: actions/setup-node@v4
181
- with:
182
- node-version: 20
183
- - run: npm ci`}
184
- `;
185
- await fse.writeFile(path.join(ciDir, 'ci.yml'), ci);
186
- console.log(chalk.green('✔ GitHub Actions CI configurado'));
345
+ const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
346
+ await fse.writeFile(path.join(ciDir, 'ci.yml'), `name: CI\non:\n push:\n branches: [main]\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n - run: cd frontend && npm ci && npm run build\n${isPython ? '' : ' - run: cd backend && npm ci\n'}`);
187
347
  }
188
348
 
189
349
  // ── Docker ────────────────────────────────────────────────────────────────────
190
350
  async function generateDocker(config, projectPath) {
191
351
  const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
192
- const backendPort = isPython ? (config.backend === 'gin' ? '8080' : '8000') : '3001';
193
-
194
- const backendService = isPython
195
- ? ` backend:
196
- build: ./backend
197
- ports:
198
- - "${backendPort}:${backendPort}"
199
- volumes:
200
- - ./backend:/app
201
- environment:
202
- - DEBUG=True`
203
- : ` backend:
204
- build: ./backend
205
- ports:
206
- - "${backendPort}:${backendPort}"
207
- volumes:
208
- - ./backend:/app
209
- - /app/node_modules
210
- environment:
211
- - NODE_ENV=development`;
212
-
213
- const compose = `version: '3.9'
214
- services:
215
- frontend:
216
- build: ./frontend
217
- ports:
218
- - "5173:5173"
219
- volumes:
220
- - ./frontend:/app
221
- - /app/node_modules
222
- environment:
223
- - NODE_ENV=development
224
- ${backendService}
225
- db:
226
- image: postgres:16-alpine
227
- environment:
228
- POSTGRES_DB: mydb
229
- POSTGRES_USER: user
230
- POSTGRES_PASSWORD: password
231
- ports:
232
- - "5432:5432"
233
- volumes:
234
- - pgdata:/var/lib/postgresql/data
235
-
236
- volumes:
237
- pgdata:
238
- `;
239
- await fse.writeFile(path.join(projectPath, 'docker-compose.yml'), compose);
240
- console.log(chalk.green('✔ docker-compose.yml generado (con PostgreSQL)'));
352
+ const backendPort = isPython ? '8000' : '3001';
353
+ const dbService = config.db === 'postgres' ? `\n db:\n image: postgres:16-alpine\n environment:\n POSTGRES_DB: mydb\n POSTGRES_USER: user\n POSTGRES_PASSWORD: password\n ports:\n - "5432:5432"\n volumes:\n - pgdata:/var/lib/postgresql/data\n\nvolumes:\n pgdata:` :
354
+ config.db === 'mongo' ? `\n db:\n image: mongo:7\n ports:\n - "27017:27017"\n volumes:\n - mongodata:/data/db\n\nvolumes:\n mongodata:` :
355
+ config.db === 'mysql' ? `\n db:\n image: mysql:8\n environment:\n MYSQL_DATABASE: mydb\n MYSQL_ROOT_PASSWORD: password\n ports:\n - "3306:3306"` : '';
356
+
357
+ await fse.writeFile(path.join(projectPath, 'docker-compose.yml'),
358
+ `version: '3.9'\nservices:\n frontend:\n build: ./frontend\n ports:\n - "5173:5173"\n backend:\n build: ./backend\n ports:\n - "${backendPort}:${backendPort}"\n env_file: ./backend/.env${dbService}\n`
359
+ );
241
360
  }
242
361
 
243
- // ── Archivos .env ─────────────────────────────────────────────────────────────
362
+ // ── .env ──────────────────────────────────────────────────────────────────────
244
363
  async function generateEnvFiles(config, projectPath) {
245
364
  await fse.writeFile(path.join(projectPath, 'frontend', '.env.example'), 'VITE_API_URL=http://localhost:3001\n');
246
365
  const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
366
+ const dbUrl = config.db === 'postgres' ? 'postgresql://user:password@localhost:5432/mydb' :
367
+ config.db === 'mysql' ? 'mysql://user:password@localhost:3306/mydb' :
368
+ config.db === 'mongo' ? 'mongodb://localhost:27017/mydb' :
369
+ config.db === 'sqlite' ? 'file:./dev.db' : '';
247
370
  if (!isPython) {
248
- await fse.writeFile(path.join(projectPath, 'backend', '.env.example'), 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\nNODE_ENV=development\n');
371
+ await fse.writeFile(path.join(projectPath, 'backend', '.env.example'),
372
+ `PORT=3001\nNODE_ENV=development\nJWT_SECRET=change_me_in_production\n${dbUrl ? 'DATABASE_URL=' + dbUrl + '\n' : ''}`
373
+ );
249
374
  }
250
- console.log(chalk.green('✔ Archivos .env.example generados'));
251
375
  }
252
376
 
253
- const EDITORCONFIG = `root = true
254
- [*]
255
- indent_style = space
256
- indent_size = 2
257
- end_of_line = lf
258
- charset = utf-8
259
- trim_trailing_whitespace = true
260
- insert_final_newline = true
261
- `;
262
-
263
- const GITIGNORE = `node_modules/
264
- dist/
265
- .next/
266
- .nuxt/
267
- .svelte-kit/
268
- .env
269
- *.log
270
- __pycache__/
271
- .venv/
272
- *.pyc
273
- target/
274
- *.class
275
- `;
276
-
277
- export { runCreate };
377
+ const EDITORCONFIG = `root = true\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n`;
378
+ const GITIGNORE = `node_modules/\ndist/\n.next/\n.nuxt/\n.svelte-kit/\n.env\n*.log\n__pycache__/\n.venv/\n*.pyc\ntarget/\n*.class\n.DS_Store\n`;