From 992e32a0f27bf015b684fde8c16d7759c741d22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 4 Oct 2026 10:34:41 -0400 Subject: [PATCH 1/2] fix(cli): a project .env cannot set the feedback email in another letter case --- packages/cli/src/utils/dotEnv.test.ts | 9 +++++++++ packages/cli/src/utils/dotEnv.ts | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/utils/dotEnv.test.ts b/packages/cli/src/utils/dotEnv.test.ts index 478d61ccc9..88594b62e1 100644 --- a/packages/cli/src/utils/dotEnv.test.ts +++ b/packages/cli/src/utils/dotEnv.test.ts @@ -13,4 +13,13 @@ describe("applyDotEnv", () => { applyDotEnv("HYPERFRAMES_FEEDBACK_EMAIL=someone@example.com\nOTHER=1", env); expect(env).toEqual({ OTHER: "1" }); }); + + it("never takes the feedback email under another letter case", () => { + const env: NodeJS.ProcessEnv = {}; + applyDotEnv( + "hyperframes_feedback_email=a@example.com\nHyperFrames_Feedback_Email=b@example.com\nOTHER=1", + env, + ); + expect(env).toEqual({ OTHER: "1" }); + }); }); diff --git a/packages/cli/src/utils/dotEnv.ts b/packages/cli/src/utils/dotEnv.ts index 64818c9f6f..a0c9fe744d 100644 --- a/packages/cli/src/utils/dotEnv.ts +++ b/packages/cli/src/utils/dotEnv.ts @@ -20,12 +20,12 @@ function entryOf(rawLine: string): [string, string] | null { return [line.slice(0, eq).trim(), valueOf(line.slice(eq + 1).trim())]; } -// Never over a key already set, and never the feedback email: only the launching app may attach it, and an agent can -// write a project `.env`. +// Never over a key already set, and never the feedback email in any letter case (Windows env names are case-blind): +// only the launching app may attach it, and an agent can write a project `.env`. export function applyDotEnv(content: string, env: NodeJS.ProcessEnv): void { for (const entry of content.split("\n").map(entryOf)) { if (!entry) continue; const [key, value] = entry; - if (key && key !== FEEDBACK_EMAIL_ENV && !(key in env)) env[key] = value; + if (key && key.toUpperCase() !== FEEDBACK_EMAIL_ENV && !(key in env)) env[key] = value; } } From 62e0c17b5a0f7762f3d2b9975cc28dc4542e6eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 4 Oct 2026 10:47:16 -0400 Subject: [PATCH 2/2] fix(cli): capture loads a project .env through the same rules as the CLI --- packages/cli/src/capture/scaffolding.test.ts | 25 +++++++++++++++++++- packages/cli/src/capture/scaffolding.ts | 15 ++---------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/capture/scaffolding.test.ts b/packages/cli/src/capture/scaffolding.test.ts index 21016cf08b..2de505915e 100644 --- a/packages/cli/src/capture/scaffolding.test.ts +++ b/packages/cli/src/capture/scaffolding.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import * as fs from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { generateProjectScaffold } from "./scaffolding.js"; +import { generateProjectScaffold, loadEnvFile } from "./scaffolding.js"; import type { DesignTokens } from "./types.js"; vi.mock("node:fs", async (importOriginal) => { @@ -123,3 +123,26 @@ describe("generateProjectScaffold metadata", () => { }, ); }); + +describe("loadEnvFile", () => { + const keys = ["hyperframes_feedback_email", "HyperFrames_Feedback_Email", "HF_SCAFFOLD_TEST_KEY"]; + let dir: string; + + beforeEach(() => { + dir = fs.mkdtempSync(join(tmpdir(), "hf-scaffold-env-")); + }); + + afterEach(() => { + for (const key of keys) delete process.env[key]; + fs.rmSync(dir, { recursive: true, force: true }); + }); + + it("never takes the feedback email from a project file, in any letter case", () => { + fs.writeFileSync( + join(dir, ".env"), + 'hyperframes_feedback_email=a@example.com\nHyperFrames_Feedback_Email=b@example.com\nHF_SCAFFOLD_TEST_KEY="kept"\n', + ); + loadEnvFile(join(dir, "out")); + expect(keys.map((key) => process.env[key])).toEqual([undefined, undefined, "kept"]); + }); +}); diff --git a/packages/cli/src/capture/scaffolding.ts b/packages/cli/src/capture/scaffolding.ts index e38a078e8c..1d8e70a487 100644 --- a/packages/cli/src/capture/scaffolding.ts +++ b/packages/cli/src/capture/scaffolding.ts @@ -7,6 +7,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; +import { applyDotEnv } from "../utils/dotEnv.js"; import { writeNewFileSync } from "../utils/writeNewFile.js"; import type { CatalogedAsset } from "./assetCataloger.js"; import type { CaptureResult, DesignTokens } from "./types.js"; @@ -21,19 +22,7 @@ export function loadEnvFile(startDir: string): void { for (let i = 0; i < 5; i++) { const envPath = resolve(dir, ".env"); try { - const envContent = readFileSync(envPath, "utf-8"); - for (const line of envContent.split("\n")) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) continue; - const eq = trimmed.indexOf("="); - if (eq === -1) continue; - const key = trimmed.slice(0, eq).trim(); - const val = trimmed - .slice(eq + 1) - .trim() - .replace(/^["']|["']$/g, ""); - if (!process.env[key]) process.env[key] = val; - } + applyDotEnv(readFileSync(envPath, "utf-8"), process.env); break; } catch { dir = resolve(dir, "..");