mnfst-run 1.0.19 → 1.0.20
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/package.json +1 -1
- package/serve.mjs +48 -25
package/package.json
CHANGED
package/serve.mjs
CHANGED
|
@@ -123,13 +123,23 @@ const LIVE_RELOAD_SCRIPT = `<script>
|
|
|
123
123
|
})();
|
|
124
124
|
\x3c/script>`;
|
|
125
125
|
|
|
126
|
-
// Read a dotenv-style file from the project root and return
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
// absent or fails to parse.
|
|
126
|
+
// Read a dotenv-style file from the project root and return two maps: `public`
|
|
127
|
+
// (vars eligible to ship to the browser) and `private` (vars kept server-side).
|
|
128
|
+
// Only the public map is injected into `window.env`; the private map is logged
|
|
129
|
+
// at startup so devs can see what was withheld, but never reaches HTML.
|
|
131
130
|
//
|
|
132
|
-
//
|
|
131
|
+
// Public/private split is by name prefix — matching the established convention
|
|
132
|
+
// (Astro `PUBLIC_`, SvelteKit `PUBLIC_`, Vite `VITE_`, Next `NEXT_PUBLIC_`):
|
|
133
|
+
// - PUBLIC_FOO=… → exposed via window.env.PUBLIC_FOO
|
|
134
|
+
// - MANIFEST_API_KEY=…, STRIPE_SECRET=…, anything else → server-side only
|
|
135
|
+
//
|
|
136
|
+
// Rationale: prior versions injected the entire .env, so the scaffold's own
|
|
137
|
+
// MANIFEST_API_KEY (which create-starter writes with a "treat like a password"
|
|
138
|
+
// comment) was visible in view-source on every served page. The prefix gate
|
|
139
|
+
// makes the rule explicit at the call site rather than relying on devs to know
|
|
140
|
+
// that .env values reach the browser.
|
|
141
|
+
//
|
|
142
|
+
// Supported parse subset (intentionally minimal, no expansion / multiline):
|
|
133
143
|
// - KEY=value (whitespace around `=` ok)
|
|
134
144
|
// - KEY="quoted" / KEY='…' (surrounding quotes stripped)
|
|
135
145
|
// - # comments and blank lines ignored
|
|
@@ -142,8 +152,9 @@ const LIVE_RELOAD_SCRIPT = `<script>
|
|
|
142
152
|
// by the host. See the Appwrite setup doc for the full pattern.
|
|
143
153
|
function loadEnvFile(rootDir) {
|
|
144
154
|
const envPath = join(rootDir, '.env');
|
|
145
|
-
if (!existsSync(envPath)) return {};
|
|
146
|
-
const
|
|
155
|
+
if (!existsSync(envPath)) return { public: {}, private: [] };
|
|
156
|
+
const publicEnv = {};
|
|
157
|
+
const privateNames = [];
|
|
147
158
|
try {
|
|
148
159
|
const text = readFileSync(envPath, 'utf8');
|
|
149
160
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -158,22 +169,24 @@ function loadEnvFile(rootDir) {
|
|
|
158
169
|
(value.startsWith("'") && value.endsWith("'"))) {
|
|
159
170
|
value = value.slice(1, -1);
|
|
160
171
|
}
|
|
161
|
-
|
|
172
|
+
if (key.startsWith('PUBLIC_')) publicEnv[key] = value;
|
|
173
|
+
else privateNames.push(key);
|
|
162
174
|
}
|
|
163
175
|
} catch (error) {
|
|
164
176
|
console.warn('[mnfst-run] Failed to parse .env:', error.message);
|
|
165
177
|
}
|
|
166
|
-
return
|
|
178
|
+
return { public: publicEnv, private: privateNames };
|
|
167
179
|
}
|
|
168
180
|
|
|
169
|
-
// Build a `<script>window.env = {…};</script>` tag from
|
|
170
|
-
// Returns '' when there are no vars (so the injection is a no-op for
|
|
171
|
-
//
|
|
172
|
-
// env value can't break out
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
// Build a `<script>window.env = {…};</script>` tag from the public env map.
|
|
182
|
+
// Returns '' when there are no public vars (so the injection is a no-op for
|
|
183
|
+
// projects whose .env contains only server-side secrets). Escapes any
|
|
184
|
+
// `</script` substring inside string values so an env value can't break out
|
|
185
|
+
// of the script tag.
|
|
186
|
+
function buildEnvInjectScript(publicEnv) {
|
|
187
|
+
const keys = Object.keys(publicEnv);
|
|
175
188
|
if (keys.length === 0) return '';
|
|
176
|
-
const json = JSON.stringify(
|
|
189
|
+
const json = JSON.stringify(publicEnv).replace(/<\/script/gi, '<\\/script');
|
|
177
190
|
return `<script>window.env = ${json};</script>`;
|
|
178
191
|
}
|
|
179
192
|
|
|
@@ -331,13 +344,22 @@ const root = resolve(process.cwd(), dir);
|
|
|
331
344
|
|
|
332
345
|
// Load .env from the serving root (if present) and pre-build the inject
|
|
333
346
|
// script. Kept as a single string so serveFile doesn't re-stringify on every
|
|
334
|
-
// HTML response. Empty string when no
|
|
335
|
-
// becomes a no-op for projects
|
|
336
|
-
const
|
|
337
|
-
const envInjectScript = buildEnvInjectScript(
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
340
|
-
console.log(`Loaded ${
|
|
347
|
+
// HTML response. Empty string when no public vars exist — the injection step
|
|
348
|
+
// becomes a no-op for projects whose .env holds only server-side secrets.
|
|
349
|
+
const { public: publicEnv, private: privateEnvNames } = loadEnvFile(root);
|
|
350
|
+
const envInjectScript = buildEnvInjectScript(publicEnv);
|
|
351
|
+
const publicCount = Object.keys(publicEnv).length;
|
|
352
|
+
if (publicCount > 0) {
|
|
353
|
+
console.log(`Loaded ${publicCount} PUBLIC_ env var(s) into window.env`);
|
|
354
|
+
}
|
|
355
|
+
if (privateEnvNames.length > 0) {
|
|
356
|
+
// Loud about what was withheld so devs notice when something they expected
|
|
357
|
+
// in the browser is server-side only — and so a misplaced PUBLIC_ prefix is
|
|
358
|
+
// obvious from the startup log.
|
|
359
|
+
console.log(
|
|
360
|
+
`[mnfst-run] ${privateEnvNames.length} non-PUBLIC_ var(s) NOT injected ` +
|
|
361
|
+
`into window.env (kept server-side): ${privateEnvNames.join(', ')}`
|
|
362
|
+
);
|
|
341
363
|
}
|
|
342
364
|
|
|
343
365
|
// Dedup: if a server is already serving this exact root, point the user at
|
|
@@ -467,11 +489,12 @@ function serveFile(res, filePath) {
|
|
|
467
489
|
// Only inject into full HTML documents — not component fragments
|
|
468
490
|
const isFullDoc = /<!doctype\s/i.test(html) || /<html[\s>]/i.test(html);
|
|
469
491
|
if (isFullDoc) {
|
|
470
|
-
// 1) Inject window.env into <head> (when
|
|
492
|
+
// 1) Inject window.env into <head> (when public env vars exist) so the
|
|
471
493
|
// framework's manifest.json env-var substitution can resolve
|
|
472
494
|
// `${VAR}` placeholders before any plugin reads the manifest.
|
|
473
495
|
// Must come BEFORE framework scripts execute — <head> insertion
|
|
474
496
|
// guarantees that ordering regardless of where script tags sit.
|
|
497
|
+
// ONLY PUBLIC_-prefixed vars are eligible; see loadEnvFile().
|
|
475
498
|
let injected = html;
|
|
476
499
|
if (envInjectScript) {
|
|
477
500
|
injected = injected.includes('</head>')
|