mgpanel-cli 1.0.1 → 1.0.2
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/bin/mgpanel.js +318 -20
- package/package.json +1 -1
package/bin/mgpanel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const http = require("http");
|
|
3
4
|
const fs = require("fs");
|
|
4
5
|
const path = require("path");
|
|
5
6
|
|
|
@@ -9,11 +10,24 @@ MGPanel CLI
|
|
|
9
10
|
|
|
10
11
|
Uso:
|
|
11
12
|
mgpanel init <nombre-proyecto>
|
|
13
|
+
|
|
14
|
+
mgpanel make page <nombre-pagina>
|
|
12
15
|
mgpanel make module <nombre-modulo>
|
|
13
16
|
|
|
17
|
+
mgpanel add module <nombre-modulo> --to <nombre-pagina>
|
|
18
|
+
|
|
19
|
+
mgpanel dev [puerto]
|
|
20
|
+
|
|
14
21
|
Ejemplos:
|
|
15
22
|
mgpanel init miweb
|
|
16
|
-
|
|
23
|
+
cd miweb
|
|
24
|
+
|
|
25
|
+
mgpanel make page home
|
|
26
|
+
mgpanel make module header
|
|
27
|
+
mgpanel add module header --to home
|
|
28
|
+
|
|
29
|
+
mgpanel dev
|
|
30
|
+
mgpanel dev 8080
|
|
17
31
|
`);
|
|
18
32
|
}
|
|
19
33
|
|
|
@@ -27,6 +41,19 @@ function writeFileIfNotExists(filePath, content) {
|
|
|
27
41
|
return true;
|
|
28
42
|
}
|
|
29
43
|
|
|
44
|
+
function readJSON(filePath) {
|
|
45
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeJSON(filePath, obj) {
|
|
50
|
+
fs.writeFileSync(filePath, JSON.stringify(obj, null, 2), "utf8");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isValidSlug(name) {
|
|
54
|
+
return /^[a-z0-9-]+$/i.test(name);
|
|
55
|
+
}
|
|
56
|
+
|
|
30
57
|
/**
|
|
31
58
|
* mgpanel init <projectName>
|
|
32
59
|
*/
|
|
@@ -40,27 +67,174 @@ function cmdInit(projectName = "mgpanel-project") {
|
|
|
40
67
|
|
|
41
68
|
console.log("🚀 Creando proyecto MGPanel:", projectName);
|
|
42
69
|
|
|
70
|
+
// base
|
|
43
71
|
ensureDir(projectPath);
|
|
72
|
+
ensureDir(path.join(projectPath, "pages"));
|
|
44
73
|
ensureDir(path.join(projectPath, "modules"));
|
|
74
|
+
ensureDir(path.join(projectPath, "dev"));
|
|
45
75
|
ensureDir(path.join(projectPath, "assets", "css"));
|
|
46
76
|
ensureDir(path.join(projectPath, "assets", "js"));
|
|
47
77
|
|
|
78
|
+
// Global CSS/JS
|
|
79
|
+
const globalCSS = `/* MGPanel Global Styles */
|
|
80
|
+
:root { color-scheme: light; }
|
|
81
|
+
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
|
82
|
+
`;
|
|
83
|
+
fs.writeFileSync(path.join(projectPath, "assets", "css", "style.css"), globalCSS, "utf8");
|
|
84
|
+
|
|
85
|
+
const globalJS = `// MGPanel Global JS
|
|
86
|
+
console.log("[MGPanel] Global JS loaded");
|
|
87
|
+
`;
|
|
88
|
+
fs.writeFileSync(path.join(projectPath, "assets", "js", "app.js"), globalJS, "utf8");
|
|
89
|
+
|
|
90
|
+
// index.html solo preview local
|
|
48
91
|
const indexHTML = `<!doctype html>
|
|
49
92
|
<html lang="es">
|
|
50
93
|
<head>
|
|
51
94
|
<meta charset="utf-8" />
|
|
52
95
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
53
|
-
<title>MGPanel</title>
|
|
96
|
+
<title>MGPanel (Local Preview)</title>
|
|
97
|
+
|
|
98
|
+
<link rel="stylesheet" href="assets/css/style.css" />
|
|
99
|
+
<script src="assets/js/app.js" defer></script>
|
|
54
100
|
</head>
|
|
55
101
|
<body>
|
|
56
|
-
<
|
|
102
|
+
<div id="app"></div>
|
|
103
|
+
<script src="dev/preview.js"></script>
|
|
57
104
|
</body>
|
|
58
105
|
</html>
|
|
59
106
|
`;
|
|
60
|
-
|
|
61
107
|
fs.writeFileSync(path.join(projectPath, "index.html"), indexHTML, "utf8");
|
|
62
108
|
|
|
109
|
+
// dev/preview.js: carga una page leyendo page.json y componiendo módulos
|
|
110
|
+
const previewJS = `async function loadModule(name) {
|
|
111
|
+
const base = \`modules/\${name}/\${name}\`;
|
|
112
|
+
|
|
113
|
+
// HTML
|
|
114
|
+
const html = await fetch(\`\${base}.html\`).then(r => r.text());
|
|
115
|
+
|
|
116
|
+
// CSS
|
|
117
|
+
const cssHref = \`\${base}.css\`;
|
|
118
|
+
if (!document.querySelector(\`link[data-mgpanel-css="\${cssHref}"]\`)) {
|
|
119
|
+
const link = document.createElement("link");
|
|
120
|
+
link.rel = "stylesheet";
|
|
121
|
+
link.href = cssHref;
|
|
122
|
+
link.setAttribute("data-mgpanel-css", cssHref);
|
|
123
|
+
document.head.appendChild(link);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// JS
|
|
127
|
+
const jsSrc = \`\${base}.js\`;
|
|
128
|
+
if (!document.querySelector(\`script[data-mgpanel-js="\${jsSrc}"]\`)) {
|
|
129
|
+
const script = document.createElement("script");
|
|
130
|
+
script.src = jsSrc;
|
|
131
|
+
script.defer = true;
|
|
132
|
+
script.setAttribute("data-mgpanel-js", jsSrc);
|
|
133
|
+
document.body.appendChild(script);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return html;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function renderPage(pageName) {
|
|
140
|
+
const configPath = \`pages/\${pageName}/page.json\`;
|
|
141
|
+
const page = await fetch(configPath).then(r => r.json());
|
|
142
|
+
|
|
143
|
+
// Title
|
|
144
|
+
if (page.title) document.title = page.title;
|
|
145
|
+
|
|
146
|
+
// Meta description
|
|
147
|
+
if (page.description) {
|
|
148
|
+
let meta = document.querySelector('meta[name="description"]');
|
|
149
|
+
if (!meta) {
|
|
150
|
+
meta = document.createElement("meta");
|
|
151
|
+
meta.setAttribute("name", "description");
|
|
152
|
+
document.head.appendChild(meta);
|
|
153
|
+
}
|
|
154
|
+
meta.setAttribute("content", page.description);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const app = document.getElementById("app");
|
|
158
|
+
app.innerHTML = "";
|
|
159
|
+
|
|
160
|
+
for (const item of (page.modules || [])) {
|
|
161
|
+
const html = await loadModule(item.name);
|
|
162
|
+
const wrapper = document.createElement("div");
|
|
163
|
+
wrapper.setAttribute("data-mgpanel-module", item.name);
|
|
164
|
+
wrapper.innerHTML = html;
|
|
165
|
+
app.appendChild(wrapper);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const pageFromPath = () => {
|
|
170
|
+
const p = window.location.pathname.replace(/^\\/+/g, "").replace(/\\/+$/g, "");
|
|
171
|
+
return p === "" ? "home" : p;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
renderPage(pageFromPath()).catch(err => {
|
|
175
|
+
console.error("[MGPanel Preview] Error:", err);
|
|
176
|
+
document.getElementById("app").innerHTML =
|
|
177
|
+
"<h2>Error cargando la página. ¿Existe pages/<page>/page.json?</h2>";
|
|
178
|
+
});
|
|
179
|
+
`;
|
|
180
|
+
fs.writeFileSync(path.join(projectPath, "dev", "preview.js"), previewJS, "utf8");
|
|
181
|
+
|
|
182
|
+
// Crear page home por defecto
|
|
183
|
+
ensureDir(path.join(projectPath, "pages", "home"));
|
|
184
|
+
const homePage = {
|
|
185
|
+
route: "/",
|
|
186
|
+
title: "Home",
|
|
187
|
+
description: "Página principal creada con MGPanel.",
|
|
188
|
+
modules: []
|
|
189
|
+
};
|
|
190
|
+
fs.writeFileSync(
|
|
191
|
+
path.join(projectPath, "pages", "home", "page.json"),
|
|
192
|
+
JSON.stringify(homePage, null, 2),
|
|
193
|
+
"utf8"
|
|
194
|
+
);
|
|
195
|
+
|
|
63
196
|
console.log("✅ Proyecto creado correctamente");
|
|
197
|
+
console.log("👉 Ejecuta: cd " + projectName + " && mgpanel dev");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* mgpanel make page <pageName>
|
|
202
|
+
*/
|
|
203
|
+
function cmdMakePage(pageName) {
|
|
204
|
+
if (!pageName) {
|
|
205
|
+
console.log("❌ Falta el nombre de la página");
|
|
206
|
+
console.log("👉 Usa: mgpanel make page <nombre-pagina>");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
if (!isValidSlug(pageName)) {
|
|
210
|
+
console.log("❌ Nombre de página inválido. Usa letras/números/guiones. Ej: home, about-us");
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const pagesDir = path.join(process.cwd(), "pages");
|
|
215
|
+
const pageDir = path.join(pagesDir, pageName);
|
|
216
|
+
ensureDir(pageDir);
|
|
217
|
+
|
|
218
|
+
const pageJsonPath = path.join(pageDir, "page.json");
|
|
219
|
+
const created = writeFileIfNotExists(
|
|
220
|
+
pageJsonPath,
|
|
221
|
+
JSON.stringify(
|
|
222
|
+
{
|
|
223
|
+
route: `/${pageName === "home" ? "" : pageName}`.replace(/\/$/, "/"),
|
|
224
|
+
title: pageName.charAt(0).toUpperCase() + pageName.slice(1),
|
|
225
|
+
description: `Descripción de la página ${pageName}.`,
|
|
226
|
+
modules: []
|
|
227
|
+
},
|
|
228
|
+
null,
|
|
229
|
+
2
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (created) {
|
|
234
|
+
console.log(`📄 Página "${pageName}" creada en: pages/${pageName}/page.json`);
|
|
235
|
+
} else {
|
|
236
|
+
console.log(`↩️ La página "${pageName}" ya existe: pages/${pageName}/page.json`);
|
|
237
|
+
}
|
|
64
238
|
}
|
|
65
239
|
|
|
66
240
|
/**
|
|
@@ -72,18 +246,13 @@ function cmdMakeModule(moduleName) {
|
|
|
72
246
|
console.log("👉 Usa: mgpanel make module <nombre-modulo>");
|
|
73
247
|
process.exit(1);
|
|
74
248
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const valid = /^[a-z0-9-]+$/i.test(moduleName);
|
|
78
|
-
if (!valid) {
|
|
79
|
-
console.log("❌ Nombre de módulo inválido. Usa letras/números/guiones, ej: home, about-us");
|
|
249
|
+
if (!isValidSlug(moduleName)) {
|
|
250
|
+
console.log("❌ Nombre de módulo inválido. Usa letras/números/guiones. Ej: header, hero-banner");
|
|
80
251
|
process.exit(1);
|
|
81
252
|
}
|
|
82
253
|
|
|
83
|
-
const
|
|
84
|
-
const modulesDir = path.join(cwd, "modules");
|
|
254
|
+
const modulesDir = path.join(process.cwd(), "modules");
|
|
85
255
|
const moduleDir = path.join(modulesDir, moduleName);
|
|
86
|
-
|
|
87
256
|
ensureDir(moduleDir);
|
|
88
257
|
|
|
89
258
|
const htmlPath = path.join(moduleDir, `${moduleName}.html`);
|
|
@@ -119,23 +288,152 @@ console.log("[MGPanel] Module loaded:", "${moduleName}");
|
|
|
119
288
|
if (skipped.length) console.log("↩️ Ya existían:", skipped.join(", "));
|
|
120
289
|
}
|
|
121
290
|
|
|
122
|
-
|
|
291
|
+
/**
|
|
292
|
+
* mgpanel add module <moduleName> --to <pageName>
|
|
293
|
+
*/
|
|
294
|
+
function cmdAddModule(moduleName, pageName) {
|
|
295
|
+
if (!moduleName || !pageName) {
|
|
296
|
+
console.log("❌ Uso inválido");
|
|
297
|
+
console.log("👉 Usa: mgpanel add module <modulo> --to <pagina>");
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
123
300
|
|
|
124
|
-
const
|
|
301
|
+
const moduleDir = path.join(process.cwd(), "modules", moduleName);
|
|
302
|
+
if (!fs.existsSync(moduleDir)) {
|
|
303
|
+
console.log(`❌ El módulo "${moduleName}" no existe en modules/${moduleName}/`);
|
|
304
|
+
console.log(`👉 Crea el módulo primero: mgpanel make module ${moduleName}`);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const pageJsonPath = path.join(process.cwd(), "pages", pageName, "page.json");
|
|
309
|
+
if (!fs.existsSync(pageJsonPath)) {
|
|
310
|
+
console.log(`❌ La página "${pageName}" no existe (pages/${pageName}/page.json)`);
|
|
311
|
+
console.log(`👉 Crea la página primero: mgpanel make page ${pageName}`);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const page = readJSON(pageJsonPath);
|
|
316
|
+
page.modules = Array.isArray(page.modules) ? page.modules : [];
|
|
317
|
+
|
|
318
|
+
const already = page.modules.some(m => m && m.name === moduleName);
|
|
319
|
+
if (already) {
|
|
320
|
+
console.log(`↩️ El módulo "${moduleName}" ya está agregado a la página "${pageName}".`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
page.modules.push({ name: moduleName });
|
|
325
|
+
writeJSON(pageJsonPath, page);
|
|
326
|
+
|
|
327
|
+
console.log(`✅ Módulo "${moduleName}" agregado a pages/${pageName}/page.json`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function cmdDev(port = 3000) {
|
|
331
|
+
const root = process.cwd();
|
|
332
|
+
|
|
333
|
+
const server = http.createServer((req, res) => {
|
|
334
|
+
// 1) Normalizar URL (sin querystring)
|
|
335
|
+
let urlPath = req.url || "/";
|
|
336
|
+
urlPath = decodeURIComponent(urlPath.split("?")[0]);
|
|
337
|
+
|
|
338
|
+
// 2) Seguridad básica: bloquear path traversal
|
|
339
|
+
if (urlPath.includes("..")) {
|
|
340
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
341
|
+
res.end("400 Bad Request");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 3) Root -> index.html
|
|
346
|
+
if (urlPath === "/") urlPath = "/index.html";
|
|
347
|
+
|
|
348
|
+
// 4) SPA fallback: si NO hay extensión (ej: /nosotros), devolver index.html
|
|
349
|
+
const hasExtension = path.extname(urlPath) !== "";
|
|
350
|
+
if (!hasExtension) {
|
|
351
|
+
urlPath = "/index.html";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const fullPath = path.join(root, urlPath);
|
|
355
|
+
|
|
356
|
+
fs.readFile(fullPath, (err, content) => {
|
|
357
|
+
if (err) {
|
|
358
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
359
|
+
res.end("404 Not Found");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
125
362
|
|
|
363
|
+
const ext = path.extname(fullPath).toLowerCase();
|
|
364
|
+
const types = {
|
|
365
|
+
".html": "text/html; charset=utf-8",
|
|
366
|
+
".js": "application/javascript; charset=utf-8",
|
|
367
|
+
".css": "text/css; charset=utf-8",
|
|
368
|
+
".json": "application/json; charset=utf-8",
|
|
369
|
+
".png": "image/png",
|
|
370
|
+
".jpg": "image/jpeg",
|
|
371
|
+
".jpeg": "image/jpeg",
|
|
372
|
+
".svg": "image/svg+xml"
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
res.writeHead(200, {
|
|
376
|
+
"Content-Type": types[ext] || "application/octet-stream",
|
|
377
|
+
"Cache-Control": "no-store"
|
|
378
|
+
});
|
|
379
|
+
res.end(content);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
server.listen(port, () => {
|
|
384
|
+
console.log(`🚀 MGPanel Dev Server`);
|
|
385
|
+
console.log(`👉 http://localhost:${port}`);
|
|
386
|
+
console.log(`🛑 Ctrl + C para detener`);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ------------------ Router CLI ------------------
|
|
391
|
+
|
|
392
|
+
const args = process.argv.slice(2);
|
|
126
393
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
127
394
|
printHelp();
|
|
128
395
|
process.exit(0);
|
|
129
396
|
}
|
|
130
397
|
|
|
131
|
-
const [cmd1, cmd2, cmd3] = args;
|
|
398
|
+
const [cmd1, cmd2, cmd3, cmd4, cmd5] = args;
|
|
132
399
|
|
|
133
400
|
if (cmd1 === "init") {
|
|
134
401
|
cmdInit(cmd2);
|
|
135
|
-
|
|
402
|
+
process.exit(0);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (cmd1 === "make" && cmd2 === "page") {
|
|
406
|
+
cmdMakePage(cmd3);
|
|
407
|
+
process.exit(0);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (cmd1 === "make" && cmd2 === "module") {
|
|
136
411
|
cmdMakeModule(cmd3);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
412
|
+
process.exit(0);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (cmd1 === "add" && cmd2 === "module") {
|
|
416
|
+
const moduleName = cmd3;
|
|
417
|
+
const flag = cmd4;
|
|
418
|
+
const pageName = cmd5;
|
|
419
|
+
|
|
420
|
+
if (flag !== "--to") {
|
|
421
|
+
console.log("❌ Falta --to <pagina>");
|
|
422
|
+
console.log("👉 Usa: mgpanel add module <modulo> --to <pagina>");
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
cmdAddModule(moduleName, pageName);
|
|
427
|
+
process.exit(0);
|
|
141
428
|
}
|
|
429
|
+
|
|
430
|
+
if (cmd1 === "dev") {
|
|
431
|
+
const port = cmd2 ? Number(cmd2) : 3000;
|
|
432
|
+
cmdDev(port);
|
|
433
|
+
// NO process.exit aquí, el server debe quedarse vivo
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
console.log("❌ Comando no reconocido.");
|
|
438
|
+
printHelp();
|
|
439
|
+
process.exit(1);
|