slicejs-web-framework 4.0.2 → 4.1.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.
Files changed (73) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +97 -97
  3. package/Slice/Components/Structural/ContextManager/ContextManager.js +422 -422
  4. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +470 -470
  5. package/Slice/Components/Structural/Controller/Controller.js +1406 -1406
  6. package/Slice/Components/Structural/Controller/allowedValuesValidation.js +52 -52
  7. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  8. package/Slice/Components/Structural/Debugger/Debugger.js +1505 -1505
  9. package/Slice/Components/Structural/EventManager/EventManager.js +651 -651
  10. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +985 -985
  11. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -296
  12. package/Slice/Components/Structural/Logger/Log.js +10 -10
  13. package/Slice/Components/Structural/Logger/LogViewer/LogViewer.js +662 -662
  14. package/Slice/Components/Structural/Logger/Logger.js +225 -225
  15. package/Slice/Components/Structural/Router/Router.js +820 -810
  16. package/Slice/Components/Structural/Router/applyRouteMetadata.js +39 -0
  17. package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
  18. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +175 -175
  19. package/Slice/Slice.js +711 -710
  20. package/api/framework/devDepsOptimizer.js +335 -335
  21. package/api/framework/express.js +3 -3
  22. package/api/framework/precompressedStatic.js +102 -93
  23. package/api/framework/publicEnvResolver.js +123 -123
  24. package/api/framework/securityMiddleware.js +197 -197
  25. package/api/framework/seo.js +387 -0
  26. package/api/framework/server.js +777 -410
  27. package/api/index.js +14 -4
  28. package/package.json +6 -2
  29. package/sliceConfig.schema.json +122 -122
  30. package/src/App/index.html +69 -69
  31. package/src/App/index.js +20 -20
  32. package/src/App/style.css +11 -11
  33. package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -9
  34. package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -8
  35. package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -12
  36. package/src/Components/AppComponents/AppShell/AppShell.css +10 -10
  37. package/src/Components/AppComponents/AppShell/AppShell.html +4 -4
  38. package/src/Components/AppComponents/AppShell/AppShell.js +36 -36
  39. package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -20
  40. package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -10
  41. package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -19
  42. package/src/Components/components.js +8 -8
  43. package/src/public/Styles/sliceStyles.css +34 -34
  44. package/src/public/Themes/Dark.css +42 -42
  45. package/src/public/Themes/Light.css +31 -31
  46. package/src/public/Themes/Slice.css +47 -47
  47. package/src/public/manifest.json +15 -15
  48. package/src/public/robots.txt +4 -4
  49. package/src/public/sitemap.xml +8 -8
  50. package/src/routes.js +9 -9
  51. package/src/sliceConfig.json +72 -72
  52. package/types/index.d.ts +219 -219
  53. package/.opencode/opencode.json +0 -15
  54. package/Slice/tests/build-bundled-component-without-category.test.js +0 -103
  55. package/Slice/tests/build-js-only-visual-components.test.js +0 -144
  56. package/Slice/tests/build-singleton.test.js +0 -244
  57. package/Slice/tests/bundle-v2-runtime-contract.test.js +0 -747
  58. package/Slice/tests/context-patch-use.test.js +0 -95
  59. package/Slice/tests/destroy-cascade.test.js +0 -215
  60. package/Slice/tests/event-emitters.test.js +0 -79
  61. package/Slice/tests/event-registry.test.js +0 -141
  62. package/Slice/tests/fixtures/real-runtime-loader.mjs +0 -21
  63. package/Slice/tests/leak-orphans.test.js +0 -131
  64. package/Slice/tests/props-allowed-values-validation.test.js +0 -119
  65. package/Slice/tests/public-env-runtime-accessors.test.js +0 -44
  66. package/Slice/tests/public-env-typed-accessors.test.js +0 -66
  67. package/Slice/tests/router-loading-finally.test.js +0 -68
  68. package/api/tests/dev-deps-optimizer.test.js +0 -137
  69. package/api/tests/dev-server-external.test.js +0 -56
  70. package/api/tests/precompressed-static.test.js +0 -119
  71. package/api/tests/public-env-resolver.test.js +0 -193
  72. package/tests/router-build-failure.test.js +0 -125
  73. package/tests/router-emit-ordering.test.js +0 -63
@@ -1,3 +1,3 @@
1
- import express from 'express';
2
-
3
- export default express;
1
+ import express from 'express';
2
+
3
+ export default express;
@@ -1,93 +1,102 @@
1
- // api/framework/precompressedStatic.js
2
- //
3
- // Serves a precompressed `.br`/`.gz` sibling (produced by `slice build --compress`)
4
- // when the client accepts that encoding and the file exists, instead of
5
- // compressing on every request. Falls through to the normal static handlers
6
- // when there is no precompressed variant or the client doesn't accept one.
7
- //
8
- // Mounted before the runtime `compression` middleware short-circuits it: the
9
- // response ends here with Content-Encoding already set, so compression never
10
- // re-compresses an already-encoded body.
11
-
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
-
15
- const ENCODING_EXTENSION = { br: '.br', gzip: '.gz' };
16
-
17
- // Only known text types are served pre-encoded; the Content-Type is taken from
18
- // the ORIGINAL extension (the browser must not see `.br` as the type).
19
- const CONTENT_TYPES = {
20
- '.js': 'application/javascript; charset=utf-8',
21
- '.mjs': 'application/javascript; charset=utf-8',
22
- '.cjs': 'application/javascript; charset=utf-8',
23
- '.css': 'text/css; charset=utf-8',
24
- '.html': 'text/html; charset=utf-8',
25
- '.htm': 'text/html; charset=utf-8',
26
- '.json': 'application/json; charset=utf-8',
27
- '.svg': 'image/svg+xml',
28
- '.xml': 'application/xml; charset=utf-8',
29
- '.txt': 'text/plain; charset=utf-8',
30
- '.map': 'application/json; charset=utf-8',
31
- '.webmanifest': 'application/manifest+json; charset=utf-8'
32
- };
33
-
34
- /** Encodings the client accepts, most-preferred first (brotli over gzip). */
35
- function preferredEncodings(acceptEncoding) {
36
- const accepts = String(acceptEncoding || '').toLowerCase();
37
- const list = [];
38
- if (accepts.includes('br')) list.push('br');
39
- if (accepts.includes('gzip')) list.push('gzip');
40
- return list;
41
- }
42
-
43
- /**
44
- * @param {string[]} roots Absolute directories to resolve `req.path` against,
45
- * in priority order (e.g. [dist/public, dist]).
46
- * @returns {import('express').RequestHandler}
47
- */
48
- export function createPrecompressedStatic(roots = []) {
49
- const resolvedRoots = roots.filter(Boolean).map((r) => path.resolve(r));
50
-
51
- return function precompressedStatic(req, res, next) {
52
- if (req.method !== 'GET' && req.method !== 'HEAD') return next();
53
-
54
- const encodings = preferredEncodings(req.headers['accept-encoding']);
55
- if (encodings.length === 0) return next();
56
-
57
- let rel;
58
- try {
59
- rel = decodeURIComponent(req.path).replace(/^\/+/, '');
60
- } catch {
61
- return next();
62
- }
63
- if (!rel || rel.includes('\0')) return next();
64
-
65
- const ext = path.extname(rel).toLowerCase();
66
- const contentType = CONTENT_TYPES[ext];
67
- if (!contentType) return next();
68
-
69
- for (const root of resolvedRoots) {
70
- const target = path.normalize(path.join(root, rel));
71
- // Path traversal guard: the resolved file must stay under the root.
72
- if (target !== root && !target.startsWith(root + path.sep)) continue;
73
-
74
- for (const enc of encodings) {
75
- const encodedPath = target + ENCODING_EXTENSION[enc];
76
- try {
77
- if (fs.existsSync(encodedPath) && fs.statSync(encodedPath).isFile()) {
78
- res.setHeader('Content-Type', contentType);
79
- res.setHeader('Content-Encoding', enc);
80
- res.setHeader('Vary', 'Accept-Encoding');
81
- return res.send(fs.readFileSync(encodedPath));
82
- }
83
- } catch {
84
- // Unreadable candidate — try the next encoding/root.
85
- }
86
- }
87
- }
88
-
89
- return next();
90
- };
91
- }
92
-
93
- export default createPrecompressedStatic;
1
+ // api/framework/precompressedStatic.js
2
+ //
3
+ // Serves a precompressed `.br`/`.gz` sibling (produced by `slice build --compress`)
4
+ // when the client accepts that encoding and the file exists, instead of
5
+ // compressing on every request. Falls through to the normal static handlers
6
+ // when there is no precompressed variant or the client doesn't accept one.
7
+ //
8
+ // Mounted before the runtime `compression` middleware short-circuits it: the
9
+ // response ends here with Content-Encoding already set, so compression never
10
+ // re-compresses an already-encoded body.
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+
15
+ const ENCODING_EXTENSION = { br: '.br', gzip: '.gz' };
16
+
17
+ // Only known text types are served pre-encoded; the Content-Type is taken from
18
+ // the ORIGINAL extension (the browser must not see `.br` as the type).
19
+ const CONTENT_TYPES = {
20
+ '.js': 'application/javascript; charset=utf-8',
21
+ '.mjs': 'application/javascript; charset=utf-8',
22
+ '.cjs': 'application/javascript; charset=utf-8',
23
+ '.css': 'text/css; charset=utf-8',
24
+ '.html': 'text/html; charset=utf-8',
25
+ '.htm': 'text/html; charset=utf-8',
26
+ '.json': 'application/json; charset=utf-8',
27
+ '.svg': 'image/svg+xml',
28
+ '.xml': 'application/xml; charset=utf-8',
29
+ '.txt': 'text/plain; charset=utf-8',
30
+ '.map': 'application/json; charset=utf-8',
31
+ '.webmanifest': 'application/manifest+json; charset=utf-8'
32
+ };
33
+
34
+ /** Encodings the client accepts, most-preferred first (brotli over gzip). */
35
+ function preferredEncodings(acceptEncoding) {
36
+ const accepts = String(acceptEncoding || '').toLowerCase();
37
+ const list = [];
38
+ if (accepts.includes('br')) list.push('br');
39
+ if (accepts.includes('gzip')) list.push('gzip');
40
+ return list;
41
+ }
42
+
43
+ /**
44
+ * @param {string[]} roots Absolute directories to resolve `req.path` against,
45
+ * in priority order (e.g. [dist/public, dist]).
46
+ * @param {{ noStorePrefixes?: string[] }} [options] Path prefixes that must not
47
+ * be cached. Bundles are unversioned, so they keep the same no-store policy
48
+ * the `/bundles/` handler applies when it serves them itself.
49
+ * @returns {import('express').RequestHandler}
50
+ */
51
+ export function createPrecompressedStatic(roots = [], options = {}) {
52
+ const resolvedRoots = roots.filter(Boolean).map((r) => path.resolve(r));
53
+ const noStorePrefixes = options.noStorePrefixes || [];
54
+
55
+ return function precompressedStatic(req, res, next) {
56
+ if (req.method !== 'GET' && req.method !== 'HEAD') return next();
57
+
58
+ const encodings = preferredEncodings(req.headers['accept-encoding']);
59
+ if (encodings.length === 0) return next();
60
+
61
+ let rel;
62
+ try {
63
+ rel = decodeURIComponent(req.path).replace(/^\/+/, '');
64
+ } catch {
65
+ return next();
66
+ }
67
+ if (!rel || rel.includes('\0')) return next();
68
+
69
+ const ext = path.extname(rel).toLowerCase();
70
+ const contentType = CONTENT_TYPES[ext];
71
+ if (!contentType) return next();
72
+
73
+ for (const root of resolvedRoots) {
74
+ const target = path.normalize(path.join(root, rel));
75
+ // Path traversal guard: the resolved file must stay under the root.
76
+ if (target !== root && !target.startsWith(root + path.sep)) continue;
77
+
78
+ for (const enc of encodings) {
79
+ const encodedPath = target + ENCODING_EXTENSION[enc];
80
+ try {
81
+ if (fs.existsSync(encodedPath) && fs.statSync(encodedPath).isFile()) {
82
+ res.setHeader('Content-Type', contentType);
83
+ res.setHeader('Content-Encoding', enc);
84
+ res.setHeader('Vary', 'Accept-Encoding');
85
+ if (noStorePrefixes.some((prefix) => req.path.startsWith(prefix))) {
86
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
87
+ res.setHeader('Pragma', 'no-cache');
88
+ res.setHeader('Expires', '0');
89
+ }
90
+ return res.send(fs.readFileSync(encodedPath));
91
+ }
92
+ } catch {
93
+ // Unreadable candidate — try the next encoding/root.
94
+ }
95
+ }
96
+ }
97
+
98
+ return next();
99
+ };
100
+ }
101
+
102
+ export default createPrecompressedStatic;
@@ -1,123 +1,123 @@
1
- import { readFileSync, existsSync } from 'node:fs';
2
-
3
- const PUBLIC_PREFIX = 'SLICE_PUBLIC_';
4
- const SUSPICIOUS_TERMS = ['SECRET', 'TOKEN', 'PASSWORD', 'PRIVATE', 'API_KEY', 'ACCESS_KEY', 'CREDENTIAL'];
5
-
6
- function parseEnvFile(envFilePath) {
7
- if (!envFilePath || !existsSync(envFilePath)) {
8
- return {};
9
- }
10
-
11
- let fileContent;
12
- try {
13
- fileContent = readFileSync(envFilePath, 'utf8').replace(/^\uFEFF/, '');
14
- } catch (error) {
15
- console.error(`Error reading env file ${envFilePath}:`, error);
16
- return {};
17
- }
18
- const parsed = {};
19
- const lines = fileContent.split(/\r?\n/);
20
-
21
- for (const rawLine of lines) {
22
- const line = rawLine.trim();
23
-
24
- if (!line || line.startsWith('#')) {
25
- continue;
26
- }
27
-
28
- const equalsIndex = line.indexOf('=');
29
- if (equalsIndex === -1) {
30
- continue;
31
- }
32
-
33
- let key = line.slice(0, equalsIndex).trim();
34
- if (!key) {
35
- continue;
36
- }
37
-
38
- if (key.startsWith('export ')) {
39
- key = key.slice('export '.length).trim();
40
- }
41
-
42
- let value = line.slice(equalsIndex + 1).trim();
43
-
44
- const quotedWithOptionalCommentMatch = value.match(/^(["'])(.*?)\1(?:\s+#.*)?$/);
45
-
46
- if (quotedWithOptionalCommentMatch) {
47
- value = quotedWithOptionalCommentMatch[2];
48
- } else {
49
- value = value.replace(/\s+#.*$/, '').trimEnd();
50
- }
51
-
52
- parsed[key] = value;
53
- }
54
-
55
- return parsed;
56
- }
57
-
58
- function warnSuspiciousKey(key, logger, warnedKeys) {
59
- const upperKey = key.toUpperCase();
60
- const isSuspicious = SUSPICIOUS_TERMS.some((term) => upperKey.includes(term));
61
-
62
- if (isSuspicious && !warnedKeys.has(key) && logger && typeof logger.warn === 'function') {
63
- logger.warn(`[slice-env] Suspicious public environment key detected: ${key}`);
64
- warnedKeys.add(key);
65
- }
66
- }
67
-
68
- function buildPublicPayload({ envFromFile, processEnv, logger }) {
69
- const env = {};
70
- const warnedKeys = new Set();
71
-
72
- for (const [key, value] of Object.entries(envFromFile)) {
73
- if (key.startsWith(PUBLIC_PREFIX)) {
74
- env[key] = String(value ?? '');
75
- warnSuspiciousKey(key, logger, warnedKeys);
76
- }
77
- }
78
-
79
- for (const [key, value] of Object.entries(processEnv || {})) {
80
- if (key.startsWith(PUBLIC_PREFIX)) {
81
- env[key] = String(value ?? '');
82
- warnSuspiciousKey(key, logger, warnedKeys);
83
- }
84
- }
85
-
86
- return env;
87
- }
88
-
89
- export function resolvePublicEnv({ mode, envFilePath, processEnv = process.env, logger = console }) {
90
- const envFromFile = parseEnvFile(envFilePath);
91
- const env = buildPublicPayload({
92
- envFromFile,
93
- processEnv,
94
- logger,
95
- });
96
-
97
- return {
98
- mode,
99
- env,
100
- };
101
- }
102
-
103
- export function createPublicEnvProvider({ mode, envFilePath, processEnv = process.env, logger = console }) {
104
- if (mode === 'production') {
105
- let cachedPayload;
106
-
107
- return {
108
- getPayload() {
109
- if (!cachedPayload) {
110
- cachedPayload = resolvePublicEnv({ mode, envFilePath, processEnv, logger });
111
- }
112
-
113
- return cachedPayload;
114
- },
115
- };
116
- }
117
-
118
- return {
119
- getPayload() {
120
- return resolvePublicEnv({ mode, envFilePath, processEnv, logger });
121
- },
122
- };
123
- }
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+
3
+ const PUBLIC_PREFIX = 'SLICE_PUBLIC_';
4
+ const SUSPICIOUS_TERMS = ['SECRET', 'TOKEN', 'PASSWORD', 'PRIVATE', 'API_KEY', 'ACCESS_KEY', 'CREDENTIAL'];
5
+
6
+ function parseEnvFile(envFilePath) {
7
+ if (!envFilePath || !existsSync(envFilePath)) {
8
+ return {};
9
+ }
10
+
11
+ let fileContent;
12
+ try {
13
+ fileContent = readFileSync(envFilePath, 'utf8').replace(/^\uFEFF/, '');
14
+ } catch (error) {
15
+ console.error(`Error reading env file ${envFilePath}:`, error);
16
+ return {};
17
+ }
18
+ const parsed = {};
19
+ const lines = fileContent.split(/\r?\n/);
20
+
21
+ for (const rawLine of lines) {
22
+ const line = rawLine.trim();
23
+
24
+ if (!line || line.startsWith('#')) {
25
+ continue;
26
+ }
27
+
28
+ const equalsIndex = line.indexOf('=');
29
+ if (equalsIndex === -1) {
30
+ continue;
31
+ }
32
+
33
+ let key = line.slice(0, equalsIndex).trim();
34
+ if (!key) {
35
+ continue;
36
+ }
37
+
38
+ if (key.startsWith('export ')) {
39
+ key = key.slice('export '.length).trim();
40
+ }
41
+
42
+ let value = line.slice(equalsIndex + 1).trim();
43
+
44
+ const quotedWithOptionalCommentMatch = value.match(/^(["'])(.*?)\1(?:\s+#.*)?$/);
45
+
46
+ if (quotedWithOptionalCommentMatch) {
47
+ value = quotedWithOptionalCommentMatch[2];
48
+ } else {
49
+ value = value.replace(/\s+#.*$/, '').trimEnd();
50
+ }
51
+
52
+ parsed[key] = value;
53
+ }
54
+
55
+ return parsed;
56
+ }
57
+
58
+ function warnSuspiciousKey(key, logger, warnedKeys) {
59
+ const upperKey = key.toUpperCase();
60
+ const isSuspicious = SUSPICIOUS_TERMS.some((term) => upperKey.includes(term));
61
+
62
+ if (isSuspicious && !warnedKeys.has(key) && logger && typeof logger.warn === 'function') {
63
+ logger.warn(`[slice-env] Suspicious public environment key detected: ${key}`);
64
+ warnedKeys.add(key);
65
+ }
66
+ }
67
+
68
+ function buildPublicPayload({ envFromFile, processEnv, logger }) {
69
+ const env = {};
70
+ const warnedKeys = new Set();
71
+
72
+ for (const [key, value] of Object.entries(envFromFile)) {
73
+ if (key.startsWith(PUBLIC_PREFIX)) {
74
+ env[key] = String(value ?? '');
75
+ warnSuspiciousKey(key, logger, warnedKeys);
76
+ }
77
+ }
78
+
79
+ for (const [key, value] of Object.entries(processEnv || {})) {
80
+ if (key.startsWith(PUBLIC_PREFIX)) {
81
+ env[key] = String(value ?? '');
82
+ warnSuspiciousKey(key, logger, warnedKeys);
83
+ }
84
+ }
85
+
86
+ return env;
87
+ }
88
+
89
+ export function resolvePublicEnv({ mode, envFilePath, processEnv = process.env, logger = console }) {
90
+ const envFromFile = parseEnvFile(envFilePath);
91
+ const env = buildPublicPayload({
92
+ envFromFile,
93
+ processEnv,
94
+ logger,
95
+ });
96
+
97
+ return {
98
+ mode,
99
+ env,
100
+ };
101
+ }
102
+
103
+ export function createPublicEnvProvider({ mode, envFilePath, processEnv = process.env, logger = console }) {
104
+ if (mode === 'production') {
105
+ let cachedPayload;
106
+
107
+ return {
108
+ getPayload() {
109
+ if (!cachedPayload) {
110
+ cachedPayload = resolvePublicEnv({ mode, envFilePath, processEnv, logger });
111
+ }
112
+
113
+ return cachedPayload;
114
+ },
115
+ };
116
+ }
117
+
118
+ return {
119
+ getPayload() {
120
+ return resolvePublicEnv({ mode, envFilePath, processEnv, logger });
121
+ },
122
+ };
123
+ }