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.
- package/LICENSE +21 -21
- package/README.md +97 -97
- package/Slice/Components/Structural/ContextManager/ContextManager.js +422 -422
- package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +470 -470
- package/Slice/Components/Structural/Controller/Controller.js +1406 -1406
- package/Slice/Components/Structural/Controller/allowedValuesValidation.js +52 -52
- package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
- package/Slice/Components/Structural/Debugger/Debugger.js +1505 -1505
- package/Slice/Components/Structural/EventManager/EventManager.js +651 -651
- package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +985 -985
- package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -296
- package/Slice/Components/Structural/Logger/Log.js +10 -10
- package/Slice/Components/Structural/Logger/LogViewer/LogViewer.js +662 -662
- package/Slice/Components/Structural/Logger/Logger.js +225 -225
- package/Slice/Components/Structural/Router/Router.js +820 -810
- package/Slice/Components/Structural/Router/applyRouteMetadata.js +39 -0
- package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
- package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +175 -175
- package/Slice/Slice.js +711 -710
- package/api/framework/devDepsOptimizer.js +335 -335
- package/api/framework/express.js +3 -3
- package/api/framework/precompressedStatic.js +102 -93
- package/api/framework/publicEnvResolver.js +123 -123
- package/api/framework/securityMiddleware.js +197 -197
- package/api/framework/seo.js +387 -0
- package/api/framework/server.js +777 -410
- package/api/index.js +14 -4
- package/package.json +6 -2
- package/sliceConfig.schema.json +122 -122
- package/src/App/index.html +69 -69
- package/src/App/index.js +20 -20
- package/src/App/style.css +11 -11
- package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -9
- package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -8
- package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -12
- package/src/Components/AppComponents/AppShell/AppShell.css +10 -10
- package/src/Components/AppComponents/AppShell/AppShell.html +4 -4
- package/src/Components/AppComponents/AppShell/AppShell.js +36 -36
- package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -20
- package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -10
- package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -19
- package/src/Components/components.js +8 -8
- package/src/public/Styles/sliceStyles.css +34 -34
- package/src/public/Themes/Dark.css +42 -42
- package/src/public/Themes/Light.css +31 -31
- package/src/public/Themes/Slice.css +47 -47
- package/src/public/manifest.json +15 -15
- package/src/public/robots.txt +4 -4
- package/src/public/sitemap.xml +8 -8
- package/src/routes.js +9 -9
- package/src/sliceConfig.json +72 -72
- package/types/index.d.ts +219 -219
- package/.opencode/opencode.json +0 -15
- package/Slice/tests/build-bundled-component-without-category.test.js +0 -103
- package/Slice/tests/build-js-only-visual-components.test.js +0 -144
- package/Slice/tests/build-singleton.test.js +0 -244
- package/Slice/tests/bundle-v2-runtime-contract.test.js +0 -747
- package/Slice/tests/context-patch-use.test.js +0 -95
- package/Slice/tests/destroy-cascade.test.js +0 -215
- package/Slice/tests/event-emitters.test.js +0 -79
- package/Slice/tests/event-registry.test.js +0 -141
- package/Slice/tests/fixtures/real-runtime-loader.mjs +0 -21
- package/Slice/tests/leak-orphans.test.js +0 -131
- package/Slice/tests/props-allowed-values-validation.test.js +0 -119
- package/Slice/tests/public-env-runtime-accessors.test.js +0 -44
- package/Slice/tests/public-env-typed-accessors.test.js +0 -66
- package/Slice/tests/router-loading-finally.test.js +0 -68
- package/api/tests/dev-deps-optimizer.test.js +0 -137
- package/api/tests/dev-server-external.test.js +0 -56
- package/api/tests/precompressed-static.test.js +0 -119
- package/api/tests/public-env-resolver.test.js +0 -193
- package/tests/router-build-failure.test.js +0 -125
- package/tests/router-emit-ordering.test.js +0 -63
package/api/framework/express.js
CHANGED
|
@@ -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
|
-
* @
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|