novatec-cli 1.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/LICENSE +21 -0
- package/README.md +331 -0
- package/bin/index.js +147 -0
- package/lib/create.js +278 -0
- package/lib/generators/backend.js +337 -0
- package/lib/generators/frontend.js +81 -0
- package/lib/generators/readme.js +99 -0
- package/lib/prompts.js +348 -0
- package/lib/utils.js +40 -0
- package/package.json +64 -0
package/lib/create.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fse from 'fs-extra';
|
|
4
|
+
import { askProjectOptions } from './prompts.js';
|
|
5
|
+
import { generateFrontend } from './generators/frontend.js';
|
|
6
|
+
import { generateBackend } from './generators/backend.js';
|
|
7
|
+
import { generateReadme } from './generators/readme.js';
|
|
8
|
+
import { run, isInstalled } from './utils.js';
|
|
9
|
+
|
|
10
|
+
const boxen = await import('boxen');
|
|
11
|
+
const gradient = await import('gradient-string');
|
|
12
|
+
|
|
13
|
+
async function runCreate(nameArg, options) {
|
|
14
|
+
const config = await askProjectOptions(nameArg, options);
|
|
15
|
+
const projectPath = path.resolve(process.cwd(), config.name);
|
|
16
|
+
|
|
17
|
+
if (await fse.pathExists(projectPath)) {
|
|
18
|
+
throw new Error(`La carpeta "${config.name}" ya existe en este directorio.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(`\nπ Creando proyecto en: ${chalk.bold(projectPath)}\n`));
|
|
22
|
+
await fse.ensureDir(projectPath);
|
|
23
|
+
|
|
24
|
+
await generateFrontend(config, projectPath);
|
|
25
|
+
await generateBackend(config, projectPath);
|
|
26
|
+
await generateReadme(config, projectPath);
|
|
27
|
+
|
|
28
|
+
const extras = config.extras || [];
|
|
29
|
+
|
|
30
|
+
if (extras.includes('env')) await generateEnvFiles(config, projectPath);
|
|
31
|
+
if (extras.includes('editorconfig')) await fse.writeFile(path.join(projectPath, '.editorconfig'), EDITORCONFIG);
|
|
32
|
+
if (extras.includes('eslint')) await setupEslint(config, projectPath);
|
|
33
|
+
if (extras.includes('husky')) await setupHusky(config, projectPath);
|
|
34
|
+
if (extras.includes('testing')) await setupTesting(config, projectPath);
|
|
35
|
+
if (extras.includes('ci')) await setupCI(config, projectPath);
|
|
36
|
+
if (extras.includes('docker')) await generateDocker(config, projectPath);
|
|
37
|
+
|
|
38
|
+
if (extras.includes('git') && isInstalled('git')) {
|
|
39
|
+
try { run('git init', projectPath, 'Inicializando repositorio git'); } catch {}
|
|
40
|
+
await fse.writeFile(path.join(projectPath, '.gitignore'), GITIGNORE);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Resumen final
|
|
44
|
+
const novatecGradient = gradient.default(['#00C6FF', '#0072FF', '#7B2FF7']);
|
|
45
|
+
const backendCmd = {
|
|
46
|
+
express: 'npm run dev',
|
|
47
|
+
nestjs: 'npm run start:dev',
|
|
48
|
+
fastify: 'npm run dev',
|
|
49
|
+
hono: 'npm run dev',
|
|
50
|
+
fastapi: 'uvicorn main:app --reload',
|
|
51
|
+
django: 'python manage.py runserver',
|
|
52
|
+
flask: 'python run.py',
|
|
53
|
+
spring: 'mvn spring-boot:run',
|
|
54
|
+
deno: 'deno task dev',
|
|
55
|
+
gin: 'go run main.go'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
console.log(
|
|
59
|
+
boxen.default(
|
|
60
|
+
novatecGradient(' β FULLSTACK CLI β\n\n') +
|
|
61
|
+
chalk.green.bold(' β
Proyecto creado exitosamente!\n\n') +
|
|
62
|
+
chalk.white(' π Proyecto : ') + chalk.cyan.bold(config.name) + '\n' +
|
|
63
|
+
chalk.white(' β Frontend : ') + chalk.cyan(config.frontend) + '\n' +
|
|
64
|
+
chalk.white(' π§ Backend : ') + chalk.cyan(config.backend) + '\n\n' +
|
|
65
|
+
chalk.bold.white(' βββ CΓ³mo iniciar βββββββββββββββββββββββββββ\n\n') +
|
|
66
|
+
chalk.yellow(' Frontend:\n') +
|
|
67
|
+
chalk.white(` cd ${config.name}\\frontend\n`) +
|
|
68
|
+
chalk.cyan(' npm run dev\n\n') +
|
|
69
|
+
chalk.yellow(' Backend:\n') +
|
|
70
|
+
chalk.white(` cd ${config.name}\\backend\n`) +
|
|
71
|
+
chalk.cyan(` ${backendCmd[config.backend] || 'npm start'}`),
|
|
72
|
+
{
|
|
73
|
+
padding: 1,
|
|
74
|
+
margin: { top: 1, left: 2 },
|
|
75
|
+
borderStyle: 'double',
|
|
76
|
+
borderColor: 'cyan',
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ββ ESLint + Prettier βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
83
|
+
async function setupEslint(config, projectPath) {
|
|
84
|
+
const frontendPath = path.join(projectPath, 'frontend');
|
|
85
|
+
const eslintConfig = `{
|
|
86
|
+
"env": { "browser": true, "es2021": true, "node": true },
|
|
87
|
+
"extends": ["eslint:recommended"],
|
|
88
|
+
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
|
|
89
|
+
"rules": { "no-unused-vars": "warn", "no-console": "off" }
|
|
90
|
+
}\n`;
|
|
91
|
+
const prettierConfig = `{
|
|
92
|
+
"semi": true,
|
|
93
|
+
"singleQuote": true,
|
|
94
|
+
"tabWidth": 2,
|
|
95
|
+
"trailingComma": "es5"
|
|
96
|
+
}\n`;
|
|
97
|
+
|
|
98
|
+
await fse.writeFile(path.join(frontendPath, '.eslintrc.json'), eslintConfig);
|
|
99
|
+
await fse.writeFile(path.join(frontendPath, '.prettierrc'), prettierConfig);
|
|
100
|
+
run('npm install -D eslint prettier eslint-config-prettier', frontendPath, 'Instalando ESLint + Prettier');
|
|
101
|
+
console.log(chalk.green('β ESLint + Prettier configurados'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ββ Husky + lint-staged βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
105
|
+
async function setupHusky(config, projectPath) {
|
|
106
|
+
const frontendPath = path.join(projectPath, 'frontend');
|
|
107
|
+
try {
|
|
108
|
+
run('npm install -D husky lint-staged', frontendPath, 'Instalando Husky + lint-staged');
|
|
109
|
+
run('npx husky init', frontendPath, 'Inicializando Husky');
|
|
110
|
+
await fse.writeFile(path.join(frontendPath, '.husky', 'pre-commit'), '#!/bin/sh\nnpx lint-staged\n');
|
|
111
|
+
const pkgPath = path.join(frontendPath, 'package.json');
|
|
112
|
+
const pkg = await fse.readJSON(pkgPath);
|
|
113
|
+
pkg['lint-staged'] = { '*.{js,jsx,ts,tsx,vue}': ['eslint --fix', 'prettier --write'] };
|
|
114
|
+
await fse.writeJSON(pkgPath, pkg, { spaces: 2 });
|
|
115
|
+
console.log(chalk.green('β Husky + lint-staged configurados'));
|
|
116
|
+
} catch { console.log(chalk.yellow('β Husky requiere git init primero')); }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ββ Testing βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
120
|
+
async function setupTesting(config, projectPath) {
|
|
121
|
+
const frontendPath = path.join(projectPath, 'frontend');
|
|
122
|
+
const isReactLike = ['react', 'next', 'solid', 'remix', 'qwik'].includes(config.frontend);
|
|
123
|
+
|
|
124
|
+
run('npm install -D vitest @vitest/ui', frontendPath, 'Instalando Vitest');
|
|
125
|
+
|
|
126
|
+
const testDir = path.join(frontendPath, 'src', '__tests__');
|
|
127
|
+
await fse.ensureDir(testDir);
|
|
128
|
+
|
|
129
|
+
const sampleTest = isReactLike
|
|
130
|
+
? `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('should work', () => {\n expect(1 + 1).toBe(2);\n });\n});\n`
|
|
131
|
+
: `import { describe, it, expect } from 'vitest';\n\ndescribe('App', () => {\n it('should work', () => {\n expect(true).toBe(true);\n });\n});\n`;
|
|
132
|
+
|
|
133
|
+
await fse.writeFile(path.join(testDir, 'app.test.js'), sampleTest);
|
|
134
|
+
|
|
135
|
+
const pkgPath = path.join(frontendPath, 'package.json');
|
|
136
|
+
const pkg = await fse.readJSON(pkgPath);
|
|
137
|
+
pkg.scripts = { ...pkg.scripts, test: 'vitest', 'test:ui': 'vitest --ui' };
|
|
138
|
+
await fse.writeJSON(pkgPath, pkg, { spaces: 2 });
|
|
139
|
+
console.log(chalk.green('β Vitest configurado con test de ejemplo'));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ββ GitHub Actions CI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
143
|
+
async function setupCI(config, projectPath) {
|
|
144
|
+
const ciDir = path.join(projectPath, '.github', 'workflows');
|
|
145
|
+
await fse.ensureDir(ciDir);
|
|
146
|
+
|
|
147
|
+
const ci = `name: CI
|
|
148
|
+
|
|
149
|
+
on:
|
|
150
|
+
push:
|
|
151
|
+
branches: [main, develop]
|
|
152
|
+
pull_request:
|
|
153
|
+
branches: [main]
|
|
154
|
+
|
|
155
|
+
jobs:
|
|
156
|
+
frontend:
|
|
157
|
+
runs-on: ubuntu-latest
|
|
158
|
+
defaults:
|
|
159
|
+
run:
|
|
160
|
+
working-directory: frontend
|
|
161
|
+
steps:
|
|
162
|
+
- uses: actions/checkout@v4
|
|
163
|
+
- uses: actions/setup-node@v4
|
|
164
|
+
with:
|
|
165
|
+
node-version: 20
|
|
166
|
+
cache: npm
|
|
167
|
+
cache-dependency-path: frontend/package-lock.json
|
|
168
|
+
- run: npm ci
|
|
169
|
+
- run: npm run build
|
|
170
|
+
${config.extras.includes('testing') ? ' - run: npm test\n' : ''}
|
|
171
|
+
backend:
|
|
172
|
+
runs-on: ubuntu-latest
|
|
173
|
+
defaults:
|
|
174
|
+
run:
|
|
175
|
+
working-directory: backend
|
|
176
|
+
steps:
|
|
177
|
+
- uses: actions/checkout@v4
|
|
178
|
+
${['fastapi', 'django', 'flask'].includes(config.backend) ? ` - uses: actions/setup-python@v5
|
|
179
|
+
with:
|
|
180
|
+
python-version: '3.11'
|
|
181
|
+
- run: pip install -r requirements.txt` : ` - uses: actions/setup-node@v4
|
|
182
|
+
with:
|
|
183
|
+
node-version: 20
|
|
184
|
+
- run: npm ci`}
|
|
185
|
+
`;
|
|
186
|
+
await fse.writeFile(path.join(ciDir, 'ci.yml'), ci);
|
|
187
|
+
console.log(chalk.green('β GitHub Actions CI configurado'));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ββ Docker ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
191
|
+
async function generateDocker(config, projectPath) {
|
|
192
|
+
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
193
|
+
const backendPort = isPython ? (config.backend === 'gin' ? '8080' : '8000') : '3001';
|
|
194
|
+
|
|
195
|
+
const backendService = isPython
|
|
196
|
+
? ` backend:
|
|
197
|
+
build: ./backend
|
|
198
|
+
ports:
|
|
199
|
+
- "${backendPort}:${backendPort}"
|
|
200
|
+
volumes:
|
|
201
|
+
- ./backend:/app
|
|
202
|
+
environment:
|
|
203
|
+
- DEBUG=True`
|
|
204
|
+
: ` backend:
|
|
205
|
+
build: ./backend
|
|
206
|
+
ports:
|
|
207
|
+
- "${backendPort}:${backendPort}"
|
|
208
|
+
volumes:
|
|
209
|
+
- ./backend:/app
|
|
210
|
+
- /app/node_modules
|
|
211
|
+
environment:
|
|
212
|
+
- NODE_ENV=development`;
|
|
213
|
+
|
|
214
|
+
const compose = `version: '3.9'
|
|
215
|
+
services:
|
|
216
|
+
frontend:
|
|
217
|
+
build: ./frontend
|
|
218
|
+
ports:
|
|
219
|
+
- "5173:5173"
|
|
220
|
+
volumes:
|
|
221
|
+
- ./frontend:/app
|
|
222
|
+
- /app/node_modules
|
|
223
|
+
environment:
|
|
224
|
+
- NODE_ENV=development
|
|
225
|
+
${backendService}
|
|
226
|
+
db:
|
|
227
|
+
image: postgres:16-alpine
|
|
228
|
+
environment:
|
|
229
|
+
POSTGRES_DB: mydb
|
|
230
|
+
POSTGRES_USER: user
|
|
231
|
+
POSTGRES_PASSWORD: password
|
|
232
|
+
ports:
|
|
233
|
+
- "5432:5432"
|
|
234
|
+
volumes:
|
|
235
|
+
- pgdata:/var/lib/postgresql/data
|
|
236
|
+
|
|
237
|
+
volumes:
|
|
238
|
+
pgdata:
|
|
239
|
+
`;
|
|
240
|
+
await fse.writeFile(path.join(projectPath, 'docker-compose.yml'), compose);
|
|
241
|
+
console.log(chalk.green('β docker-compose.yml generado (con PostgreSQL)'));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ββ Archivos .env βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
245
|
+
async function generateEnvFiles(config, projectPath) {
|
|
246
|
+
await fse.writeFile(path.join(projectPath, 'frontend', '.env.example'), 'VITE_API_URL=http://localhost:3001\n');
|
|
247
|
+
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
248
|
+
if (!isPython) {
|
|
249
|
+
await fse.writeFile(path.join(projectPath, 'backend', '.env.example'), 'PORT=3001\nDB_URL=\nJWT_SECRET=change_me\nNODE_ENV=development\n');
|
|
250
|
+
}
|
|
251
|
+
console.log(chalk.green('β Archivos .env.example generados'));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const EDITORCONFIG = `root = true
|
|
255
|
+
[*]
|
|
256
|
+
indent_style = space
|
|
257
|
+
indent_size = 2
|
|
258
|
+
end_of_line = lf
|
|
259
|
+
charset = utf-8
|
|
260
|
+
trim_trailing_whitespace = true
|
|
261
|
+
insert_final_newline = true
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
const GITIGNORE = `node_modules/
|
|
265
|
+
dist/
|
|
266
|
+
.next/
|
|
267
|
+
.nuxt/
|
|
268
|
+
.svelte-kit/
|
|
269
|
+
.env
|
|
270
|
+
*.log
|
|
271
|
+
__pycache__/
|
|
272
|
+
.venv/
|
|
273
|
+
*.pyc
|
|
274
|
+
target/
|
|
275
|
+
*.class
|
|
276
|
+
`;
|
|
277
|
+
|
|
278
|
+
export { runCreate };
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fse from 'fs-extra';
|
|
4
|
+
import { run, isInstalled } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
async function generateBackend(config, projectPath) {
|
|
7
|
+
console.log(chalk.blue(`\nπ§ Generando backend (${chalk.bold(config.backend)})...`));
|
|
8
|
+
const handlers = { express, nestjs, fastify, hono, fastapi, django, flask, spring, deno, gin };
|
|
9
|
+
const fn = handlers[config.backend];
|
|
10
|
+
if (!fn) throw new Error(`Backend no soportado: ${config.backend}`);
|
|
11
|
+
await fn(config, projectPath);
|
|
12
|
+
}
|
|
13
|
+
|
|
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
|
+
// ββ EXPRESS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
23
|
+
async function express(config, projectPath) {
|
|
24
|
+
const bp = backendPath(projectPath);
|
|
25
|
+
await fse.ensureDir(path.join(bp, 'src', 'routes'));
|
|
26
|
+
await fse.ensureDir(path.join(bp, 'src', 'middlewares'));
|
|
27
|
+
await fse.ensureDir(path.join(bp, 'src', 'controllers'));
|
|
28
|
+
|
|
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');
|
|
36
|
+
|
|
37
|
+
const pkg = {
|
|
38
|
+
name: 'backend', version: '1.0.0', main: 'src/server.js',
|
|
39
|
+
scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
|
|
40
|
+
};
|
|
41
|
+
await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
|
|
42
|
+
|
|
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');" : ''}
|
|
49
|
+
|
|
50
|
+
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'));" : ''}
|
|
55
|
+
app.use(express.json());
|
|
56
|
+
app.use(express.urlencoded({ extended: true }));
|
|
57
|
+
|
|
58
|
+
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
|
+
|
|
62
|
+
app.get('/', (req, res) => res.json({ status: 'ok', message: 'API funcionando β
' }));
|
|
63
|
+
|
|
64
|
+
app.use((err, req, res, next) => {
|
|
65
|
+
console.error(err.stack);
|
|
66
|
+
res.status(500).json({ error: err.message });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
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
|
+
`;
|
|
74
|
+
|
|
75
|
+
const routes = `const router = require('express').Router();
|
|
76
|
+
router.get('/health', (req, res) => res.json({ status: 'healthy', timestamp: new Date() }));
|
|
77
|
+
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');
|
|
83
|
+
|
|
84
|
+
await installNodeDeps(['express', ...deps], [], bp, 'Instalando dependencias Express');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ββ NESTJS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
88
|
+
async function nestjs(config, projectPath) {
|
|
89
|
+
run('npx @nestjs/cli@latest new backend --skip-git --package-manager npm', projectPath, 'Creando proyecto NestJS');
|
|
90
|
+
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');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ββ FASTIFY βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
97
|
+
async function fastify(config, projectPath) {
|
|
98
|
+
const bp = backendPath(projectPath);
|
|
99
|
+
await fse.ensureDir(path.join(bp, 'src', 'routes'));
|
|
100
|
+
|
|
101
|
+
const pkg = {
|
|
102
|
+
name: 'backend', version: '1.0.0', main: 'src/server.js',
|
|
103
|
+
scripts: { start: 'node src/server.js', dev: 'nodemon src/server.js' },
|
|
104
|
+
};
|
|
105
|
+
await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
|
|
106
|
+
|
|
107
|
+
const deps = config.backendDeps || [];
|
|
108
|
+
const hasDotenv = deps.includes('dotenv');
|
|
109
|
+
|
|
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' });" : ''}
|
|
113
|
+
|
|
114
|
+
fastify.get('/', async () => ({ status: 'ok', message: 'API funcionando β
' }));
|
|
115
|
+
fastify.get('/api/health', async () => ({ status: 'healthy', timestamp: new Date() }));
|
|
116
|
+
|
|
117
|
+
const start = async () => {
|
|
118
|
+
try {
|
|
119
|
+
await fastify.listen({ port: process.env.PORT || 3001, host: '0.0.0.0' });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
fastify.log.error(err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
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');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ββ HONO ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
133
|
+
async function hono(config, projectPath) {
|
|
134
|
+
const bp = backendPath(projectPath);
|
|
135
|
+
await fse.ensureDir(bp);
|
|
136
|
+
|
|
137
|
+
const pkg = {
|
|
138
|
+
name: 'backend', version: '1.0.0', main: 'src/index.js',
|
|
139
|
+
scripts: { start: 'node src/index.js', dev: 'nodemon src/index.js' },
|
|
140
|
+
};
|
|
141
|
+
await fse.writeJSON(path.join(bp, 'package.json'), pkg, { spaces: 2 });
|
|
142
|
+
|
|
143
|
+
const deps = config.backendDeps || [];
|
|
144
|
+
const server = `const { Hono } = require('hono');
|
|
145
|
+
const { serve } = require('@hono/node-server');
|
|
146
|
+
|
|
147
|
+
const app = new Hono();
|
|
148
|
+
|
|
149
|
+
app.get('/', (c) => c.json({ status: 'ok', message: 'API funcionando β
' }));
|
|
150
|
+
app.get('/api/health', (c) => c.json({ status: 'healthy', timestamp: new Date() }));
|
|
151
|
+
|
|
152
|
+
serve({ fetch: app.fetch, port: process.env.PORT || 3001 }, (info) =>
|
|
153
|
+
console.log(\`π Servidor en http://localhost:\${info.port}\`)
|
|
154
|
+
);
|
|
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');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ββ FASTAPI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
163
|
+
async function fastapi(config, projectPath) {
|
|
164
|
+
const bp = backendPath(projectPath);
|
|
165
|
+
await fse.ensureDir(path.join(bp, 'app', 'routers'));
|
|
166
|
+
await fse.ensureDir(path.join(bp, 'app', 'models'));
|
|
167
|
+
await fse.ensureDir(path.join(bp, 'app', 'schemas'));
|
|
168
|
+
|
|
169
|
+
const deps = config.backendDeps || [];
|
|
170
|
+
const hasDotenv = deps.includes('python-dotenv');
|
|
171
|
+
|
|
172
|
+
const main = `${hasDotenv ? 'from dotenv import load_dotenv\nload_dotenv()\n\n' : ''}from fastapi import FastAPI
|
|
173
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
174
|
+
from app.routers import health
|
|
175
|
+
|
|
176
|
+
app = FastAPI(title="My API", version="1.0.0", docs_url="/api/docs")
|
|
177
|
+
|
|
178
|
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
|
179
|
+
app.include_router(health.router, prefix="/api", tags=["health"])
|
|
180
|
+
|
|
181
|
+
@app.get("/")
|
|
182
|
+
def root():
|
|
183
|
+
return {"status": "ok", "message": "API funcionando β
"}
|
|
184
|
+
`;
|
|
185
|
+
const healthRouter = `from fastapi import APIRouter
|
|
186
|
+
router = APIRouter()
|
|
187
|
+
|
|
188
|
+
@router.get("/health")
|
|
189
|
+
def health_check():
|
|
190
|
+
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);
|
|
198
|
+
await fse.writeFile(path.join(bp, 'app', 'models', '__init__.py'), '');
|
|
199
|
+
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'));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ββ DJANGO ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
208
|
+
async function django(config, projectPath) {
|
|
209
|
+
const bp = backendPath(projectPath);
|
|
210
|
+
await fse.ensureDir(bp);
|
|
211
|
+
|
|
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`
|
|
217
|
+
);
|
|
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.'));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ββ FLASK βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
227
|
+
async function flask(config, projectPath) {
|
|
228
|
+
const bp = backendPath(projectPath);
|
|
229
|
+
await fse.ensureDir(path.join(bp, 'app'));
|
|
230
|
+
|
|
231
|
+
const deps = config.backendDeps || [];
|
|
232
|
+
const hasDotenv = deps.includes('python-dotenv');
|
|
233
|
+
|
|
234
|
+
const appInit = `from flask import Flask
|
|
235
|
+
from flask_cors import CORS
|
|
236
|
+
|
|
237
|
+
def create_app():
|
|
238
|
+
app = Flask(__name__)
|
|
239
|
+
CORS(app)
|
|
240
|
+
|
|
241
|
+
@app.route('/')
|
|
242
|
+
def index():
|
|
243
|
+
return {'status': 'ok', 'message': 'API funcionando β
'}
|
|
244
|
+
|
|
245
|
+
@app.route('/api/health')
|
|
246
|
+
def health():
|
|
247
|
+
return {'status': 'healthy'}
|
|
248
|
+
|
|
249
|
+
return app
|
|
250
|
+
`;
|
|
251
|
+
const run_py = `${hasDotenv ? 'from dotenv import load_dotenv\nload_dotenv()\n\n' : ''}from app import create_app
|
|
252
|
+
app = create_app()
|
|
253
|
+
|
|
254
|
+
if __name__ == '__main__':
|
|
255
|
+
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');
|
|
263
|
+
|
|
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'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ββ SPRING BOOT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
269
|
+
async function spring(config, projectPath) {
|
|
270
|
+
const bp = backendPath(projectPath);
|
|
271
|
+
await fse.ensureDir(bp);
|
|
272
|
+
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
|
+
);
|
|
275
|
+
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
|
+
}
|
|
278
|
+
|
|
279
|
+
// ββ DENO/OAK ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
280
|
+
async function deno(config, projectPath) {
|
|
281
|
+
const bp = backendPath(projectPath);
|
|
282
|
+
await fse.ensureDir(bp);
|
|
283
|
+
|
|
284
|
+
const server = `import { Application, Router } from "https://deno.land/x/oak/mod.ts";
|
|
285
|
+
|
|
286
|
+
const app = new Application();
|
|
287
|
+
const router = new Router();
|
|
288
|
+
|
|
289
|
+
router.get("/", (ctx) => { ctx.response.body = { status: "ok", message: "API funcionando β
" }; });
|
|
290
|
+
router.get("/api/health", (ctx) => { ctx.response.body = { status: "healthy" }; });
|
|
291
|
+
|
|
292
|
+
app.use(router.routes());
|
|
293
|
+
app.use(router.allowedMethods());
|
|
294
|
+
|
|
295
|
+
console.log("π Servidor en http://localhost:8000");
|
|
296
|
+
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'));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ββ GO/GIN ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
305
|
+
async function gin(config, projectPath) {
|
|
306
|
+
const bp = backendPath(projectPath);
|
|
307
|
+
await fse.ensureDir(bp);
|
|
308
|
+
|
|
309
|
+
const main = `package main
|
|
310
|
+
|
|
311
|
+
import (
|
|
312
|
+
"net/http"
|
|
313
|
+
"github.com/gin-gonic/gin"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
func main() {
|
|
317
|
+
r := gin.Default()
|
|
318
|
+
|
|
319
|
+
r.GET("/", func(c *gin.Context) {
|
|
320
|
+
c.JSON(http.StatusOK, gin.H{"status": "ok", "message": "API funcionando β
"})
|
|
321
|
+
})
|
|
322
|
+
r.GET("/api/health", func(c *gin.Context) {
|
|
323
|
+
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
r.Run(":8080")
|
|
327
|
+
}
|
|
328
|
+
`;
|
|
329
|
+
await fse.writeFile(path.join(bp, 'main.go'), main);
|
|
330
|
+
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
|
+
}
|
|
336
|
+
|
|
337
|
+
export { generateBackend };
|