@benco112/quick-express-setup 1.0.4
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/package.json +44 -0
- package/quick-express-setup.js +231 -0
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@benco112/quick-express-setup",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "CLI profesional para generar APIs con Express, JWT y Netlify en segundos.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"quickexpress": "./quick-express-setup.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "quick-express-setup.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"quick-express-setup.js",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node quick-express-setup.js",
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/benco112/quick-express-setup.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"cli",
|
|
24
|
+
"express",
|
|
25
|
+
"generator",
|
|
26
|
+
"jwt",
|
|
27
|
+
"netlify",
|
|
28
|
+
"scaffold",
|
|
29
|
+
"create-app",
|
|
30
|
+
"api-rest"
|
|
31
|
+
],
|
|
32
|
+
"author": "benco112",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/benco112/quick-express-setup/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/benco112/quick-express-setup#readme",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
|
|
9
|
+
// --- MOTOR GRÁFICO (UI) ---
|
|
10
|
+
const style = {
|
|
11
|
+
reset: "\x1b[0m",
|
|
12
|
+
bold: "\x1b[1m",
|
|
13
|
+
dim: "\x1b[2m",
|
|
14
|
+
red: "\x1b[31m",
|
|
15
|
+
green: "\x1b[32m",
|
|
16
|
+
yellow: "\x1b[33m",
|
|
17
|
+
blue: "\x1b[34m",
|
|
18
|
+
cyan: "\x1b[36m",
|
|
19
|
+
bgGreen: "\x1b[42m",
|
|
20
|
+
black: "\x1b[30m",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
24
|
+
|
|
25
|
+
// Mascota ASCII
|
|
26
|
+
const botFace = `
|
|
27
|
+
${style.cyan}╭─────╮${style.reset}
|
|
28
|
+
${style.cyan}│ 0 0 │${style.reset} ${style.dim}<( ¿Qué vamos a construir hoy? )${style.reset}
|
|
29
|
+
${style.cyan}╰──┬──╯${style.reset}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const printBadge = (label, bgStyle) => {
|
|
33
|
+
process.stdout.write(`${bgStyle}${style.black} ${label} ${style.reset} `);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const step = async (title, actionFn) => {
|
|
37
|
+
process.stdout.write(`${style.cyan}●${style.reset} ${title}... `);
|
|
38
|
+
await sleep(400);
|
|
39
|
+
try {
|
|
40
|
+
actionFn();
|
|
41
|
+
process.stdout.write(`\r${style.green}✔${style.reset} ${title} ${style.dim}Completado${style.reset} \n`);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
process.stdout.write(`\r${style.red}✖${style.reset} ${title} ${style.red}Falló${style.reset} \n`);
|
|
44
|
+
console.error(style.dim + e.message + style.reset);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const box = (text) => {
|
|
49
|
+
const lines = text.split('\n');
|
|
50
|
+
const width = Math.max(...lines.map(l => l.length)) + 4;
|
|
51
|
+
console.log(style.dim + '╭' + '─'.repeat(width) + '╮' + style.reset);
|
|
52
|
+
lines.forEach(l => {
|
|
53
|
+
console.log(`${style.dim}│${style.reset} ${l.padEnd(width - 4)} ${style.dim}│${style.reset}`);
|
|
54
|
+
});
|
|
55
|
+
console.log(style.dim + '╰' + '─'.repeat(width) + '╯' + style.reset);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Función para pedir input al usuario
|
|
59
|
+
const ask = (question) => {
|
|
60
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
rl.question(style.green + "? " + style.reset + style.bold + question + " " + style.dim + "› " + style.reset, (answer) => {
|
|
63
|
+
rl.close();
|
|
64
|
+
resolve(answer.trim());
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// --- COMIENZO DEL SCRIPT ---
|
|
70
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
71
|
+
const __dirname = path.dirname(__filename); // Ojo: Esto es donde está el script, no donde se crea el proyecto
|
|
72
|
+
|
|
73
|
+
(async () => {
|
|
74
|
+
console.clear();
|
|
75
|
+
console.log(botFace);
|
|
76
|
+
await sleep(800);
|
|
77
|
+
|
|
78
|
+
box(`${style.bold}GENERADOR DE EXPRESS + JWT + NETLIFY${style.reset}\nVersion 3.0 "Interactive Mode"`);
|
|
79
|
+
console.log("");
|
|
80
|
+
|
|
81
|
+
// --- 0. PREGUNTAR NOMBRE ---
|
|
82
|
+
let projectName = await ask("Nombre del proyecto (ej: mi-api):");
|
|
83
|
+
if (!projectName) projectName = "express-jwt-api"; // Nombre por defecto
|
|
84
|
+
|
|
85
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
86
|
+
|
|
87
|
+
// --- 1. CREAR CARPETA Y ENTRAR ---
|
|
88
|
+
if (fs.existsSync(projectPath)) {
|
|
89
|
+
console.log(`\n${style.yellow}⚠ La carpeta "${projectName}" ya existe.${style.reset}`);
|
|
90
|
+
console.log(" Se intentará configurar dentro de ella.\n");
|
|
91
|
+
} else {
|
|
92
|
+
fs.mkdirSync(projectPath);
|
|
93
|
+
console.log(`\n${style.green}✔ Carpeta creada:${style.reset} ${projectName}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// MAGIA: Cambiamos el directorio de trabajo del proceso
|
|
97
|
+
process.chdir(projectPath);
|
|
98
|
+
|
|
99
|
+
// Ahora "Current Working Directory" es la nueva carpeta
|
|
100
|
+
// Todas las escrituras de archivos se harán ahí automáticamente.
|
|
101
|
+
|
|
102
|
+
// --- 2. PACKAGE.JSON ---
|
|
103
|
+
await step("Generando identidad (package.json)", () => {
|
|
104
|
+
if (!fs.existsSync('package.json')) {
|
|
105
|
+
execSync('npm init -y', { stdio: 'ignore' });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// --- 3. DEPENDENCIAS ---
|
|
110
|
+
console.log(`\n${style.blue}⚡ Descargando módulos en /${projectName}...${style.reset}`);
|
|
111
|
+
const deps = ['express', 'serverless-http', 'jsonwebtoken', 'cookie-parser', 'cors', 'dotenv'];
|
|
112
|
+
|
|
113
|
+
const total = 20;
|
|
114
|
+
for(let i=0; i<=total; i++) {
|
|
115
|
+
const bar = "█".repeat(i) + "░".repeat(total - i);
|
|
116
|
+
process.stdout.write(`\r ${style.dim}[${bar}]${style.reset} ${(i/total*100).toFixed(0)}%`);
|
|
117
|
+
await sleep(30);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
execSync(`npm install ${deps.join(' ')}`, { stdio: 'ignore' });
|
|
122
|
+
console.log(`\r ${style.green}✔ Módulos instalados correctamente${style.reset} \n`);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.log(`\r ${style.yellow}⚠ Warning en instalación (Check logs)${style.reset}\n`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- 4. ESTRUCTURA ---
|
|
128
|
+
// IMPORTANTE: functionsDir ahora es relativo a la nueva carpeta
|
|
129
|
+
const functionsDir = path.join(process.cwd(), 'functions');
|
|
130
|
+
|
|
131
|
+
await step("Arquitectura de carpetas", () => {
|
|
132
|
+
if (!fs.existsSync(functionsDir)) fs.mkdirSync(functionsDir);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// --- 5. CÓDIGO API ---
|
|
136
|
+
await step("Escribiendo código backend (api.js)", () => {
|
|
137
|
+
const apiContent = `
|
|
138
|
+
require('dotenv').config();
|
|
139
|
+
const express = require('express');
|
|
140
|
+
const serverless = require('serverless-http');
|
|
141
|
+
const cookieParser = require('cookie-parser');
|
|
142
|
+
const jwt = require('jsonwebtoken');
|
|
143
|
+
const cors = require('cors');
|
|
144
|
+
|
|
145
|
+
const app = express();
|
|
146
|
+
const router = express.Router();
|
|
147
|
+
|
|
148
|
+
// CONFIG
|
|
149
|
+
const SECRET_KEY = process.env.JWT_SECRET || 'secreto-inseguro';
|
|
150
|
+
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
151
|
+
|
|
152
|
+
app.use(cors({ origin: [FRONTEND_URL, 'http://localhost:3000'], credentials: true }));
|
|
153
|
+
app.use(cookieParser());
|
|
154
|
+
app.use(express.json());
|
|
155
|
+
|
|
156
|
+
// AUTH
|
|
157
|
+
const verificarAuth = (req, res, next) => {
|
|
158
|
+
const token = req.cookies.jwt_token;
|
|
159
|
+
if (!token) return res.status(401).json({ error: '⛔ No autenticado' });
|
|
160
|
+
try {
|
|
161
|
+
const user = jwt.verify(token, SECRET_KEY);
|
|
162
|
+
req.user = user;
|
|
163
|
+
next();
|
|
164
|
+
} catch (err) { res.status(403).json({ error: '💀 Token inválido' }); }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
router.post('/auth/login', (req, res) => {
|
|
168
|
+
const { username } = req.body;
|
|
169
|
+
const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' });
|
|
170
|
+
res.cookie('jwt_token', token, { httpOnly: true, secure: true, sameSite: 'none', maxAge: 3600000 });
|
|
171
|
+
res.json({ mensaje: 'Login exitoso 🍪' });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
router.post('/auth/logout', (req, res) => {
|
|
175
|
+
res.clearCookie('jwt_token', { httpOnly: true, secure: true, sameSite: 'none' });
|
|
176
|
+
res.json({ mensaje: 'Bye bye 👋' });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
router.get('/dashboard', verificarAuth, (req, res) => {
|
|
180
|
+
res.json({ secreto: 'Datos confidenciales', usuario: req.user });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
router.get('/', (req, res) => res.json({ status: 'Online 🟢' }));
|
|
184
|
+
|
|
185
|
+
app.use('/.netlify/functions/api', router);
|
|
186
|
+
app.use('/', router);
|
|
187
|
+
|
|
188
|
+
// ARRANQUE LOCAL
|
|
189
|
+
if (!process.env.NETLIFY && !process.env.AWS_LAMBDA_FUNCTION_VERSION) {
|
|
190
|
+
const PORT = 3000;
|
|
191
|
+
app.listen(PORT, () => {
|
|
192
|
+
console.log(\`🚀 Server: http://localhost:\${PORT}\`);
|
|
193
|
+
console.log(\`👉 Login: http://localhost:\${PORT}/auth/login\`);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
module.exports.handler = serverless(app);
|
|
197
|
+
`;
|
|
198
|
+
fs.writeFileSync(path.join(functionsDir, 'api.js'), apiContent);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// --- 6. CONFIGURACIONES ---
|
|
202
|
+
await step("Configurando Netlify & Git", () => {
|
|
203
|
+
const netlifyToml = `[build]\n functions = "functions"\n[[redirects]]\n from = "/*"\n to = "/.netlify/functions/api"\n status = 200`;
|
|
204
|
+
fs.writeFileSync('netlify.toml', netlifyToml);
|
|
205
|
+
|
|
206
|
+
const gitignoreContent = `node_modules/\n.env\n.netlify/\n.DS_Store`;
|
|
207
|
+
fs.writeFileSync('.gitignore', gitignoreContent);
|
|
208
|
+
|
|
209
|
+
const envContent = `JWT_SECRET=super-secreto-largo\nFRONTEND_URL=http://localhost:5173`;
|
|
210
|
+
if (!fs.existsSync('.env')) fs.writeFileSync('.env', envContent);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// --- 7. SCRIPTS ---
|
|
214
|
+
await step("Optimizando package.json", () => {
|
|
215
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
216
|
+
delete pkg.type; delete pkg.main;
|
|
217
|
+
pkg.scripts = { "start": "node functions/api.js", "dev": "node functions/api.js", "build": "echo 'OK'" };
|
|
218
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
console.log("\n");
|
|
222
|
+
|
|
223
|
+
// --- FINAL SCREEN ---
|
|
224
|
+
printBadge(" HECHO ", style.bgGreen);
|
|
225
|
+
process.stdout.write(` ${style.bold}Proyecto creado en /${projectName}${style.reset}\n\n`);
|
|
226
|
+
|
|
227
|
+
console.log(`${style.dim}Siguientes pasos:${style.reset}`);
|
|
228
|
+
console.log(` ${style.cyan}➜${style.reset} ${style.bold}cd ${projectName}${style.reset}`);
|
|
229
|
+
console.log(` ${style.cyan}➜${style.reset} ${style.bold}npm run dev${style.reset}`);
|
|
230
|
+
console.log("\n");
|
|
231
|
+
})();
|