mgpanel-cli 1.0.4 → 1.0.7
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 +647 -9
- package/package.json +1 -1
package/bin/mgpanel.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const http = require("http");
|
|
4
|
+
const https = require("https");
|
|
4
5
|
const fs = require("fs");
|
|
5
6
|
const path = require("path");
|
|
7
|
+
const { URL } = require("url");
|
|
6
8
|
|
|
7
9
|
function printHelp() {
|
|
8
10
|
console.log(`
|
|
@@ -18,6 +20,8 @@ Uso:
|
|
|
18
20
|
|
|
19
21
|
mgpanel dev [puerto]
|
|
20
22
|
|
|
23
|
+
mgpanel publish --account <cuenta> [--token <token>] [--api-url <url>]
|
|
24
|
+
|
|
21
25
|
Ejemplos:
|
|
22
26
|
mgpanel init miweb
|
|
23
27
|
cd miweb
|
|
@@ -28,6 +32,9 @@ Ejemplos:
|
|
|
28
32
|
|
|
29
33
|
mgpanel dev
|
|
30
34
|
mgpanel dev 8080
|
|
35
|
+
|
|
36
|
+
mgpanel publish --account zzz-eloymanuel --token mi-token
|
|
37
|
+
mgpanel publish --account zzz-eloymanuel --token mi-token --api-url https://api.mgpanel.com
|
|
31
38
|
`);
|
|
32
39
|
}
|
|
33
40
|
|
|
@@ -106,6 +113,28 @@ console.log("[MGPanel] Global JS loaded");
|
|
|
106
113
|
`;
|
|
107
114
|
fs.writeFileSync(path.join(projectPath, "index.html"), indexHTML, "utf8");
|
|
108
115
|
|
|
116
|
+
// .env: Variables de entorno para publish
|
|
117
|
+
const envContent = `# MGPanel CLI - Variables de entorno
|
|
118
|
+
# Descomenta y configura estas variables para usar 'mgpanel publish'
|
|
119
|
+
|
|
120
|
+
# Token de autenticación para publicar (obtén uno desde el panel de MGPanel)
|
|
121
|
+
# MGPANEL_TOKEN=tu-token-aqui
|
|
122
|
+
|
|
123
|
+
# URL de la API (opcional, por defecto usa https://dev.mgpanel.co)
|
|
124
|
+
# MGPANEL_API_URL=https://dev.mgpanel.co
|
|
125
|
+
`;
|
|
126
|
+
fs.writeFileSync(path.join(projectPath, ".env"), envContent, "utf8");
|
|
127
|
+
// Agregar .env al .gitignore si existe
|
|
128
|
+
const gitignorePath = path.join(projectPath, ".gitignore");
|
|
129
|
+
if (fs.existsSync(gitignorePath)) {
|
|
130
|
+
const gitignore = fs.readFileSync(gitignorePath, "utf8");
|
|
131
|
+
if (!gitignore.includes(".env")) {
|
|
132
|
+
fs.appendFileSync(gitignorePath, "\n# MGPanel CLI\n.env\n");
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
fs.writeFileSync(gitignorePath, "# MGPanel CLI\n.env\n", "utf8");
|
|
136
|
+
}
|
|
137
|
+
|
|
109
138
|
// dev/preview.js: carga una page leyendo page.json y componiendo módulos
|
|
110
139
|
const previewJS = `async function loadModule(name) {
|
|
111
140
|
const base = \`modules/\${name}/\${name}\`;
|
|
@@ -185,6 +214,7 @@ renderPage(pageFromPath()).catch(err => {
|
|
|
185
214
|
route: "/",
|
|
186
215
|
title: "Home",
|
|
187
216
|
description: "Página principal creada con MGPanel.",
|
|
217
|
+
directory: "",
|
|
188
218
|
modules: []
|
|
189
219
|
};
|
|
190
220
|
fs.writeFileSync(
|
|
@@ -248,10 +278,23 @@ Esto crea:
|
|
|
248
278
|
"route": "/about",
|
|
249
279
|
"title": "About",
|
|
250
280
|
"description": "Descripción de la página about.",
|
|
251
|
-
"
|
|
281
|
+
"directory": "",
|
|
282
|
+
"modules": [
|
|
283
|
+
{ "name": "header", "import": false }
|
|
284
|
+
]
|
|
252
285
|
}
|
|
253
286
|
\`\`\`
|
|
254
287
|
|
|
288
|
+
**Campo \`directory\`:**
|
|
289
|
+
- Por defecto viene vacío (\`""\`)
|
|
290
|
+
- Puede editarse manualmente para agregar valores como \`"app/"\` cuando se requiera
|
|
291
|
+
- Se usa en la nube para organizar las páginas en directorios
|
|
292
|
+
|
|
293
|
+
**Campo \`import\` en módulos:**
|
|
294
|
+
- \`import: false\` - Módulo propio de esta página (se crea la sección completa)
|
|
295
|
+
- \`import: true\` - Módulo importado de otra página (se referencia la sección existente)
|
|
296
|
+
- Se asigna automáticamente al agregar módulos con \`mgpanel add module\`
|
|
297
|
+
|
|
255
298
|
### Crear un Módulo
|
|
256
299
|
|
|
257
300
|
\`\`\`bash
|
|
@@ -290,15 +333,20 @@ Esto actualiza el archivo \`pages/<nombre-pagina>/page.json\` añadiendo el mód
|
|
|
290
333
|
"route": "/",
|
|
291
334
|
"title": "Home",
|
|
292
335
|
"description": "Página principal.",
|
|
336
|
+
"directory": "",
|
|
293
337
|
"modules": [
|
|
294
|
-
{ "name": "header" },
|
|
295
|
-
{ "name": "hero-banner" },
|
|
296
|
-
{ "name": "footer" }
|
|
338
|
+
{ "name": "header", "import": false },
|
|
339
|
+
{ "name": "hero-banner", "import": false },
|
|
340
|
+
{ "name": "footer", "import": true }
|
|
297
341
|
]
|
|
298
342
|
}
|
|
299
343
|
\`\`\`
|
|
300
344
|
|
|
301
|
-
**Importante:**
|
|
345
|
+
**Importante:**
|
|
346
|
+
- Los módulos se renderizan en el orden en que aparecen en el array \`modules\`.
|
|
347
|
+
- El campo \`import\` se agrega automáticamente:
|
|
348
|
+
- \`import: false\` si el módulo es propio de esta página (primera vez que se agrega)
|
|
349
|
+
- \`import: true\` si el módulo pertenece a otra página y se está reutilizando
|
|
302
350
|
|
|
303
351
|
### Servidor de Desarrollo
|
|
304
352
|
|
|
@@ -400,6 +448,96 @@ Cuando despliegues el proyecto en MGPanel:
|
|
|
400
448
|
3. El contenido puede ser editado desde el panel de administración
|
|
401
449
|
4. La estructura de archivos se mantiene compatible
|
|
402
450
|
|
|
451
|
+
### Importante: compatibilidad Local Preview vs MGPanel (Nube)
|
|
452
|
+
|
|
453
|
+
En *preview local* ( dev/preview.js ) cada módulo se inserta envuelto en un contenedor como:
|
|
454
|
+
|
|
455
|
+
\`\`\`html
|
|
456
|
+
<div data-mgpanel-module="header">...contenido del módulo...</div>
|
|
457
|
+
\`\`\`
|
|
458
|
+
|
|
459
|
+
En *MGPanel (nube)* el CMS puede renderizar el HTML del módulo *sin ese wrapper, y además el HTML puede inyectarse **después** de que el archivo JS del módulo ya fue cargado.
|
|
460
|
+
|
|
461
|
+
Por eso, en los módulos con JS, sigue estas reglas para que funcionen *tanto local como en nube*:
|
|
462
|
+
|
|
463
|
+
• *Seleccionar root con fallback*:
|
|
464
|
+
- Primero intenta \`document.querySelector('[data-mgpanel-module="<modulo>"]')\` (preview).
|
|
465
|
+
- Si no existe, busca el nodo real del módulo, por ejemplo \`.mg-<modulo>\` (nube).
|
|
466
|
+
• *Inicialización tardía (cloud-safe)*:
|
|
467
|
+
- No asumas que el HTML existe cuando corre el JS.
|
|
468
|
+
- Implementa retry + \`MutationObserver\` , y escucha \`DOMContentLoaded\` / \`load\` .
|
|
469
|
+
• *Evitar doble init*:
|
|
470
|
+
- Marca el nodo root con \`dataset\` (por ejemplo \`data-mg-init="1"\` ) para no registrar listeners dos veces.
|
|
471
|
+
• *Rutas de assets*:
|
|
472
|
+
- En SPAs y en nube, usa rutas absolutas: \`src="/assets/Logo.png"\` en vez de \`assets/Logo.png\` .
|
|
473
|
+
• *No ocultar contenido sin JS*:
|
|
474
|
+
- Si usas animaciones por clase (ej. \`data-mg-animate\` ), asegúrate que el contenido no quede invisible si el JS no corre.
|
|
475
|
+
|
|
476
|
+
### Campo \`import\` en módulos
|
|
477
|
+
|
|
478
|
+
Cada módulo en \`page.json\` puede tener un campo \`import\` que indica si el módulo es propio de la página o se está importando de otra:
|
|
479
|
+
|
|
480
|
+
- **\`import: false\`** - Módulo propio: La primera página donde se agrega un módulo tiene \`import: false\`. En la nube, esto crea una nueva sección completa.
|
|
481
|
+
- **\`import: true\`** - Módulo importado: Si un módulo ya existe en otra página y lo agregas a una nueva página, tendrá \`import: true\`. En la nube, esto referencia la sección existente en lugar de duplicarla.
|
|
482
|
+
|
|
483
|
+
**Ejemplo:**
|
|
484
|
+
\`\`\`json
|
|
485
|
+
{
|
|
486
|
+
"modules": [
|
|
487
|
+
{ "name": "header", "import": false }, // Propio de esta página
|
|
488
|
+
{ "name": "banner", "import": false }, // Propio de esta página
|
|
489
|
+
{ "name": "footer", "import": true } // Importado de otra página
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
\`\`\`
|
|
493
|
+
|
|
494
|
+
El campo \`import\` se asigna automáticamente cuando usas \`mgpanel add module\`. La primera página donde agregas un módulo será la "dueña" (\`import: false\`), y las páginas subsecuentes que usen ese módulo tendrán \`import: true\`.
|
|
495
|
+
|
|
496
|
+
#### Plantilla recomendada para JS de módulos (cloud-safe)
|
|
497
|
+
|
|
498
|
+
\`\`\`js
|
|
499
|
+
(() => {
|
|
500
|
+
const MODULE = "header"; // cambia por el nombre del módulo
|
|
501
|
+
|
|
502
|
+
const findRoot = () => {
|
|
503
|
+
const wrapped = document.querySelector(\`[data-mgpanel-module="\${MODULE}"]\`);
|
|
504
|
+
if (wrapped) return wrapped;
|
|
505
|
+
return document.querySelector(\`.mg-\${MODULE}\`);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const init = (rootOrEl) => {
|
|
509
|
+
if (!rootOrEl) return false;
|
|
510
|
+
const el = rootOrEl.classList?.contains(\`mg-\${MODULE}\`)
|
|
511
|
+
? rootOrEl
|
|
512
|
+
: rootOrEl.querySelector?.(\`.mg-\${MODULE}\`);
|
|
513
|
+
if (!el) return false;
|
|
514
|
+
|
|
515
|
+
if (el.dataset.mgInit === "1") return true;
|
|
516
|
+
el.dataset.mgInit = "1";
|
|
517
|
+
|
|
518
|
+
// ... tu lógica aquí ...
|
|
519
|
+
return true;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const tryInit = () => init(findRoot());
|
|
523
|
+
if (tryInit()) return;
|
|
524
|
+
|
|
525
|
+
let tries = 0;
|
|
526
|
+
const t = window.setInterval(() => {
|
|
527
|
+
tries += 1;
|
|
528
|
+
if (tryInit() || tries > 200) window.clearInterval(t); // ~10s
|
|
529
|
+
}, 50);
|
|
530
|
+
|
|
531
|
+
const mo = new MutationObserver(() => {
|
|
532
|
+
if (tryInit()) mo.disconnect();
|
|
533
|
+
});
|
|
534
|
+
mo.observe(document.documentElement, { childList: true, subtree: true });
|
|
535
|
+
|
|
536
|
+
window.addEventListener("DOMContentLoaded", tryInit, { once: true });
|
|
537
|
+
window.addEventListener("load", tryInit, { once: true });
|
|
538
|
+
})();
|
|
539
|
+
\`\`\`
|
|
540
|
+
|
|
403
541
|
## Consejos para IDEs
|
|
404
542
|
|
|
405
543
|
- Los archivos \`page.json\` definen la configuración de cada página
|
|
@@ -471,6 +609,17 @@ Este archivo contiene información detallada sobre:
|
|
|
471
609
|
└── index.html # Preview local
|
|
472
610
|
\`\`\`
|
|
473
611
|
|
|
612
|
+
## ✅ Nota importante (Local vs Nube)
|
|
613
|
+
|
|
614
|
+
En local, \`dev/preview.js\` envuelve módulos en un contenedor \`data-mgpanel-module="..."\`, pero en MGPanel (nube) ese wrapper puede **no existir** y los módulos pueden inyectarse **después** de que el JS ya cargó.
|
|
615
|
+
|
|
616
|
+
Por eso, al crear nuevos módulos con JavaScript:
|
|
617
|
+
- No asumas que el HTML existe en el momento de ejecución.
|
|
618
|
+
- Usa un patrón "cloud-safe" (retry + MutationObserver) y un selector con fallback (\`[data-mgpanel-module="x"]\` o \`.mg-x\`).
|
|
619
|
+
- Usa rutas absolutas para assets (\`/assets/...\`) para evitar problemas de routing.
|
|
620
|
+
|
|
621
|
+
Para más detalles, consulta la sección de compatibilidad en \`dev/config.md\`.
|
|
622
|
+
|
|
474
623
|
## 🔗 Más Información
|
|
475
624
|
|
|
476
625
|
- **Documentación de desarrollo:** Ver \`dev/config.md\`
|
|
@@ -509,6 +658,7 @@ function cmdMakePage(pageName) {
|
|
|
509
658
|
route: `/${pageName === "home" ? "" : pageName}`.replace(/\/$/, "/"),
|
|
510
659
|
title: pageName.charAt(0).toUpperCase() + pageName.slice(1),
|
|
511
660
|
description: `Descripción de la página ${pageName}.`,
|
|
661
|
+
directory: "",
|
|
512
662
|
modules: []
|
|
513
663
|
},
|
|
514
664
|
null,
|
|
@@ -546,7 +696,7 @@ function cmdMakeModule(moduleName) {
|
|
|
546
696
|
const jsPath = path.join(moduleDir, `${moduleName}.js`);
|
|
547
697
|
|
|
548
698
|
const htmlContent = `<!-- Module: ${moduleName} -->
|
|
549
|
-
<section class="${moduleName}">
|
|
699
|
+
<section class="${moduleName} mg-${moduleName}">
|
|
550
700
|
<h2>${moduleName}</h2>
|
|
551
701
|
<p>Módulo creado con MGPanel ✅</p>
|
|
552
702
|
</section>
|
|
@@ -559,7 +709,57 @@ function cmdMakeModule(moduleName) {
|
|
|
559
709
|
`;
|
|
560
710
|
|
|
561
711
|
const jsContent = `// Module: ${moduleName}
|
|
562
|
-
|
|
712
|
+
// Patrón cloud-safe: funciona tanto en preview local como en MGPanel (nube)
|
|
713
|
+
|
|
714
|
+
(() => {
|
|
715
|
+
const MODULE = "${moduleName}";
|
|
716
|
+
|
|
717
|
+
// Busca el root del módulo con fallback (local vs nube)
|
|
718
|
+
const findRoot = () => {
|
|
719
|
+
const wrapped = document.querySelector(\`[data-mgpanel-module="\${MODULE}"]\`);
|
|
720
|
+
if (wrapped) return wrapped;
|
|
721
|
+
return document.querySelector(\`.mg-\${MODULE}\`);
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// Inicializa el módulo
|
|
725
|
+
const init = (rootOrEl) => {
|
|
726
|
+
if (!rootOrEl) return false;
|
|
727
|
+
const el = rootOrEl.classList?.contains(\`mg-\${MODULE}\`)
|
|
728
|
+
? rootOrEl
|
|
729
|
+
: rootOrEl.querySelector?.(\`.mg-\${MODULE}\`);
|
|
730
|
+
if (!el) return false;
|
|
731
|
+
|
|
732
|
+
// Evitar doble inicialización
|
|
733
|
+
if (el.dataset.mgInit === "1") return true;
|
|
734
|
+
el.dataset.mgInit = "1";
|
|
735
|
+
|
|
736
|
+
// ... tu lógica aquí ...
|
|
737
|
+
console.log("[MGPanel] Module initialized:", MODULE);
|
|
738
|
+
|
|
739
|
+
return true;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// Intenta inicializar inmediatamente
|
|
743
|
+
const tryInit = () => init(findRoot());
|
|
744
|
+
if (tryInit()) return;
|
|
745
|
+
|
|
746
|
+
// Retry con intervalo (hasta ~10 segundos)
|
|
747
|
+
let tries = 0;
|
|
748
|
+
const intervalId = window.setInterval(() => {
|
|
749
|
+
tries += 1;
|
|
750
|
+
if (tryInit() || tries > 200) window.clearInterval(intervalId);
|
|
751
|
+
}, 50);
|
|
752
|
+
|
|
753
|
+
// Observar cambios en el DOM (para inyección tardía)
|
|
754
|
+
const mo = new MutationObserver(() => {
|
|
755
|
+
if (tryInit()) mo.disconnect();
|
|
756
|
+
});
|
|
757
|
+
mo.observe(document.documentElement, { childList: true, subtree: true });
|
|
758
|
+
|
|
759
|
+
// Escuchar eventos de carga
|
|
760
|
+
window.addEventListener("DOMContentLoaded", tryInit, { once: true });
|
|
761
|
+
window.addEventListener("load", tryInit, { once: true });
|
|
762
|
+
})();
|
|
563
763
|
`;
|
|
564
764
|
|
|
565
765
|
const created = [];
|
|
@@ -574,6 +774,35 @@ console.log("[MGPanel] Module loaded:", "${moduleName}");
|
|
|
574
774
|
if (skipped.length) console.log("↩️ Ya existían:", skipped.join(", "));
|
|
575
775
|
}
|
|
576
776
|
|
|
777
|
+
/**
|
|
778
|
+
* Verifica si un módulo existe en otras páginas (diferentes a la actual)
|
|
779
|
+
*/
|
|
780
|
+
function moduleExistsInOtherPages(moduleName, currentPageName) {
|
|
781
|
+
const pagesDir = path.join(process.cwd(), "pages");
|
|
782
|
+
if (!fs.existsSync(pagesDir)) return false;
|
|
783
|
+
|
|
784
|
+
const pageDirs = fs.readdirSync(pagesDir, { withFileTypes: true })
|
|
785
|
+
.filter(dirent => dirent.isDirectory() && dirent.name !== currentPageName)
|
|
786
|
+
.map(dirent => dirent.name);
|
|
787
|
+
|
|
788
|
+
for (const pageName of pageDirs) {
|
|
789
|
+
const pageJsonPath = path.join(pagesDir, pageName, "page.json");
|
|
790
|
+
if (fs.existsSync(pageJsonPath)) {
|
|
791
|
+
try {
|
|
792
|
+
const page = readJSON(pageJsonPath);
|
|
793
|
+
if (Array.isArray(page.modules)) {
|
|
794
|
+
const exists = page.modules.some(m => m && m.name === moduleName);
|
|
795
|
+
if (exists) return true;
|
|
796
|
+
}
|
|
797
|
+
} catch (e) {
|
|
798
|
+
// Si hay error leyendo el archivo, continuar con la siguiente página
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
|
|
577
806
|
/**
|
|
578
807
|
* mgpanel add module <moduleName> --to <pageName>
|
|
579
808
|
*/
|
|
@@ -607,10 +836,402 @@ function cmdAddModule(moduleName, pageName) {
|
|
|
607
836
|
return;
|
|
608
837
|
}
|
|
609
838
|
|
|
610
|
-
|
|
839
|
+
// Determinar si el módulo es importado (existe en otras páginas)
|
|
840
|
+
const isImported = moduleExistsInOtherPages(moduleName, pageName);
|
|
841
|
+
page.modules.push({
|
|
842
|
+
name: moduleName,
|
|
843
|
+
import: isImported
|
|
844
|
+
});
|
|
611
845
|
writeJSON(pageJsonPath, page);
|
|
612
846
|
|
|
613
|
-
|
|
847
|
+
const importStatus = isImported ? "importado" : "propio";
|
|
848
|
+
console.log(`✅ Módulo "${moduleName}" agregado a pages/${pageName}/page.json (${importStatus})`);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* mgpanel publish --account <cuenta> [--token <token>] [--api-url <url>]
|
|
853
|
+
*/
|
|
854
|
+
async function cmdPublish(args) {
|
|
855
|
+
// Parsear argumentos
|
|
856
|
+
let account = null;
|
|
857
|
+
let token = null;
|
|
858
|
+
let apiUrl = "https://dev.mgpanel.co"; // default
|
|
859
|
+
|
|
860
|
+
for (let i = 0; i < args.length; i++) {
|
|
861
|
+
if (args[i] === "--account" && args[i + 1]) {
|
|
862
|
+
account = args[i + 1];
|
|
863
|
+
i++;
|
|
864
|
+
} else if (args[i] === "--token" && args[i + 1]) {
|
|
865
|
+
token = args[i + 1];
|
|
866
|
+
i++;
|
|
867
|
+
} else if (args[i] === "--api-url" && args[i + 1]) {
|
|
868
|
+
apiUrl = args[i + 1];
|
|
869
|
+
i++;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Cargar variables de entorno desde .env si existe
|
|
874
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
875
|
+
if (fs.existsSync(envPath)) {
|
|
876
|
+
const envContent = fs.readFileSync(envPath, "utf8");
|
|
877
|
+
const envLines = envContent.split("\n");
|
|
878
|
+
for (const line of envLines) {
|
|
879
|
+
const trimmed = line.trim();
|
|
880
|
+
// Ignorar comentarios y líneas vacías
|
|
881
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
882
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
883
|
+
if (match) {
|
|
884
|
+
const key = match[1].trim();
|
|
885
|
+
const value = match[2].trim().replace(/^["']|["']$/g, ""); // Remover comillas
|
|
886
|
+
if (key === "MGPANEL_TOKEN") {
|
|
887
|
+
process.env.MGPANEL_TOKEN = value;
|
|
888
|
+
} else if (key === "MGPANEL_API_URL") {
|
|
889
|
+
process.env.MGPANEL_API_URL = value;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Token puede venir de variable de entorno
|
|
897
|
+
if (!token) {
|
|
898
|
+
token = process.env.MGPANEL_TOKEN;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// API URL puede venir de variable de entorno (tiene prioridad sobre default)
|
|
902
|
+
if (process.env.MGPANEL_API_URL) {
|
|
903
|
+
apiUrl = process.env.MGPANEL_API_URL;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Validaciones
|
|
907
|
+
if (!account) {
|
|
908
|
+
console.log("❌ Falta el parámetro --account");
|
|
909
|
+
console.log("👉 Usa: mgpanel publish --account <nombre-cuenta> [--token <token>] [--api-url <url>]");
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (!token) {
|
|
914
|
+
console.log("❌ Falta el token de autenticación");
|
|
915
|
+
console.log("👉 Usa: mgpanel publish --account <cuenta> --token <token>");
|
|
916
|
+
console.log(" O configura la variable de entorno: MGPANEL_TOKEN");
|
|
917
|
+
console.log(" O agrega MGPANEL_TOKEN=tu-token en el archivo .env");
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Mostrar información del token (solo primeros y últimos caracteres por seguridad)
|
|
922
|
+
const tokenPreview = token.length > 10
|
|
923
|
+
? `${token.substring(0, 6)}...${token.substring(token.length - 4)}`
|
|
924
|
+
: "***";
|
|
925
|
+
console.log(`🔑 Token: ${tokenPreview}`);
|
|
926
|
+
|
|
927
|
+
const projectRoot = process.cwd();
|
|
928
|
+
const pagesDir = path.join(projectRoot, "pages");
|
|
929
|
+
const modulesDir = path.join(projectRoot, "modules");
|
|
930
|
+
const assetsDir = path.join(projectRoot, "assets");
|
|
931
|
+
|
|
932
|
+
// Validar que estamos en un proyecto MGPanel
|
|
933
|
+
if (!fs.existsSync(pagesDir) || !fs.existsSync(modulesDir)) {
|
|
934
|
+
console.log("❌ No se encontró un proyecto MGPanel válido");
|
|
935
|
+
console.log("👉 Asegúrate de estar en un directorio con carpetas 'pages' y 'modules'");
|
|
936
|
+
console.log(" O ejecuta: mgpanel init <nombre-proyecto>");
|
|
937
|
+
process.exit(1);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
console.log("📦 Leyendo estructura del proyecto...");
|
|
941
|
+
|
|
942
|
+
try {
|
|
943
|
+
// Leer assets globales
|
|
944
|
+
const globalCSS = fs.existsSync(path.join(assetsDir, "css", "style.css"))
|
|
945
|
+
? fs.readFileSync(path.join(assetsDir, "css", "style.css"), "utf8")
|
|
946
|
+
: "";
|
|
947
|
+
const globalJS = fs.existsSync(path.join(assetsDir, "js", "app.js"))
|
|
948
|
+
? fs.readFileSync(path.join(assetsDir, "js", "app.js"), "utf8")
|
|
949
|
+
: "";
|
|
950
|
+
|
|
951
|
+
// Leer todas las páginas
|
|
952
|
+
const pages = [];
|
|
953
|
+
const pageDirs = fs.readdirSync(pagesDir, { withFileTypes: true })
|
|
954
|
+
.filter(dirent => dirent.isDirectory())
|
|
955
|
+
.map(dirent => dirent.name);
|
|
956
|
+
|
|
957
|
+
console.log(`📂 Páginas encontradas en directorio: ${pageDirs.length} (${pageDirs.join(", ")})`);
|
|
958
|
+
|
|
959
|
+
for (const pageName of pageDirs) {
|
|
960
|
+
console.log(`📄 Procesando página: ${pageName}`);
|
|
961
|
+
// Compatibilidad: algunas personas lo renombraron a "pago.json" por error.
|
|
962
|
+
// Preferimos "page.json", pero aceptamos "pago.json" como fallback.
|
|
963
|
+
const pageJsonPath = path.join(pagesDir, pageName, "page.json");
|
|
964
|
+
const pagoJsonPath = path.join(pagesDir, pageName, "pago.json");
|
|
965
|
+
let pageConfigPath = null;
|
|
966
|
+
|
|
967
|
+
if (fs.existsSync(pageJsonPath)) {
|
|
968
|
+
pageConfigPath = pageJsonPath;
|
|
969
|
+
} else if (fs.existsSync(pagoJsonPath)) {
|
|
970
|
+
pageConfigPath = pagoJsonPath;
|
|
971
|
+
console.log(`⚠️ Aviso: usando "${pageName}/pago.json". Recomendado: renómbralo a "page.json".`);
|
|
972
|
+
} else {
|
|
973
|
+
console.log(`⚠️ Saltando página "${pageName}": no se encontró page.json (ni pago.json)`);
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
let pageConfig;
|
|
978
|
+
try {
|
|
979
|
+
pageConfig = readJSON(pageConfigPath);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.log(`❌ Error al leer ${pageConfigPath}: ${error.message}`);
|
|
982
|
+
console.log(`⚠️ Saltando página "${pageName}"`);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const sections = [];
|
|
987
|
+
|
|
988
|
+
// Procesar módulos de la página
|
|
989
|
+
if (Array.isArray(pageConfig.modules)) {
|
|
990
|
+
for (let i = 0; i < pageConfig.modules.length; i++) {
|
|
991
|
+
const moduleName = pageConfig.modules[i].name;
|
|
992
|
+
if (!moduleName) continue;
|
|
993
|
+
|
|
994
|
+
const moduleDir = path.join(modulesDir, moduleName);
|
|
995
|
+
if (!fs.existsSync(moduleDir)) {
|
|
996
|
+
console.log(`⚠️ Módulo "${moduleName}" no encontrado, saltando...`);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const moduleHTML = fs.existsSync(path.join(moduleDir, `${moduleName}.html`))
|
|
1001
|
+
? fs.readFileSync(path.join(moduleDir, `${moduleName}.html`), "utf8")
|
|
1002
|
+
: "";
|
|
1003
|
+
const moduleCSS = fs.existsSync(path.join(moduleDir, `${moduleName}.css`))
|
|
1004
|
+
? fs.readFileSync(path.join(moduleDir, `${moduleName}.css`), "utf8")
|
|
1005
|
+
: "";
|
|
1006
|
+
const moduleJS = fs.existsSync(path.join(moduleDir, `${moduleName}.js`))
|
|
1007
|
+
? fs.readFileSync(path.join(moduleDir, `${moduleName}.js`), "utf8")
|
|
1008
|
+
: "";
|
|
1009
|
+
|
|
1010
|
+
// Leer el campo import del page.json (por defecto false si no existe)
|
|
1011
|
+
const moduleConfig = pageConfig.modules[i];
|
|
1012
|
+
const isImported = moduleConfig.import === true || moduleConfig.import === "true";
|
|
1013
|
+
|
|
1014
|
+
sections.push({
|
|
1015
|
+
code: moduleName,
|
|
1016
|
+
name: moduleName,
|
|
1017
|
+
html: {
|
|
1018
|
+
type: "text/html",
|
|
1019
|
+
code: moduleHTML
|
|
1020
|
+
},
|
|
1021
|
+
css: {
|
|
1022
|
+
type: "text/css",
|
|
1023
|
+
code: moduleCSS
|
|
1024
|
+
},
|
|
1025
|
+
javascript: {
|
|
1026
|
+
type: "application/javascript",
|
|
1027
|
+
code: moduleJS
|
|
1028
|
+
},
|
|
1029
|
+
module: [], // Array de referencias a módulos (el backend lo manejará)
|
|
1030
|
+
order: i + 1,
|
|
1031
|
+
status: 1,
|
|
1032
|
+
import: isImported
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Validar que la página tenga al menos un campo requerido
|
|
1038
|
+
if (!pageConfig.title && !pageName) {
|
|
1039
|
+
console.log(`⚠️ Saltando página "${pageName}": falta información básica`);
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
pages.push({
|
|
1044
|
+
// page: ObjectId - El backend debe generar o usar el existente
|
|
1045
|
+
name: pageName,
|
|
1046
|
+
title: pageConfig.title || pageName,
|
|
1047
|
+
description: pageConfig.description || "",
|
|
1048
|
+
// IMPORTANTE: en MGPanel el `directory` por defecto debe ser "".
|
|
1049
|
+
// No usar `|| pageName` porque "" es válido y si cae al fallback rompe el render (termina guardando "home", "about", etc.).
|
|
1050
|
+
directory: (typeof pageConfig.directory === "string") ? pageConfig.directory : "",
|
|
1051
|
+
url: pageConfig.route || `/${pageName === "home" ? "" : pageName}`,
|
|
1052
|
+
sections: sections,
|
|
1053
|
+
status: 1,
|
|
1054
|
+
soon: false,
|
|
1055
|
+
index: pageName === "home"
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
console.log(`✅ Página "${pageName}" procesada (${sections.length} secciones)`);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
console.log(`📊 Total de páginas procesadas: ${pages.length}`);
|
|
1062
|
+
|
|
1063
|
+
// Seguridad: nunca publicar si no se encontró ninguna página.
|
|
1064
|
+
if (pages.length === 0) {
|
|
1065
|
+
console.log("❌ No se encontró ninguna página para publicar.");
|
|
1066
|
+
console.log("👉 Verifica que existan carpetas en ./pages/<pagina>/ y que el archivo sea page.json (o pago.json).");
|
|
1067
|
+
console.log(`💡 Directorios encontrados: ${pageDirs.length > 0 ? pageDirs.join(", ") : "ninguno"}`);
|
|
1068
|
+
process.exit(1);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Construir payload según modelo MongoDB
|
|
1072
|
+
const payload = {
|
|
1073
|
+
type: "published",
|
|
1074
|
+
css: {
|
|
1075
|
+
type: "text/css",
|
|
1076
|
+
code: globalCSS
|
|
1077
|
+
},
|
|
1078
|
+
javascript: {
|
|
1079
|
+
type: "application/javascript",
|
|
1080
|
+
code: globalJS
|
|
1081
|
+
},
|
|
1082
|
+
pages: pages,
|
|
1083
|
+
modified_date: new Date(),
|
|
1084
|
+
has_changes: true,
|
|
1085
|
+
version_number: 1
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
console.log(`📤 Publicando a cuenta: ${account}`);
|
|
1089
|
+
console.log(`📄 Páginas encontradas: ${pages.length}`);
|
|
1090
|
+
console.log(`🧩 Módulos totales: ${pages.reduce((sum, p) => sum + p.sections.length, 0)}`);
|
|
1091
|
+
console.log(`🌐 URL de la API: ${apiUrl}`);
|
|
1092
|
+
|
|
1093
|
+
// Enviar POST request
|
|
1094
|
+
const url = new URL(`${apiUrl}/api/cli/publish`);
|
|
1095
|
+
const postData = JSON.stringify(payload);
|
|
1096
|
+
const urlOptions = {
|
|
1097
|
+
hostname: url.hostname,
|
|
1098
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
1099
|
+
path: url.pathname + url.search,
|
|
1100
|
+
method: "POST",
|
|
1101
|
+
headers: {
|
|
1102
|
+
"Content-Type": "application/json",
|
|
1103
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
1104
|
+
"Authorization": `Bearer ${token}`,
|
|
1105
|
+
"X-Account-Nick": account
|
|
1106
|
+
},
|
|
1107
|
+
timeout: 60000, // 60 segundos de timeout
|
|
1108
|
+
rejectUnauthorized: true, // Aceptar certificados SSL válidos
|
|
1109
|
+
agent: false // No usar agent pool, crear nueva conexión
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
const client = url.protocol === "https:" ? https : http;
|
|
1113
|
+
|
|
1114
|
+
console.log(`🔄 Enviando petición a ${url.hostname}${url.pathname}...`);
|
|
1115
|
+
console.log(`📤 Tamaño del payload: ${Buffer.byteLength(postData)} bytes`);
|
|
1116
|
+
|
|
1117
|
+
// Usar promesa para asegurar que esperamos la respuesta
|
|
1118
|
+
await new Promise((resolve, reject) => {
|
|
1119
|
+
console.log(`🔧 Creando request HTTP...`);
|
|
1120
|
+
const req = client.request(urlOptions, (res) => {
|
|
1121
|
+
console.log(`✅ Callback del request ejecutado`);
|
|
1122
|
+
let responseData = "";
|
|
1123
|
+
|
|
1124
|
+
console.log(`📡 Respuesta recibida: ${res.statusCode} ${res.statusMessage || ""}`);
|
|
1125
|
+
|
|
1126
|
+
// Manejar errores de respuesta
|
|
1127
|
+
res.on("error", (err) => {
|
|
1128
|
+
console.log(`❌ Error al leer la respuesta: ${err.message}`);
|
|
1129
|
+
reject(err);
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
res.on("data", (chunk) => {
|
|
1133
|
+
responseData += chunk;
|
|
1134
|
+
console.log(`📥 Recibiendo datos... (${responseData.length} bytes acumulados)`);
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
res.on("end", () => {
|
|
1138
|
+
console.log(`✅ Evento 'end' disparado`);
|
|
1139
|
+
console.log(`📦 Tamaño de respuesta: ${responseData.length} bytes`);
|
|
1140
|
+
|
|
1141
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
1142
|
+
console.log("✅ Publicación exitosa!");
|
|
1143
|
+
try {
|
|
1144
|
+
const response = JSON.parse(responseData);
|
|
1145
|
+
if (response.message) {
|
|
1146
|
+
console.log(`📝 ${response.message}`);
|
|
1147
|
+
}
|
|
1148
|
+
if (response.data) {
|
|
1149
|
+
console.log(`📊 Versión: ${response.data.version_number || "N/A"}`);
|
|
1150
|
+
}
|
|
1151
|
+
if (response.success === false) {
|
|
1152
|
+
console.log(`⚠️ El servidor reportó: success=false`);
|
|
1153
|
+
console.log(`💬 ${JSON.stringify(response, null, 2)}`);
|
|
1154
|
+
}
|
|
1155
|
+
} catch (e) {
|
|
1156
|
+
// No es JSON, mostrar respuesta tal cual
|
|
1157
|
+
if (responseData) {
|
|
1158
|
+
console.log(`📝 Respuesta (texto): ${responseData.substring(0, 500)}`);
|
|
1159
|
+
} else {
|
|
1160
|
+
console.log(`📝 Respuesta vacía`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
resolve();
|
|
1164
|
+
} else {
|
|
1165
|
+
console.log(`❌ Error en la publicación (${res.statusCode})`);
|
|
1166
|
+
try {
|
|
1167
|
+
const error = JSON.parse(responseData);
|
|
1168
|
+
console.log(`💬 ${error.message || error.error || "Error desconocido"}`);
|
|
1169
|
+
if (error.details) {
|
|
1170
|
+
console.log(`🔍 Detalles:`, JSON.stringify(error.details, null, 2));
|
|
1171
|
+
}
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
if (responseData) {
|
|
1174
|
+
console.log(`💬 Respuesta del servidor: ${responseData.substring(0, 500)}`);
|
|
1175
|
+
} else {
|
|
1176
|
+
console.log(`💬 Sin respuesta del servidor`);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
req.on("error", (error) => {
|
|
1185
|
+
console.log(`❌ Error de conexión: ${error.message}`);
|
|
1186
|
+
console.log(`💡 Código de error: ${error.code || "N/A"}`);
|
|
1187
|
+
console.log(`💡 Stack: ${error.stack || "N/A"}`);
|
|
1188
|
+
console.log(`💡 Verifica que la URL de la API sea correcta: ${apiUrl}`);
|
|
1189
|
+
if (error.code === "ENOTFOUND") {
|
|
1190
|
+
console.log(`💡 No se pudo resolver el hostname: ${url.hostname}`);
|
|
1191
|
+
} else if (error.code === "ECONNREFUSED") {
|
|
1192
|
+
console.log(`💡 Conexión rechazada. ¿El servidor está corriendo?`);
|
|
1193
|
+
} else if (error.code === "ETIMEDOUT") {
|
|
1194
|
+
console.log(`💡 Timeout de conexión. El servidor no respondió a tiempo.`);
|
|
1195
|
+
} else if (error.code === "CERT_HAS_EXPIRED" || error.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") {
|
|
1196
|
+
console.log(`💡 Problema con el certificado SSL. Intenta con rejectUnauthorized: false (solo para desarrollo)`);
|
|
1197
|
+
}
|
|
1198
|
+
reject(error);
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
req.setTimeout(60000, () => {
|
|
1202
|
+
console.log(`❌ Timeout: La petición tardó más de 60 segundos sin respuesta`);
|
|
1203
|
+
console.log(`💡 El servidor puede estar sobrecargado o hay un problema de red`);
|
|
1204
|
+
req.destroy();
|
|
1205
|
+
reject(new Error("Timeout"));
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
console.log(`📤 Escribiendo datos en el request...`);
|
|
1209
|
+
req.write(postData);
|
|
1210
|
+
console.log(`✅ Datos escritos, cerrando request...`);
|
|
1211
|
+
req.end();
|
|
1212
|
+
|
|
1213
|
+
console.log(`📨 Petición enviada, esperando respuesta...`);
|
|
1214
|
+
|
|
1215
|
+
// Agregar listener para verificar si el request se está enviando
|
|
1216
|
+
req.on("finish", () => {
|
|
1217
|
+
console.log(`✅ Request finalizado (datos enviados)`);
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
req.on("close", () => {
|
|
1221
|
+
console.log(`🔒 Conexión cerrada`);
|
|
1222
|
+
});
|
|
1223
|
+
}).catch((error) => {
|
|
1224
|
+
console.log(`❌ Error en la promesa: ${error.message}`);
|
|
1225
|
+
throw error;
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
console.log(`❌ Error al procesar el proyecto: ${error.message}`);
|
|
1230
|
+
if (error.stack) {
|
|
1231
|
+
console.log(error.stack);
|
|
1232
|
+
}
|
|
1233
|
+
process.exit(1);
|
|
1234
|
+
}
|
|
614
1235
|
}
|
|
615
1236
|
|
|
616
1237
|
function cmdDev(port = 3000) {
|
|
@@ -720,6 +1341,23 @@ if (cmd1 === "dev") {
|
|
|
720
1341
|
return;
|
|
721
1342
|
}
|
|
722
1343
|
|
|
1344
|
+
if (cmd1 === "publish") {
|
|
1345
|
+
(async () => {
|
|
1346
|
+
try {
|
|
1347
|
+
await cmdPublish(args);
|
|
1348
|
+
process.exit(0);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
console.log(`❌ Error: ${error.message}`);
|
|
1351
|
+
if (error.stack) {
|
|
1352
|
+
console.log(error.stack);
|
|
1353
|
+
}
|
|
1354
|
+
process.exit(1);
|
|
1355
|
+
}
|
|
1356
|
+
})();
|
|
1357
|
+
// NO hacer process.exit aquí, dejar que la función async maneje la salida
|
|
1358
|
+
return; // Evitar que el código continúe
|
|
1359
|
+
}
|
|
1360
|
+
|
|
723
1361
|
console.log("❌ Comando no reconocido.");
|
|
724
1362
|
printHelp();
|
|
725
1363
|
process.exit(1);
|