From 1ede37281f7c32362f11b4074209b2a49120db4a Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Tue, 16 Jun 2026 17:56:32 -0700 Subject: [PATCH] Don't ship env-var conflict-throw in generated customer SDK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated env.ts getters ship inside @hexclave/stack / @hexclave/react and are read on a hot, side-effect-free path by SDK consumers. The conflict-detection helper added with the HEXCLAVE_* rename made those getters throw when both a HEXCLAVE_* and STACK_* spelling were set to different values — a breaking change to env-var reading for SDK users. Revert the generated getters to a plain || dual-read chain (prefer HEXCLAVE_*, fall back to legacy STACK_*, empty-as-unset), with no throw. Conflict detection stays in our own non-shipped infra only (packages/shared getEnvVariable/ getProcessEnv, dashboard inline env, CLI auth, docker entrypoint). Order-preserving dedup of the candidate list is kept so HEXCLAVE_API_URL no longer emits its STACK_URL aliases twice. --- packages/template/scripts/generate-env.ts | 80 ++++------------------- 1 file changed, 14 insertions(+), 66 deletions(-) diff --git a/packages/template/scripts/generate-env.ts b/packages/template/scripts/generate-env.ts index 717452d79..1b3058495 100644 --- a/packages/template/scripts/generate-env.ts +++ b/packages/template/scripts/generate-env.ts @@ -63,76 +63,31 @@ const envVarsConfig: Record(); - const snippets: string[] = []; - - for (const variableName of allVariables) { - if (emittedVariables.has(variableName)) { - continue; - } - - const stackName = getStackEnvVarName(variableName); - if (stackName != null && allVariablesSet.has(stackName)) { - emittedVariables.add(variableName); - emittedVariables.add(stackName); - snippets.push(deindent` - resolveHexclaveStackEnvVar("${variableName}", "${stackName}", ${getEnvVarSnippet(variableName)}, ${getEnvVarSnippet(stackName)}) - `); - continue; - } - - const hexclaveName = getHexclaveEnvVarName(variableName); - if (hexclaveName != null && allVariablesSet.has(hexclaveName)) { - emittedVariables.add(hexclaveName); - emittedVariables.add(variableName); - snippets.push(deindent` - resolveHexclaveStackEnvVar("${hexclaveName}", "${variableName}", ${getEnvVarSnippet(hexclaveName)}, ${getEnvVarSnippet(variableName)}) - `); - continue; - } - - emittedVariables.add(variableName); - snippets.push(getEnvVarSnippet(variableName)); - } - - return snippets; -} - function generateEnvVarsConstSnippet() { const getters: string[] = []; for (const [key, config] of Object.entries(envVarsConfig)) { - const allVariables = [key, ...(config.deprecatedLegacyNames ?? [])] - .flatMap(k => k.startsWith("HEXCLAVE_") ? [k, k.replace("HEXCLAVE_", "STACK_")] : [k]) - .flatMap(k => config.allowPublic ? [k, `NEXT_PUBLIC_${k}`, `VITE_${k}`] : [k]); - const candidateSnippets = getEnvVarCandidateSnippets(allVariables); - // Use || (not ??) between candidates: empty-string env vars (e.g. the empty - // HEXCLAVE_* placeholders in checked-in .env templates) must not shadow a - // real value under a legacy STACK_* name further down the chain. + const allVariables = [...new Set( + [key, ...(config.deprecatedLegacyNames ?? [])] + .flatMap(k => k.startsWith("HEXCLAVE_") ? [k, k.replace("HEXCLAVE_", "STACK_")] : [k]) + .flatMap(k => config.allowPublic ? [k, `NEXT_PUBLIC_${k}`, `VITE_${k}`] : [k]) + )]; + // Prefer the canonical HEXCLAVE_* spelling, falling back to the legacy STACK_* + // name. Use || (not ??) between candidates so an empty-string env var (e.g. an + // empty HEXCLAVE_* placeholder) can't shadow a real value further down the chain. + // + // IMPORTANT: this getter ships in the customer SDK, so it must NOT throw when + // both spellings are set — reading an env var is a hot, side-effect-free path + // for SDK consumers. Conflict detection lives only in our own (non-shipped) + // env helpers (packages/shared, dashboard inline env, CLI), never here. getters.push(deindent` get ${key}() { - return ${candidateSnippets.join("\n || ")} || undefined; + return ${allVariables.map(getEnvVarSnippet).join("\n || ")} || undefined; }, `); } @@ -141,13 +96,6 @@ function generateEnvVarsConstSnippet() { // DO NOT EDIT IT BY HAND. /* eslint-disable no-restricted-properties */ - function resolveHexclaveStackEnvVar(hexclaveName: string, stackName: string, hexclaveValue: string | undefined, stackValue: string | undefined): string | undefined { - if (hexclaveValue && stackValue && hexclaveValue !== stackValue) { - throw new Error(\`Environment variables \${hexclaveName} and \${stackName} are both set to different values. Remove one of them or set them to the same value.\`); - } - return hexclaveValue || stackValue || undefined; - } - export const envVars = { ${getters.join("\n")} };