novatec-cli 1.0.2 → 3.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.
@@ -0,0 +1,97 @@
1
+ import path from 'path';
2
+ import fse from 'fs-extra';
3
+
4
+ export async function generateDocker(config, root) {
5
+ const isPython = ['fastapi','django','flask'].includes(config.backend);
6
+ const isJava = config.backend === 'spring';
7
+ const isGo = config.backend === 'gin';
8
+ await writeFrontendDockerfile(root);
9
+ await writeBackendDockerfile(root, config.backend, isPython, isJava, isGo);
10
+ await writeDockerCompose(root, config);
11
+ const ignore = `node_modules\ndist\n.env\n*.log\nlogs/\n.git\nREADME.md\n`;
12
+ await fse.writeFile(path.join(root, 'frontend', '.dockerignore'), ignore);
13
+ await fse.writeFile(path.join(root, 'backend', '.dockerignore'), ignore);
14
+ }
15
+
16
+ async function writeFrontendDockerfile(root) {
17
+ await fse.writeFile(path.join(root, 'frontend', 'Dockerfile'),
18
+ `FROM node:20-alpine AS builder
19
+ WORKDIR /app
20
+ COPY package*.json ./
21
+ RUN npm ci
22
+ COPY . .
23
+ RUN npm run build
24
+
25
+ FROM nginx:alpine AS production
26
+ COPY --from=builder /app/dist /usr/share/nginx/html
27
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
28
+ EXPOSE 80
29
+ CMD ["nginx", "-g", "daemon off;"]
30
+ `);
31
+ await fse.writeFile(path.join(root, 'frontend', 'nginx.conf'),
32
+ `server {
33
+ listen 80;
34
+ root /usr/share/nginx/html;
35
+ index index.html;
36
+ location / { try_files $uri $uri/ /index.html; }
37
+ location /api { proxy_pass http://backend:3001; proxy_set_header Host $host; }
38
+ gzip on;
39
+ gzip_types text/plain text/css application/json application/javascript;
40
+ }
41
+ `);
42
+ }
43
+
44
+ async function writeBackendDockerfile(root, backend, isPython, isJava, isGo) {
45
+ let content;
46
+ if (isPython) {
47
+ const cmd = backend === 'fastapi' ? 'CMD ["uvicorn","main:app","--host","0.0.0.0","--port","8000"]'
48
+ : backend === 'django' ? 'CMD ["python","manage.py","runserver","0.0.0.0:8000"]'
49
+ : 'CMD ["python","run.py"]';
50
+ content = `FROM python:3.11-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY . .\nEXPOSE 8000\n${cmd}\n`;
51
+ } else if (isJava) {
52
+ content = `FROM maven:3.9-eclipse-temurin-21 AS builder\nWORKDIR /app\nCOPY pom.xml .\nRUN mvn dependency:go-offline\nCOPY src ./src\nRUN mvn package -DskipTests\n\nFROM eclipse-temurin:21-jre-alpine\nWORKDIR /app\nCOPY --from=builder /app/target/*.jar app.jar\nEXPOSE 8080\nCMD ["java","-jar","app.jar"]\n`;
53
+ } else if (isGo) {
54
+ content = `FROM golang:1.22-alpine AS builder\nWORKDIR /app\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\nRUN go build -o main .\n\nFROM alpine:latest\nWORKDIR /app\nCOPY --from=builder /app/main .\nEXPOSE 8080\nCMD ["./main"]\n`;
55
+ } else {
56
+ content = `FROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=production\nCOPY . .\n\nFROM node:20-alpine AS production\nWORKDIR /app\nRUN addgroup -S app && adduser -S app -G app\nCOPY --from=builder /app .\nUSER app\nEXPOSE 3001\nCMD ["node","src/index.js"]\n`;
57
+ }
58
+ await fse.writeFile(path.join(root, 'backend', 'Dockerfile'), content);
59
+ }
60
+
61
+ async function writeDockerCompose(root, config) {
62
+ const isPython = ['fastapi','django','flask'].includes(config.backend);
63
+ const bePort = isPython || config.backend === 'gin' ? '8000' : '3001';
64
+ const dbServices = {
65
+ postgres: `\n db:\n image: postgres:16-alpine\n restart: unless-stopped\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`,
66
+ mysql: `\n db:\n image: mysql:8\n restart: unless-stopped\n environment:\n MYSQL_DATABASE: mydb\n MYSQL_ROOT_PASSWORD: password\n ports:\n - "3306:3306"\n volumes:\n - mysqldata:/var/lib/mysql`,
67
+ mongo: `\n db:\n image: mongo:7\n restart: unless-stopped\n ports:\n - "27017:27017"\n volumes:\n - mongodata:/data/db`,
68
+ };
69
+ const volDefs = { postgres: '\nvolumes:\n pgdata:', mysql: '\nvolumes:\n mysqldata:', mongo: '\nvolumes:\n mongodata:' };
70
+ const db = dbServices[config.db] || '';
71
+ const vol = volDefs[config.db] || '';
72
+
73
+ await fse.writeFile(path.join(root, 'docker-compose.yml'),
74
+ `version: '3.9'
75
+ services:
76
+ frontend:
77
+ build:
78
+ context: ./frontend
79
+ target: production
80
+ ports:
81
+ - "80:80"
82
+ depends_on:
83
+ - backend
84
+ restart: unless-stopped
85
+
86
+ backend:
87
+ build: ./backend
88
+ ports:
89
+ - "${bePort}:${bePort}"
90
+ env_file: ./backend/.env
91
+ restart: unless-stopped
92
+ volumes:
93
+ - ./backend/logs:/app/logs
94
+ ${db}
95
+ ${vol}
96
+ `);
97
+ }
@@ -0,0 +1,132 @@
1
+ import path from 'path';
2
+ import fse from 'fs-extra';
3
+
4
+ export async function generateSecurity(config, root) {
5
+ const isPython = ['fastapi','django','flask'].includes(config.backend);
6
+ if (isPython) return;
7
+ const beDir = path.join(root, 'backend', 'src');
8
+ await fse.ensureDir(beDir);
9
+ await writeSecurityMiddleware(beDir);
10
+ await writeSwagger(beDir, config);
11
+ await writeMainWithSecurity(root, config);
12
+ }
13
+
14
+ async function writeSecurityMiddleware(beDir) {
15
+ await fse.ensureDir(path.join(beDir, 'middlewares'));
16
+ await fse.writeFile(path.join(beDir, 'middlewares', 'security.js'),
17
+ `import helmet from 'helmet';
18
+ import cors from 'cors';
19
+ import rateLimit from 'express-rate-limit';
20
+ import mongoSanitize from 'express-mongo-sanitize';
21
+
22
+ export const helmetMiddleware = helmet({
23
+ contentSecurityPolicy: {
24
+ directives: {
25
+ defaultSrc: ["'self'"],
26
+ styleSrc: ["'self'", "'unsafe-inline'"],
27
+ scriptSrc: ["'self'"],
28
+ imgSrc: ["'self'", 'data:', 'https:'],
29
+ },
30
+ },
31
+ });
32
+
33
+ export const corsMiddleware = cors({
34
+ origin: process.env.FRONTEND_URL || 'http://localhost:5173',
35
+ credentials: true,
36
+ methods: ['GET','POST','PUT','DELETE','PATCH','OPTIONS'],
37
+ allowedHeaders: ['Content-Type','Authorization'],
38
+ });
39
+
40
+ export const rateLimiter = rateLimit({
41
+ windowMs: 15 * 60 * 1000, // 15 min
42
+ max: 100,
43
+ standardHeaders: true,
44
+ legacyHeaders: false,
45
+ message: { error: 'Demasiadas peticiones, intenta más tarde.' },
46
+ });
47
+
48
+ export const authRateLimiter = rateLimit({
49
+ windowMs: 15 * 60 * 1000,
50
+ max: 10,
51
+ message: { error: 'Demasiados intentos de login.' },
52
+ });
53
+
54
+ export const sanitize = mongoSanitize();
55
+ `);
56
+ }
57
+
58
+ async function writeSwagger(beDir, config) {
59
+ await fse.writeFile(path.join(beDir, 'swagger.js'),
60
+ `import swaggerJsdoc from 'swagger-jsdoc';
61
+ import swaggerUi from 'swagger-ui-express';
62
+
63
+ const options = {
64
+ definition: {
65
+ openapi: '3.0.0',
66
+ info: {
67
+ title: '${config.name || "API"} — NovaTec',
68
+ version: '1.0.0',
69
+ description: 'Documentación automática generada por NovaTec CLI',
70
+ },
71
+ servers: [{ url: 'http://localhost:3001', description: 'Desarrollo' }],
72
+ components: {
73
+ securitySchemes: {
74
+ bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
75
+ },
76
+ },
77
+ security: [{ bearerAuth: [] }],
78
+ },
79
+ apis: ['./src/**/*.routes.js'],
80
+ };
81
+
82
+ export const swaggerSpec = swaggerJsdoc(options);
83
+ export const swaggerMiddleware = swaggerUi.serve;
84
+ export const swaggerHandler = swaggerUi.setup(swaggerSpec, {
85
+ customCss: '.swagger-ui .topbar { background: #111; } .swagger-ui .topbar-wrapper img { display: none; }',
86
+ customSiteTitle: 'API Docs — NovaTec',
87
+ });
88
+ `);
89
+ }
90
+
91
+ async function writeMainWithSecurity(root, config) {
92
+ const indexPath = path.join(root, 'backend', 'src', 'index.js');
93
+ const content =
94
+ `import express from 'express';
95
+ import 'dotenv/config';
96
+ import { helmetMiddleware, corsMiddleware, rateLimiter, sanitize } from './middlewares/security.js';
97
+ import { swaggerMiddleware, swaggerHandler } from './swagger.js';
98
+
99
+ const app = express();
100
+ const PORT = process.env.PORT || 3001;
101
+
102
+ // ── Seguridad ─────────────────────────────────────────────
103
+ app.use(helmetMiddleware);
104
+ app.use(corsMiddleware);
105
+ app.use(rateLimiter);
106
+ app.use(express.json({ limit: '10kb' }));
107
+ app.use(express.urlencoded({ extended: true, limit: '10kb' }));
108
+ app.use(sanitize);
109
+
110
+ // ── Swagger ───────────────────────────────────────────────
111
+ app.use('/api-docs', swaggerMiddleware, swaggerHandler);
112
+
113
+ // ── Rutas ─────────────────────────────────────────────────
114
+ app.get('/api/health', (req, res) => res.json({ status: 'ok', timestamp: new Date().toISOString() }));
115
+
116
+ // TODO: importar tus rutas aquí
117
+ // import authRoutes from './auth/auth.routes.js';
118
+ // app.use('/api/auth', authRoutes);
119
+
120
+ // ── Error handler ─────────────────────────────────────────
121
+ app.use((err, req, res, next) => {
122
+ console.error(err.stack);
123
+ res.status(err.status || 500).json({ error: err.message || 'Error interno del servidor' });
124
+ });
125
+
126
+ app.listen(PORT, () => {
127
+ console.log(\`🚀 Servidor en http://localhost:\${PORT}\`);
128
+ console.log(\`📚 API Docs en http://localhost:\${PORT}/api-docs\`);
129
+ });
130
+ `;
131
+ await fse.writeFile(indexPath, content);
132
+ }