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.
@@ -1,24 +1,40 @@
1
1
  import path from 'path';
2
2
  import chalk from 'chalk';
3
3
  import fse from 'fs-extra';
4
- import { run, isInstalled } from '../utils.js';
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
+ };
16
+
17
+ function installNodeDeps(prod, dev, cwd) {
18
+ if (prod.length) {
19
+ execSync(`npm install ${prod.join(' ')}`, { cwd, stdio: 'pipe' });
20
+ }
21
+ if (dev && dev.length) {
22
+ execSync(`npm install -D ${dev.join(' ')}`, { cwd, stdio: 'pipe' });
23
+ }
24
+ }
25
+
26
+ function backendPath(projectPath) {
27
+ return path.join(projectPath, 'backend');
28
+ }
5
29
 
6
- async function generateBackend(config, projectPath) {
7
- console.log(chalk.blue(`\nπŸ”§ Generando backend (${chalk.bold(config.backend)})...`));
30
+ export async function generateBackend(config, projectPath) {
31
+ console.log(chalk.blue(`\n βš™ Generando backend (${chalk.bold(config.backend)})...`));
8
32
  const handlers = { express, nestjs, fastify, hono, fastapi, django, flask, spring, deno, gin };
9
33
  const fn = handlers[config.backend];
10
34
  if (!fn) throw new Error(`Backend no soportado: ${config.backend}`);
11
35
  await fn(config, projectPath);
12
36
  }
13
37
 
14
- // ── HELPERS ───────────────────────────────────────────────────────────────────
15
- function backendPath(projectPath) { return path.join(projectPath, 'backend'); }
16
-
17
- async function writeDotenv(bPath, content) {
18
- await fse.writeFile(path.join(bPath, '.env'), content);
19
- await fse.writeFile(path.join(bPath, '.env.example'), content.replace(/=.+/g, '='));
20
- }
21
-
22
38
  // ── EXPRESS ───────────────────────────────────────────────────────────────────
23
39
  async function express(config, projectPath) {
24
40
  const bp = backendPath(projectPath);
@@ -26,38 +42,24 @@ async function express(config, projectPath) {
26
42
  await fse.ensureDir(path.join(bp, 'src', 'middlewares'));
27
43
  await fse.ensureDir(path.join(bp, 'src', 'controllers'));
28
44
 
29
- const deps = config.backendDeps || [];
30
- const hasDotenv = deps.includes('dotenv');
31
- const hasCors = deps.includes('cors');
32
- const hasHelmet = deps.includes('helmet');
33
- const hasMorgan = deps.includes('morgan');
34
- const hasSwagger = deps.includes('swagger-ui-express');
35
- const hasSocket = deps.includes('socket.io');
45
+ const dbDeps = NODE_DB_DEPS[config.db] || [];
36
46
 
37
- const pkg = {
47
+ await fse.writeJSON(path.join(bp, 'package.json'), {
38
48
  name: 'backend', version: '1.0.0', main: 'src/server.js',
39
49
  scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
40
- };
41
- await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
50
+ }, { spaces: 2 });
42
51
 
43
- const server = `${hasDotenv ? "require('dotenv').config();\n" : ''}const express = require('express');
44
- ${hasCors ? "const cors = require('cors');" : ''}
45
- ${hasHelmet ? "const helmet = require('helmet');" : ''}
46
- ${hasMorgan ? "const morgan = require('morgan');" : ''}
47
- ${hasSwagger ? "const swaggerUi = require('swagger-ui-express');\nconst swaggerDoc = require('./swagger.json');" : ''}
48
- ${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');
49
56
 
50
57
  const app = express();
51
- ${hasSocket ? 'const httpServer = createServer(app);\nconst io = new Server(httpServer, { cors: { origin: "*" } });' : ''}
52
- ${hasCors ? 'app.use(cors());' : ''}
53
- ${hasHelmet ? 'app.use(helmet());' : ''}
54
- ${hasMorgan ? "app.use(morgan('dev'));" : ''}
58
+ app.use(cors());
55
59
  app.use(express.json());
56
60
  app.use(express.urlencoded({ extended: true }));
57
61
 
58
62
  app.use('/api', require('./routes/index'));
59
- ${hasSwagger ? "app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc));" : ''}
60
- ${hasSocket ? "\nio.on('connection', (socket) => {\n console.log('Cliente conectado:', socket.id);\n socket.on('disconnect', () => console.log('Cliente desconectado'));\n});" : ''}
61
63
 
62
64
  app.get('/', (req, res) => res.json({ status: 'ok', message: 'API funcionando βœ…' }));
63
65
 
@@ -67,49 +69,60 @@ app.use((err, req, res, next) => {
67
69
  });
68
70
 
69
71
  const PORT = process.env.PORT || 3001;
70
- const server${hasSocket ? ' = httpServer' : 'Inst = app'}.listen(PORT, () =>
71
- console.log(\`πŸš€ Servidor en http://localhost:\${PORT}\`)
72
- );
73
- `;
72
+ app.listen(PORT, () => console.log(\`πŸš€ Servidor en http://localhost:\${PORT}\`));
73
+ `);
74
74
 
75
- const routes = `const router = require('express').Router();
75
+ await fse.writeFile(path.join(bp, 'src', 'routes', 'index.js'),
76
+ `const router = require('express').Router();
76
77
  router.get('/health', (req, res) => res.json({ status: 'healthy', timestamp: new Date() }));
77
78
  module.exports = router;
78
- `;
79
-
80
- await fse.writeFile(path.join(bp, 'src', 'server.js'), server);
81
- await fse.writeFile(path.join(bp, 'src', 'routes', 'index.js'), routes);
82
- if (hasDotenv) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\nNODE_ENV=development\n');
79
+ `);
83
80
 
84
- await installNodeDeps(['express', ...deps], [], bp, 'Instalando dependencias Express');
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
+ }
85
88
  }
86
89
 
87
90
  // ── NESTJS ────────────────────────────────────────────────────────────────────
88
91
  async function nestjs(config, projectPath) {
89
- run('npx @nestjs/cli@latest new backend --skip-git --package-manager npm', projectPath, 'Creando proyecto NestJS');
90
92
  const bp = backendPath(projectPath);
91
- const deps = (config.backendDeps || []).filter(d => d !== 'nodemon');
92
- if (deps.length) run(`npm install ${deps.join(' ')}`, bp, `Instalando: ${deps.join(', ')}`);
93
- if (deps.includes('@nestjs/config')) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\n');
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
+ }
94
107
  }
95
108
 
96
109
  // ── FASTIFY ───────────────────────────────────────────────────────────────────
97
110
  async function fastify(config, projectPath) {
98
111
  const bp = backendPath(projectPath);
99
- await fse.ensureDir(path.join(bp, 'src', 'routes'));
112
+ await fse.ensureDir(path.join(bp, 'src'));
100
113
 
101
- const pkg = {
114
+ const dbDeps = NODE_DB_DEPS[config.db] || [];
115
+
116
+ await fse.writeJSON(path.join(bp, 'package.json'), {
102
117
  name: 'backend', version: '1.0.0', main: 'src/server.js',
103
118
  scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
104
- };
105
- await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
119
+ }, { spaces: 2 });
106
120
 
107
- const deps = config.backendDeps || [];
108
- const hasDotenv = deps.includes('dotenv');
121
+ await fse.writeFile(path.join(bp, 'src', 'server.js'),
122
+ `require('dotenv').config();
123
+ const fastify = require('fastify')({ logger: true });
109
124
 
110
- const server = `${hasDotenv ? "require('dotenv').config();\n" : ''}const fastify = require('fastify')({ logger: true });
111
- ${deps.includes('@fastify/cors') ? "fastify.register(require('@fastify/cors'));" : ''}
112
- ${deps.includes('@fastify/rate-limit') ? "fastify.register(require('@fastify/rate-limit'), { max: 100, timeWindow: '1 minute' });" : ''}
125
+ fastify.register(require('@fastify/cors'));
113
126
 
114
127
  fastify.get('/', async () => ({ status: 'ok', message: 'API funcionando βœ…' }));
115
128
  fastify.get('/api/health', async () => ({ status: 'healthy', timestamp: new Date() }));
@@ -123,25 +136,32 @@ const start = async () => {
123
136
  }
124
137
  };
125
138
  start();
126
- `;
127
- await fse.writeFile(path.join(bp, 'src', 'server.js'), server);
128
- if (hasDotenv) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\n');
129
- await installNodeDeps(['fastify', ...deps], [], bp, 'Instalando dependencias Fastify');
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
+ }
130
148
  }
131
149
 
132
150
  // ── HONO ──────────────────────────────────────────────────────────────────────
133
151
  async function hono(config, projectPath) {
134
152
  const bp = backendPath(projectPath);
135
- await fse.ensureDir(bp);
153
+ await fse.ensureDir(path.join(bp, 'src'));
154
+
155
+ const dbDeps = NODE_DB_DEPS[config.db] || [];
136
156
 
137
- const pkg = {
157
+ await fse.writeJSON(path.join(bp, 'package.json'), {
138
158
  name: 'backend', version: '1.0.0', main: 'src/index.js',
139
159
  scripts: { start: 'node src/index.js', dev: 'nodemon src/index.js' },
140
- };
141
- await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
160
+ }, { spaces: 2 });
142
161
 
143
- const deps = config.backendDeps || [];
144
- const server = `const { Hono } = require('hono');
162
+ await fse.writeFile(path.join(bp, 'src', 'index.js'),
163
+ `require('dotenv').config();
164
+ const { Hono } = require('hono');
145
165
  const { serve } = require('@hono/node-server');
146
166
 
147
167
  const app = new Hono();
@@ -152,11 +172,15 @@ app.get('/api/health', (c) => c.json({ status: 'healthy', timestamp: new Date()
152
172
  serve({ fetch: app.fetch, port: process.env.PORT || 3001 }, (info) =>
153
173
  console.log(\`πŸš€ Servidor en http://localhost:\${info.port}\`)
154
174
  );
155
- `;
156
- await fse.ensureDir(path.join(bp, 'src'));
157
- await fse.writeFile(path.join(bp, 'src', 'index.js'), server);
158
- if (deps.includes('dotenv')) await writeDotenv(bp, 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\n');
159
- await installNodeDeps(['hono', '@hono/node-server', ...deps], ['nodemon'], bp, 'Instalando dependencias Hono');
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
+ }
160
184
  }
161
185
 
162
186
  // ── FASTAPI ───────────────────────────────────────────────────────────────────
@@ -166,10 +190,20 @@ async function fastapi(config, projectPath) {
166
190
  await fse.ensureDir(path.join(bp, 'app', 'models'));
167
191
  await fse.ensureDir(path.join(bp, 'app', 'schemas'));
168
192
 
169
- const deps = config.backendDeps || [];
170
- const hasDotenv = deps.includes('python-dotenv');
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()
171
205
 
172
- const main = `${hasDotenv ? 'from dotenv import load_dotenv\nload_dotenv()\n\n' : ''}from fastapi import FastAPI
206
+ from fastapi import FastAPI
173
207
  from fastapi.middleware.cors import CORSMiddleware
174
208
  from app.routers import health
175
209
 
@@ -181,27 +215,23 @@ app.include_router(health.router, prefix="/api", tags=["health"])
181
215
  @app.get("/")
182
216
  def root():
183
217
  return {"status": "ok", "message": "API funcionando βœ…"}
184
- `;
185
- const healthRouter = `from fastapi import APIRouter
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
186
224
  router = APIRouter()
187
225
 
188
226
  @router.get("/health")
189
227
  def health_check():
190
228
  return {"status": "healthy"}
191
- `;
192
- const requirements = ['fastapi', 'uvicorn[standard]', ...deps].join('\n') + '\n';
193
-
194
- await fse.writeFile(path.join(bp, 'main.py'), main);
195
- await fse.writeFile(path.join(bp, 'app', '__init__.py'), '');
196
- await fse.writeFile(path.join(bp, 'app', 'routers', '__init__.py'), '');
197
- await fse.writeFile(path.join(bp, 'app', 'routers', 'health.py'), healthRouter);
229
+ `);
198
230
  await fse.writeFile(path.join(bp, 'app', 'models', '__init__.py'), '');
199
231
  await fse.writeFile(path.join(bp, 'app', 'schemas', '__init__.py'), '');
200
- await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
201
- if (hasDotenv) await writeDotenv(bp, 'DATABASE_URL=\nSECRET_KEY=change_me\nDEBUG=True\n');
202
-
203
- if (isInstalled('pip')) run('pip install -r requirements.txt', bp, 'Instalando dependencias FastAPI');
204
- 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
+ );
205
235
  }
206
236
 
207
237
  // ── DJANGO ────────────────────────────────────────────────────────────────────
@@ -209,17 +239,32 @@ async function django(config, projectPath) {
209
239
  const bp = backendPath(projectPath);
210
240
  await fse.ensureDir(bp);
211
241
 
212
- const deps = config.backendDeps || [];
213
- const requirements = ['django', 'gunicorn', ...deps].join('\n') + '\n';
214
- await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
215
- await fse.writeFile(path.join(bp, 'README_SETUP.md'),
216
- `# Django Backend\n\n\`\`\`bash\npip install -r requirements.txt\ndjango-admin startproject core .\npython manage.py migrate\npython manage.py runserver\n\`\`\`\n`
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'
217
253
  );
218
- if (isInstalled('pip')) {
219
- run('pip install -r requirements.txt', bp, 'Instalando Django y dependencias');
220
- run('django-admin startproject core .', bp, 'Creando proyecto Django');
221
- } else {
222
- console.log(chalk.yellow('\n ⚠ pip no encontrado. Instala manualmente.'));
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 {}
223
268
  }
224
269
  }
225
270
 
@@ -228,10 +273,17 @@ async function flask(config, projectPath) {
228
273
  const bp = backendPath(projectPath);
229
274
  await fse.ensureDir(path.join(bp, 'app'));
230
275
 
231
- const deps = config.backendDeps || [];
232
- const hasDotenv = deps.includes('python-dotenv');
233
-
234
- const appInit = `from flask import Flask
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
235
287
  from flask_cors import CORS
236
288
 
237
289
  def create_app():
@@ -247,22 +299,21 @@ def create_app():
247
299
  return {'status': 'healthy'}
248
300
 
249
301
  return app
250
- `;
251
- const run_py = `${hasDotenv ? 'from dotenv import load_dotenv\nload_dotenv()\n\n' : ''}from app import create_app
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
252
308
  app = create_app()
253
309
 
254
310
  if __name__ == '__main__':
255
311
  app.run(debug=True, port=5000)
256
- `;
257
- const requirements = ['flask', 'flask-cors', ...deps].join('\n') + '\n';
258
-
259
- await fse.writeFile(path.join(bp, 'app', '__init__.py'), appInit);
260
- await fse.writeFile(path.join(bp, 'run.py'), run_py);
261
- await fse.writeFile(path.join(bp, 'requirements.txt'), requirements);
262
- if (hasDotenv) await writeDotenv(bp, 'FLASK_ENV=development\nSECRET_KEY=change_me\nDATABASE_URL=\n');
312
+ `);
263
313
 
264
- if (isInstalled('pip')) run('pip install -r requirements.txt', bp, 'Instalando Flask y dependencias');
265
- else console.log(chalk.yellow('\n ⚠ pip no encontrado. Ejecuta: pip install -r requirements.txt'));
314
+ await fse.writeFile(path.join(bp, 'requirements.txt'),
315
+ ['flask', 'flask-cors', 'python-dotenv', ...pyDbDeps].join('\n') + '\n'
316
+ );
266
317
  }
267
318
 
268
319
  // ── SPRING BOOT ───────────────────────────────────────────────────────────────
@@ -270,10 +321,18 @@ async function spring(config, projectPath) {
270
321
  const bp = backendPath(projectPath);
271
322
  await fse.ensureDir(bp);
272
323
  await fse.writeFile(path.join(bp, 'README_SETUP.md'),
273
- `# Spring Boot Backend\n\nGenera el proyecto en: https://start.spring.io\n\nDependencias sugeridas:\n${(config.backendDeps || []).map(d => `- ${d}`).join('\n')}\n\nO usa Spring CLI:\n\`\`\`bash\nspring init --dependencies=web,data-jpa,security backend\n\`\`\`\n`
274
- );
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
+ `);
275
335
  console.log(chalk.yellow('\n β„Ή Spring Boot: genera el proyecto en https://start.spring.io'));
276
- console.log(chalk.gray(' Se creΓ³ un README_SETUP.md con instrucciones.\n'));
277
336
  }
278
337
 
279
338
  // ── DENO/OAK ──────────────────────────────────────────────────────────────────
@@ -281,7 +340,8 @@ async function deno(config, projectPath) {
281
340
  const bp = backendPath(projectPath);
282
341
  await fse.ensureDir(bp);
283
342
 
284
- const server = `import { Application, Router } from "https://deno.land/x/oak/mod.ts";
343
+ await fse.writeFile(path.join(bp, 'main.ts'),
344
+ `import { Application, Router } from "https://deno.land/x/oak/mod.ts";
285
345
 
286
346
  const app = new Application();
287
347
  const router = new Router();
@@ -294,11 +354,15 @@ app.use(router.allowedMethods());
294
354
 
295
355
  console.log("πŸš€ Servidor en http://localhost:8000");
296
356
  await app.listen({ port: 8000 });
297
- `;
298
- await fse.writeFile(path.join(bp, 'main.ts'), server);
299
- await fse.writeFile(path.join(bp, 'deno.json'), JSON.stringify({ tasks: { dev: 'deno run --allow-net main.ts' } }, null, 2));
300
- console.log(chalk.green('βœ” Backend Deno/Oak generado'));
301
- if (!isInstalled('deno')) console.log(chalk.yellow(' ⚠ Deno no instalado. Instala desde https://deno.land'));
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
+ }
302
366
  }
303
367
 
304
368
  // ── GO/GIN ────────────────────────────────────────────────────────────────────
@@ -306,7 +370,8 @@ async function gin(config, projectPath) {
306
370
  const bp = backendPath(projectPath);
307
371
  await fse.ensureDir(bp);
308
372
 
309
- const main = `package main
373
+ await fse.writeFile(path.join(bp, 'main.go'),
374
+ `package main
310
375
 
311
376
  import (
312
377
  "net/http"
@@ -325,13 +390,19 @@ func main() {
325
390
 
326
391
  r.Run(":8080")
327
392
  }
328
- `;
329
- await fse.writeFile(path.join(bp, 'main.go'), main);
393
+ `);
394
+
330
395
  await fse.writeFile(path.join(bp, 'README_SETUP.md'),
331
- `# Go/Gin Backend\n\n\`\`\`bash\ngo mod init backend\ngo get github.com/gin-gonic/gin\ngo run main.go\n\`\`\`\n`
332
- );
333
- console.log(chalk.green('βœ” Backend Go/Gin generado'));
334
- if (!isInstalled('go')) console.log(chalk.yellow(' ⚠ Go no instalado. Instala desde https://go.dev'));
335
- }
396
+ `# Go/Gin Backend
336
397
 
337
- export { generateBackend };
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
+ }
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import fse from 'fs-extra';
3
3
  import chalk from 'chalk';
4
4
  import boxen from 'boxen';
5
- import { spawnSync } from 'child_process';
5
+ import { execSync } from 'child_process';
6
6
  import { isInstalled } from '../utils.js';
7
7
 
8
8
  // ── GitHub Actions CI ─────────────────────────────────────────────────────────
@@ -108,8 +108,12 @@ export async function runBuild(options) {
108
108
 
109
109
  if ((options.frontend || !options.backend) && hasFe) {
110
110
  const spinner = (await import('ora')).default({ text: chalk.white('Build frontend...'), spinner: 'dots', color: 'white' }).start();
111
- const r = spawnSync('npm run build', { cwd: path.join(root, 'frontend'), shell: true, stdio: 'pipe', encoding: 'utf8' });
112
- r.status === 0 ? spinner.succeed(chalk.white('Frontend build OK')) : spinner.fail(chalk.red('Frontend build fallΓ³\n' + r.stderr));
111
+ try {
112
+ execSync('npm run build', { cwd: path.join(root, 'frontend'), stdio: 'pipe', encoding: 'utf8' });
113
+ spinner.succeed(chalk.white('Frontend build OK'));
114
+ } catch (err) {
115
+ spinner.fail(chalk.red('Frontend build fallΓ³\n' + (err.stderr || '')));
116
+ }
113
117
  }
114
118
 
115
119
  if ((options.backend || !options.frontend) && hasBe) {
@@ -118,8 +122,12 @@ export async function runBuild(options) {
118
122
  const pkg = await fse.readJSON(pkgPath);
119
123
  if (pkg.scripts?.build) {
120
124
  const spinner = (await import('ora')).default({ text: chalk.white('Build backend...'), spinner: 'dots', color: 'white' }).start();
121
- const r = spawnSync('npm run build', { cwd: path.join(root, 'backend'), shell: true, stdio: 'pipe', encoding: 'utf8' });
122
- r.status === 0 ? spinner.succeed(chalk.white('Backend build OK')) : spinner.fail(chalk.red('Backend build fallΓ³'));
125
+ try {
126
+ execSync('npm run build', { cwd: path.join(root, 'backend'), stdio: 'pipe', encoding: 'utf8' });
127
+ spinner.succeed(chalk.white('Backend build OK'));
128
+ } catch {
129
+ spinner.fail(chalk.red('Backend build fallΓ³'));
130
+ }
123
131
  }
124
132
  }
125
133
  }
@@ -147,19 +155,19 @@ export async function runDeploy(options) {
147
155
  if ((options.frontend || !options.backend)) {
148
156
  if (!isInstalled('vercel')) {
149
157
  console.log(chalk.yellow('\n Instalando Vercel CLI...'));
150
- spawnSync('npm install -g vercel', { shell: true, stdio: 'inherit' });
158
+ execSync('npm install -g vercel', { stdio: 'inherit' });
151
159
  }
152
160
  console.log(chalk.white('\n Desplegando frontend en Vercel...'));
153
- spawnSync('vercel --prod', { cwd: path.join(root, 'frontend'), shell: true, stdio: 'inherit' });
161
+ execSync('vercel --prod', { cwd: path.join(root, 'frontend'), stdio: 'inherit' });
154
162
  }
155
163
 
156
164
  if ((options.backend || !options.frontend)) {
157
165
  if (!isInstalled('railway')) {
158
166
  console.log(chalk.yellow('\n Instalando Railway CLI...'));
159
- spawnSync('npm install -g @railway/cli', { shell: true, stdio: 'inherit' });
167
+ execSync('npm install -g @railway/cli', { stdio: 'inherit' });
160
168
  }
161
169
  console.log(chalk.white('\n Desplegando backend en Railway...'));
162
- spawnSync('railway up', { cwd: path.join(root, 'backend'), shell: true, stdio: 'inherit' });
170
+ execSync('railway up', { cwd: path.join(root, 'backend'), stdio: 'inherit' });
163
171
  }
164
172
 
165
173
  console.log();
@@ -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 { run } from '../utils.js';
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: 'npm create svelte@latest frontend -- --no-install',
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
- run('npx tailwindcss init -p', frontendPath, 'Configurando Tailwind CSS');
54
- } catch { /* puede fallar si ya existe */ }
55
-
56
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */
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 };