novatec-cli 3.0.1 β 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/create.js +106 -195
- package/lib/doctor.js +1 -7
- package/lib/generators/backend.js +203 -132
- package/lib/generators/deploy.js +17 -9
- package/lib/generators/frontend.js +8 -57
- package/lib/prompts.js +54 -130
- package/lib/utils.js +12 -10
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
`;
|
|
72
|
+
app.listen(PORT, () => console.log(\`π Servidor en http://localhost:\${PORT}\`));
|
|
73
|
+
`);
|
|
74
74
|
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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'
|
|
112
|
+
await fse.ensureDir(path.join(bp, 'src'));
|
|
100
113
|
|
|
101
|
-
const
|
|
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
|
-
|
|
108
|
-
|
|
121
|
+
await fse.writeFile(path.join(bp, 'src', 'server.js'),
|
|
122
|
+
`require('dotenv').config();
|
|
123
|
+
const fastify = require('fastify')({ logger: true });
|
|
109
124
|
|
|
110
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'),
|
|
201
|
-
|
|
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
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
299
|
-
await fse.
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
393
|
+
`);
|
|
394
|
+
|
|
330
395
|
await fse.writeFile(path.join(bp, 'README_SETUP.md'),
|
|
331
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/generators/deploy.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
158
|
+
execSync('npm install -g vercel', { stdio: 'inherit' });
|
|
151
159
|
}
|
|
152
160
|
console.log(chalk.white('\n Desplegando frontend en Vercel...'));
|
|
153
|
-
|
|
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
|
-
|
|
167
|
+
execSync('npm install -g @railway/cli', { stdio: 'inherit' });
|
|
160
168
|
}
|
|
161
169
|
console.log(chalk.white('\n Desplegando backend en Railway...'));
|
|
162
|
-
|
|
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 {
|
|
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 };
|