slicejs-web-framework 3.4.4 → 3.5.1

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.
Files changed (110) hide show
  1. package/Slice/Components/Structural/Controller/Controller.js +135 -0
  2. package/Slice/Components/Structural/EventManager/EventManager.js +289 -4
  3. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +368 -30
  4. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -0
  5. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +56 -0
  6. package/Slice/Slice.js +14 -1
  7. package/Slice/tests/event-emitters.test.js +79 -0
  8. package/Slice/tests/event-registry.test.js +141 -0
  9. package/Slice/tests/leak-orphans.test.js +131 -0
  10. package/api/{middleware → framework}/securityMiddleware.js +18 -74
  11. package/api/framework/server.js +333 -0
  12. package/api/index.js +5 -279
  13. package/api/tests/public-env-resolver.test.js +1 -1
  14. package/package.json +4 -2
  15. package/sliceConfig.schema.json +10 -1
  16. package/src/App/index.html +31 -1
  17. package/src/manifest.json +15 -0
  18. package/src/robots.txt +4 -0
  19. package/src/sitemap.xml +8 -0
  20. package/src/sliceConfig.json +5 -1
  21. package/types/index.d.ts +12 -0
  22. package/slicejs-cli/.github/workflows/ci.yml +0 -43
  23. package/slicejs-cli/LICENSE +0 -21
  24. package/slicejs-cli/assets/Slice.js-logo.png +0 -0
  25. package/slicejs-cli/client.js +0 -740
  26. package/slicejs-cli/commands/Print.js +0 -164
  27. package/slicejs-cli/commands/Validations.js +0 -92
  28. package/slicejs-cli/commands/build/build.js +0 -40
  29. package/slicejs-cli/commands/buildProduction/buildProduction.js +0 -577
  30. package/slicejs-cli/commands/bundle/bundle.js +0 -236
  31. package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +0 -55
  32. package/slicejs-cli/commands/createComponent/createComponent.js +0 -128
  33. package/slicejs-cli/commands/deleteComponent/deleteComponent.js +0 -81
  34. package/slicejs-cli/commands/doctor/doctor.js +0 -516
  35. package/slicejs-cli/commands/getComponent/getComponent.js +0 -695
  36. package/slicejs-cli/commands/init/init.js +0 -473
  37. package/slicejs-cli/commands/listComponents/listComponents.js +0 -172
  38. package/slicejs-cli/commands/startServer/startServer.js +0 -240
  39. package/slicejs-cli/commands/startServer/watchServer.js +0 -66
  40. package/slicejs-cli/commands/types/types.js +0 -583
  41. package/slicejs-cli/commands/utils/LocalCliDelegation.js +0 -53
  42. package/slicejs-cli/commands/utils/PackageManager.js +0 -148
  43. package/slicejs-cli/commands/utils/PathHelper.js +0 -75
  44. package/slicejs-cli/commands/utils/VersionChecker.js +0 -169
  45. package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +0 -2525
  46. package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +0 -933
  47. package/slicejs-cli/commands/utils/loadConfig.js +0 -31
  48. package/slicejs-cli/commands/utils/sliceScripts.js +0 -48
  49. package/slicejs-cli/commands/utils/updateManager.js +0 -471
  50. package/slicejs-cli/package.json +0 -71
  51. package/slicejs-cli/playwright.config.js +0 -51
  52. package/slicejs-cli/pnpm-lock.yaml +0 -1629
  53. package/slicejs-cli/tests/build-command-integration.test.js +0 -87
  54. package/slicejs-cli/tests/build-mangling-e2e.test.js +0 -290
  55. package/slicejs-cli/tests/build-production-e2e.test.js +0 -140
  56. package/slicejs-cli/tests/builder-edge-cases.test.js +0 -322
  57. package/slicejs-cli/tests/bundle-generate-e2e.test.js +0 -115
  58. package/slicejs-cli/tests/bundle-generator.test.js +0 -691
  59. package/slicejs-cli/tests/bundle-v2-register-output.test.js +0 -470
  60. package/slicejs-cli/tests/bundling-dependency-edges.test.js +0 -127
  61. package/slicejs-cli/tests/bundling-imports-unit.test.js +0 -267
  62. package/slicejs-cli/tests/client-launcher-contract.test.js +0 -211
  63. package/slicejs-cli/tests/commands-component-crud.test.js +0 -128
  64. package/slicejs-cli/tests/commands-doctor.test.js +0 -152
  65. package/slicejs-cli/tests/commands-version-checker.test.js +0 -37
  66. package/slicejs-cli/tests/component-registry-parse.test.js +0 -34
  67. package/slicejs-cli/tests/dependency-analyzer.test.js +0 -24
  68. package/slicejs-cli/tests/e2e/bundles.spec.js +0 -91
  69. package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +0 -56
  70. package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  71. package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  72. package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  73. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  74. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  75. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  76. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  77. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  78. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  79. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  80. package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  81. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  82. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  83. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  84. package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  85. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  86. package/slicejs-cli/tests/e2e/fixtures/components/registry.json +0 -12
  87. package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +0 -65
  88. package/slicejs-cli/tests/e2e/navigation.spec.js +0 -44
  89. package/slicejs-cli/tests/e2e/render.spec.js +0 -34
  90. package/slicejs-cli/tests/e2e/serve.mjs +0 -264
  91. package/slicejs-cli/tests/e2e/shared-deps.spec.js +0 -61
  92. package/slicejs-cli/tests/e2e/unminified.spec.js +0 -33
  93. package/slicejs-cli/tests/e2e-serve.test.js +0 -148
  94. package/slicejs-cli/tests/fixtures/components.js +0 -8
  95. package/slicejs-cli/tests/fixtures/sliceConfig.json +0 -74
  96. package/slicejs-cli/tests/getcomponent.test.js +0 -435
  97. package/slicejs-cli/tests/helpers/setup.js +0 -102
  98. package/slicejs-cli/tests/init-command-contract.test.js +0 -101
  99. package/slicejs-cli/tests/init-e2e.test.js +0 -82
  100. package/slicejs-cli/tests/init-project-isolation.test.js +0 -98
  101. package/slicejs-cli/tests/local-cli-delegation.test.js +0 -81
  102. package/slicejs-cli/tests/package-manager.test.js +0 -127
  103. package/slicejs-cli/tests/path-helper.test.js +0 -206
  104. package/slicejs-cli/tests/perf-budget.test.js +0 -84
  105. package/slicejs-cli/tests/postinstall-command.test.js +0 -103
  106. package/slicejs-cli/tests/property-mangling-regression.test.js +0 -182
  107. package/slicejs-cli/tests/types-breakage.test.js +0 -491
  108. package/slicejs-cli/tests/types-generator-errors.test.js +0 -361
  109. package/slicejs-cli/tests/types-generator.test.js +0 -348
  110. /package/api/{utils → framework}/publicEnvResolver.js +0 -0
@@ -0,0 +1,131 @@
1
+ // Real-Controller tests for the dev-mode leak detector (findOrphans + criteria).
2
+ // Loads the REAL Controller via the resolve hook (same pattern as
3
+ // destroy-cascade.test.js) and drives it with a minimal fake DOM. Fake nodes are
4
+ // made `instanceof HTMLElement` because findOrphans filters on that.
5
+ import { register } from 'node:module';
6
+ import test from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+
9
+ globalThis.alert = () => {};
10
+ register(new URL('./fixtures/real-runtime-loader.mjs', import.meta.url));
11
+
12
+ globalThis.HTMLElement = class HTMLElement {};
13
+ globalThis.slice = {
14
+ logger: { logError() {}, logWarning() {}, logInfo() {} },
15
+ events: null,
16
+ };
17
+
18
+ const { default: Controller } = await import('../Components/Structural/Controller/Controller.js');
19
+
20
+ function mkEl(sliceId, children = []) {
21
+ return Object.assign(Object.create(HTMLElement.prototype), {
22
+ sliceId,
23
+ tagName: 'SLICE-X',
24
+ isConnected: true,
25
+ parentComponent: null,
26
+ _children: children,
27
+ querySelectorAll() {
28
+ const out = [];
29
+ const walk = (n) => { for (const c of n._children) { out.push(c); walk(c); } };
30
+ walk(this);
31
+ return out;
32
+ },
33
+ setAttribute() {},
34
+ remove() { this.isConnected = false; },
35
+ });
36
+ }
37
+
38
+ function flatten(root) {
39
+ const out = [root];
40
+ for (const c of root._children) out.push(...flatten(c));
41
+ return out;
42
+ }
43
+
44
+ function buildTree(controller, root) {
45
+ for (const node of flatten(root)) controller.registerComponent(node);
46
+ controller.registerComponentsRecursively(root);
47
+ }
48
+
49
+ test('a built-then-detached component (no destroy) IS flagged', () => {
50
+ const c = new Controller();
51
+ const comp = mkEl('leaky');
52
+ buildTree(c, comp);
53
+ comp.isConnected = false; // simulate innerHTML='' / remove() without destroyComponent
54
+
55
+ const orphans = c.findOrphans();
56
+ assert.equal(orphans.length, 1, 'one orphan');
57
+ assert.equal(orphans[0].sliceId, 'leaky');
58
+ assert.ok(orphans[0].retainPath.includes('Object(leaky)') || orphans[0].retainPath.length >= 1);
59
+ });
60
+
61
+ test('a connected component is NOT flagged', () => {
62
+ const c = new Controller();
63
+ const comp = mkEl('alive');
64
+ buildTree(c, comp);
65
+ assert.equal(c.findOrphans().length, 0);
66
+ });
67
+
68
+ test('__sliceCached components are excluded (Route/MultiRoute cache)', () => {
69
+ const c = new Controller();
70
+ const comp = mkEl('cachedView');
71
+ buildTree(c, comp);
72
+ comp.isConnected = false;
73
+ comp.__sliceCached = true;
74
+ assert.equal(c.findOrphans().length, 0);
75
+ });
76
+
77
+ test('router-managed route-* sliceIds are excluded', () => {
78
+ const c = new Controller();
79
+ const comp = mkEl('route-Home');
80
+ buildTree(c, comp);
81
+ comp.isConnected = false;
82
+ assert.equal(c.findOrphans().length, 0);
83
+ });
84
+
85
+ test('ancestor de-dup: only the detached root is reported', () => {
86
+ const c = new Controller();
87
+ const child = mkEl('child');
88
+ const parent = mkEl('parent', [child]);
89
+ buildTree(c, parent);
90
+ parent.isConnected = false;
91
+ child.isConnected = false;
92
+
93
+ const orphans = c.findOrphans();
94
+ assert.equal(orphans.length, 1, 'only the root, not the descendant');
95
+ assert.equal(orphans[0].sliceId, 'parent');
96
+ });
97
+
98
+ test('descendants of a cached subtree are NOT flagged', () => {
99
+ const c = new Controller();
100
+ const child = mkEl('child');
101
+ const parent = mkEl('parent', [child]);
102
+ buildTree(c, parent);
103
+ parent.__sliceCached = true; // cached route root
104
+ parent.isConnected = false;
105
+ child.isConnected = false;
106
+ assert.equal(c.findOrphans().length, 0);
107
+ });
108
+
109
+ test('app-registered exclusion predicate is honored', () => {
110
+ const c = new Controller();
111
+ const comp = mkEl('special');
112
+ buildTree(c, comp);
113
+ comp.isConnected = false;
114
+ c.registerLeakExclusion((node) => node.sliceId === 'special');
115
+ assert.equal(c.findOrphans().length, 0);
116
+ });
117
+
118
+ test('growth heuristic trips on monotonic increase and resets on shrink', () => {
119
+ const c = new Controller();
120
+ assert.equal(c.isGrowthMonotonic(), false, 'needs >= 5 samples');
121
+
122
+ for (let i = 0; i < 5; i++) {
123
+ c.registerComponent(mkEl(`g${i}`));
124
+ c.sampleComponentGrowth();
125
+ }
126
+ assert.equal(c.isGrowthMonotonic(), true, 'sizes 1..5 are monotonic growth');
127
+
128
+ c.destroyComponent('g4');
129
+ c.sampleComponentGrowth(); // size dips
130
+ assert.equal(c.isGrowthMonotonic(), false, 'a shrink breaks the monotonic signal');
131
+ });
@@ -1,4 +1,4 @@
1
- // api/middleware/securityMiddleware.js
1
+ // api/framework/securityMiddleware.js
2
2
  import path from 'path';
3
3
 
4
4
  /**
@@ -7,7 +7,17 @@ import path from 'path';
7
7
  */
8
8
  export function securityMiddleware(options = {}) {
9
9
  const {
10
- allowedExtensions = ['.js', '.css', '.html', '.json', '.svg', '.png', '.jpg', '.jpeg', '.gif', '.woff', '.woff2', '.ttf'],
10
+ allowedExtensions = [
11
+ '.js', '.mjs', '.cjs',
12
+ '.css',
13
+ '.html', '.htm',
14
+ '.json',
15
+ '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico',
16
+ '.woff', '.woff2', '.ttf', '.otf', '.eot',
17
+ '.txt', '.xml', '.pdf',
18
+ '.webm', '.mp4', '.mp3', '.wav', '.ogg',
19
+ '.wasm'
20
+ ],
11
21
  blockedPaths = [
12
22
  '/node_modules',
13
23
  '/package.json',
@@ -15,7 +25,8 @@ export function securityMiddleware(options = {}) {
15
25
  '/.env',
16
26
  '/.git'
17
27
  ],
18
- allowPublicAssets = true
28
+ allowPublicAssets = true,
29
+ publicPaths = ['/assets', '/public', '/images', '/styles']
19
30
  } = options;
20
31
 
21
32
  return (req, res, next) => {
@@ -35,9 +46,8 @@ export function securityMiddleware(options = {}) {
35
46
  });
36
47
  }
37
48
 
38
- // 2. Permitir acceso a assets públicos
49
+ // 2. Permitir acceso a assets públicos (según sliceConfig.publicFolders)
39
50
  if (allowPublicAssets) {
40
- const publicPaths = ['/assets', '/public', '/images', '/styles'];
41
51
  const isPublicAsset = publicPaths.some(publicPath =>
42
52
  requestPath.startsWith(publicPath)
43
53
  );
@@ -92,9 +102,7 @@ export function sliceFrameworkProtection(options = {}) {
92
102
 
93
103
  // Rutas del framework que requieren verificación
94
104
  const frameworkPaths = [
95
- '/Slice/Components/Structural',
96
- '/Slice/Core',
97
- '/Slice/Services'
105
+ '/Slice/Components/Structural'
98
106
  ];
99
107
 
100
108
  const isFrameworkFile = frameworkPaths.some(fwPath =>
@@ -182,72 +190,8 @@ export function suspiciousRequestLogger() {
182
190
  };
183
191
  }
184
192
 
185
- /**
186
- * Middleware para bloquear acceso directo vía navegador (typing en la URL)
187
- * pero permitir peticiones desde scripts (fetch, import, etc.)
188
- */
189
- export function directAccessProtection(options = {}) {
190
- const { protectedPaths = [] } = options;
191
-
192
- return (req, res, next) => {
193
- const requestPath = req.path;
194
-
195
- const isProtectedPath = protectedPaths.some(protectedPath =>
196
- requestPath.startsWith(protectedPath)
197
- );
198
-
199
- if (!isProtectedPath) {
200
- return next();
201
- }
202
-
203
- // Detectar acceso directo:
204
- // - No tiene Referer (usuario escribió la URL directamente)
205
- // - Accept header indica navegación HTML
206
- const referer = req.get('Referer');
207
- const accept = req.get('Accept') || '';
208
-
209
- const isDirectBrowserAccess = !referer && accept.includes('text/html');
210
-
211
- if (isDirectBrowserAccess) {
212
- console.warn(`🚫 Blocked direct browser access: ${requestPath}`);
213
- return res.status(403).send(`
214
- <!DOCTYPE html>
215
- <html>
216
- <head>
217
- <title>Access Denied</title>
218
- <style>
219
- body {
220
- font-family: system-ui;
221
- max-width: 600px;
222
- margin: 100px auto;
223
- padding: 20px;
224
- }
225
- h1 { color: #d32f2f; }
226
- code {
227
- background: #f5f5f5;
228
- padding: 2px 6px;
229
- border-radius: 3px;
230
- }
231
- </style>
232
- </head>
233
- <body>
234
- <h1>🚫 Direct Access Denied</h1>
235
- <p>This file cannot be accessed directly.</p>
236
- <p>Path: <code>${requestPath}</code></p>
237
- <p>Framework files are automatically loaded by the application.</p>
238
- <p><a href="/">← Return to application</a></p>
239
- </body>
240
- </html>
241
- `);
242
- }
243
-
244
- next();
245
- };
246
- }
247
-
248
193
  export default {
249
194
  securityMiddleware,
250
195
  sliceFrameworkProtection,
251
- suspiciousRequestLogger,
252
- directAccessProtection
253
- };
196
+ suspiciousRequestLogger
197
+ };
@@ -0,0 +1,333 @@
1
+ // api/framework/server.js - Servidor Express encapsulado del framework
2
+ import express from 'express';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import compression from 'compression';
6
+ import {
7
+ securityMiddleware,
8
+ sliceFrameworkProtection,
9
+ suspiciousRequestLogger
10
+ } from './securityMiddleware.js';
11
+ import { createPublicEnvProvider } from './publicEnvResolver.js';
12
+
13
+ export function createSliceServer(options = {}) {
14
+ const projectRoot = options.projectRoot || process.cwd();
15
+ const runMode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
16
+ const folderDeployed = runMode === 'production' ? 'dist' : 'src';
17
+
18
+ let sliceConfig = {};
19
+ try {
20
+ const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
21
+ if (fs.existsSync(configPath)) {
22
+ sliceConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
23
+ }
24
+ } catch {
25
+ // usa defaults si no se puede leer la config
26
+ }
27
+
28
+ let frameworkVersion = '';
29
+ try {
30
+ const pkgPath = new URL('../../package.json', import.meta.url);
31
+ frameworkVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
32
+ } catch {
33
+ frameworkVersion = 'unknown';
34
+ }
35
+
36
+ const PORT = process.env.PORT || sliceConfig.server?.port || 3001;
37
+
38
+ const normalizedPublicFolders = (Array.isArray(sliceConfig.publicFolders) ? sliceConfig.publicFolders : [])
39
+ .filter((entry) => typeof entry === 'string')
40
+ .map((entry) => entry.trim())
41
+ .filter((entry) => entry.length > 0)
42
+ .map((entry) => (entry.startsWith('/') ? entry : `/${entry}`));
43
+
44
+ const publicEnvProvider = createPublicEnvProvider({
45
+ mode: runMode,
46
+ envFilePath: path.join(projectRoot, '.env')
47
+ });
48
+
49
+ const app = express();
50
+
51
+ // ==============================================
52
+ // MIDDLEWARES DE SEGURIDAD (APLICAR PRIMERO)
53
+ // ==============================================
54
+
55
+ // 1. Logger de peticiones sospechosas (solo observación, no bloquea)
56
+ app.use(suspiciousRequestLogger());
57
+
58
+ // 2. Protección del framework - TOTALMENTE AUTOMÁTICA
59
+ app.use(sliceFrameworkProtection({ port: PORT }));
60
+
61
+ app.use(securityMiddleware({
62
+ publicPaths: normalizedPublicFolders
63
+ }));
64
+
65
+ // ==============================================
66
+ // MIDDLEWARES DE APLICACIÓN
67
+ // ==============================================
68
+
69
+ // Compresión gzip/brotli para respuestas (threshold 0 = comprimir todo)
70
+ app.use(compression({ threshold: 0 }));
71
+
72
+ // Security headers básicos
73
+ app.use((req, res, next) => {
74
+ res.setHeader('X-Content-Type-Options', 'nosniff');
75
+ res.setHeader('X-Frame-Options', 'DENY');
76
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
77
+ next();
78
+ });
79
+
80
+ // Middleware global para archivos JavaScript con MIME types correctos
81
+ app.use((req, res, next) => {
82
+ if (req.path.endsWith('.js') || req.path.endsWith('.mjs') || req.path.endsWith('.cjs')) {
83
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
84
+ }
85
+ next();
86
+ });
87
+
88
+ // Middleware para parsear JSON y formularios
89
+ app.use(express.json());
90
+ app.use(express.urlencoded({ extended: true }));
91
+
92
+ // Configurar headers de CORS
93
+ app.use((req, res, next) => {
94
+ res.header('Access-Control-Allow-Origin', '*');
95
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
96
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
97
+
98
+ if (req.method === 'OPTIONS') {
99
+ res.sendStatus(200);
100
+ } else {
101
+ next();
102
+ }
103
+ });
104
+
105
+ // ==============================================
106
+ // RUNTIME MODE ENDPOINT
107
+ // ==============================================
108
+
109
+ app.get('/slice-env.json', (req, res) => {
110
+ const payload = publicEnvProvider.getPayload();
111
+ res.setHeader('Cache-Control', 'no-store');
112
+ res.setHeader('Pragma', 'no-cache');
113
+ res.setHeader('Expires', '0');
114
+ res.json(payload);
115
+ });
116
+
117
+ // ==============================================
118
+ // PWA — manifest + service worker (dev y prod)
119
+ // ==============================================
120
+
121
+ const servePwaFile = (res, fileName, contentType, extraHeaders = {}) => {
122
+ const filePath = path.join(projectRoot, folderDeployed, fileName);
123
+ try {
124
+ if (fs.existsSync(filePath)) {
125
+ res.setHeader('Content-Type', contentType);
126
+ for (const [key, value] of Object.entries(extraHeaders)) res.setHeader(key, value);
127
+ return res.send(fs.readFileSync(filePath, 'utf8'));
128
+ }
129
+ } catch (error) {
130
+ console.error(`Error reading ${fileName}:`, error);
131
+ return res.status(500).send(`Error reading ${fileName}`);
132
+ }
133
+ return res.status(404).send(`${fileName} not found`);
134
+ };
135
+
136
+ app.get('/service-worker.js', (req, res) => {
137
+ servePwaFile(res, 'service-worker.js', 'application/javascript; charset=utf-8', {
138
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
139
+ 'Service-Worker-Allowed': '/',
140
+ });
141
+ });
142
+
143
+ app.get('/manifest.json', (req, res) => {
144
+ servePwaFile(res, 'manifest.json', 'application/manifest+json; charset=utf-8');
145
+ });
146
+
147
+ // ==============================================
148
+ // ARCHIVOS ESTÁTICOS (DESPUÉS DE SEGURIDAD)
149
+ // ==============================================
150
+
151
+ if (runMode === 'production') {
152
+ app.get('/Slice/Slice.js', (req, res) => {
153
+ const slicePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'Slice', 'Slice.js');
154
+ try {
155
+ if (fs.existsSync(slicePath)) {
156
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
157
+ return res.send(fs.readFileSync(slicePath, 'utf8'));
158
+ }
159
+ } catch (error) {
160
+ console.error(`Error reading Slice.js:`, error);
161
+ return res.status(500).send('Error loading framework');
162
+ }
163
+ return res.status(404).send('Slice.js not found');
164
+ });
165
+
166
+ app.use('/Slice', (req, res) => res.status(404).send('Not found'));
167
+ app.use('/Components', (req, res) => res.status(404).send('Not found'));
168
+ }
169
+
170
+ // Middleware personalizado para archivos de bundles con MIME types correctos
171
+ app.use('/bundles/', (req, res, next) => {
172
+ if (req.path.endsWith('.js')) {
173
+ const filePath = path.join(projectRoot, folderDeployed, 'bundles', req.path);
174
+ console.log(`📂 Processing bundle: ${req.path} -> ${filePath}`);
175
+
176
+ if (fs.existsSync(filePath)) {
177
+ try {
178
+ const fileContent = fs.readFileSync(filePath, 'utf8');
179
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
180
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
181
+ res.setHeader('Pragma', 'no-cache');
182
+ res.setHeader('Expires', '0');
183
+ console.log(`✅ Serving bundle: ${req.path} (${fileContent.length} bytes, ${Buffer.byteLength(fileContent, 'utf8')} bytes UTF-8)`);
184
+ return res.send(fileContent);
185
+ } catch (error) {
186
+ console.error(`Error reading bundle file:`, error);
187
+ return res.status(500).send('Error reading bundle file');
188
+ }
189
+ } else {
190
+ console.log(`❌ Bundle file not found: ${filePath}`);
191
+ return res.status(404).send('Bundle file not found');
192
+ }
193
+ }
194
+
195
+ next();
196
+ });
197
+
198
+ app.use('/bundles/', express.static(path.join(projectRoot, folderDeployed, 'bundles')));
199
+ console.log(`📦 Serving bundles from /${folderDeployed}/bundles`);
200
+
201
+ // Servir framework Slice.js (solo development)
202
+ if (runMode === 'development') {
203
+ app.use('/Slice/', express.static(path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'Slice')));
204
+ }
205
+
206
+ if (runMode === 'development') {
207
+ app.use(express.static(path.join(projectRoot, folderDeployed)));
208
+ } else {
209
+ app.use('/App', express.static(path.join(projectRoot, folderDeployed, 'App')));
210
+ const serveStaticFile = (req, res, filePath, contentType, fileName) => {
211
+ try {
212
+ if (fs.existsSync(filePath)) {
213
+ res.setHeader('Content-Type', contentType);
214
+ return res.send(fs.readFileSync(filePath, 'utf8'));
215
+ }
216
+ } catch (error) {
217
+ console.error(`Error reading ${fileName}:`, error);
218
+ return res.status(500).send(`Error reading ${fileName}`);
219
+ }
220
+ return res.status(404).send(`${fileName} not found`);
221
+ };
222
+
223
+ app.get('/routes.js', (req, res) => {
224
+ const routesPath = path.join(projectRoot, folderDeployed, 'routes.js');
225
+ serveStaticFile(req, res, routesPath, 'application/javascript; charset=utf-8', 'routes.js');
226
+ });
227
+ app.get('/sliceConfig.json', (req, res) => {
228
+ const configPath = path.join(projectRoot, folderDeployed, 'sliceConfig.json');
229
+ serveStaticFile(req, res, configPath, 'application/json; charset=utf-8', 'sliceConfig.json');
230
+ });
231
+ for (const folder of normalizedPublicFolders) {
232
+ app.use(folder, express.static(path.join(projectRoot, folderDeployed, folder)));
233
+ }
234
+ app.use('/bundles/', express.static(path.join(projectRoot, folderDeployed, 'bundles')));
235
+ app.use('/dist/', express.static(path.join(projectRoot, 'dist')));
236
+ }
237
+
238
+ // ==============================================
239
+ // RUTAS DE API
240
+ // ==============================================
241
+
242
+ // Ruta de ejemplo para API
243
+ app.get('/api/status', (req, res) => {
244
+ res.json({
245
+ status: 'ok',
246
+ mode: runMode,
247
+ folder: folderDeployed,
248
+ timestamp: new Date().toISOString(),
249
+ framework: 'Slice.js',
250
+ version: frameworkVersion,
251
+ security: {
252
+ enabled: true,
253
+ mode: 'automatic',
254
+ description: 'Zero-config security - works with any domain'
255
+ }
256
+ });
257
+ });
258
+
259
+ // ==============================================
260
+ // SEO: robots.txt & sitemap.xml
261
+ // ==============================================
262
+
263
+ app.get('/robots.txt', (req, res) => {
264
+ res.sendFile(path.join(projectRoot, folderDeployed, 'robots.txt'));
265
+ });
266
+
267
+ app.get('/sitemap.xml', (req, res) => {
268
+ res.sendFile(path.join(projectRoot, folderDeployed, 'sitemap.xml'));
269
+ });
270
+
271
+ // ==============================================
272
+ // API 404 — las rutas /api/* irreconocibles devuelven JSON, no HTML
273
+ // ==============================================
274
+
275
+ app.use('/api', (req, res) => {
276
+ res.status(404).json({ error: 'API route not found', path: req.originalUrl });
277
+ });
278
+
279
+ // ==============================================
280
+ // SPA FALLBACK
281
+ // ==============================================
282
+
283
+ app.get('*', (req, res) => {
284
+ const indexPath = path.join(projectRoot, folderDeployed, 'App', 'index.html');
285
+ res.sendFile(indexPath, (err) => {
286
+ if (err) {
287
+ console.error(`[SPA Fallback] Error sending ${indexPath}:`, err);
288
+ res.status(500).send(`<h1>500 - Internal Server Error</h1><p>Could not serve application file</p>`);
289
+ }
290
+ });
291
+ });
292
+
293
+ // ==============================================
294
+ // ERROR HANDLER CENTRALIZADO
295
+ // ==============================================
296
+
297
+ app.use((err, req, res, next) => {
298
+ console.error('[Server Error]', err.stack || err.message || err);
299
+ if (res.headersSent) return next(err);
300
+ res.status(err.status || 500).json({
301
+ error: 'Internal Server Error',
302
+ message: process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : err.message
303
+ });
304
+ });
305
+
306
+ // ==============================================
307
+ // INICIO DEL SERVIDOR
308
+ // ==============================================
309
+
310
+ function startServer() {
311
+ const server = app.listen(PORT, () => {
312
+ console.log(`🔒 Security middleware: active (zero-config, automatic)`);
313
+ console.log(`🚀 Slice.js server running on port ${PORT}`);
314
+ });
315
+
316
+ process.on('SIGINT', () => {
317
+ console.log('\n🛑 Slice server stopped');
318
+ process.exit(0);
319
+ });
320
+
321
+ process.on('SIGTERM', () => {
322
+ console.log('\n🛑 Server terminated');
323
+ process.exit(0);
324
+ });
325
+
326
+ return server;
327
+ }
328
+
329
+ return {
330
+ app,
331
+ start: startServer
332
+ };
333
+ }