mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Summary: Detects conflicting non-empty HEXCLAVE_* and STACK_* values
across shared env helpers, dashboard public envs, generated SDK env
access, Docker scripts, CLI/docs/examples, and related tests.
Verification: pnpm test run packages/shared/src/utils/env.test.tsx
apps/dashboard/src/lib/env.test.tsx packages/cli/src/lib/auth.test.ts;
targeted lint/typecheck across touched workspaces; bash -n/node --check
for changed scripts; node
docker/local-emulator/generate-env-development.mjs --check.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Detects and blocks conflicting `HEXCLAVE_*` and `STACK_*` env vars
across the monorepo. Prefers `HEXCLAVE_*`, falls back to `STACK_*` when
empty, and fails fast when both are set to different values.
- **New Features**
- Added conflict-aware env resolvers used across apps, CLI, docs,
examples, and Docker (build/runtime).
- Validates critical vars (e.g., database connection, API/dashboard
URLs, emulator flags, tokens) and ignores post-build sentinel values.
- Prisma, Next.js, and Docker startup now error on mismatched values;
CLI enforces project ID/key conflicts; tests added.
- **Migration**
- If both names are set with different values, builds/tests/scripts will
error. Set only `HEXCLAVE_*` or make both equal.
- Update `.env`, CI secrets, and Docker envs to use `HEXCLAVE_*`. Keep
`STACK_*` only as a temporary fallback.
<sup>Written for commit 4d63fa3bad.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1604?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
158 lines
4.9 KiB
TypeScript
158 lines
4.9 KiB
TypeScript
import { writeFileSyncIfChanged } from "@hexclave/shared/dist/utils/fs";
|
|
import { deindent } from "@hexclave/shared/dist/utils/strings";
|
|
|
|
const envVarsConfig: Record<string, { allowPublic?: boolean, deprecatedLegacyNames?: string[] }> = {
|
|
HEXCLAVE_PORT_PREFIX: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_PROJECT_ID: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_PUBLISHABLE_CLIENT_KEY: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_SECRET_SERVER_KEY: {},
|
|
HEXCLAVE_SUPER_SECRET_ADMIN_KEY: {},
|
|
HEXCLAVE_EXTRA_REQUEST_HEADERS: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_API_URL_BROWSER: {
|
|
allowPublic: true,
|
|
deprecatedLegacyNames: ["BROWSER_STACK_API_URL", "BROWSER_HEXCLAVE_API_URL"],
|
|
},
|
|
HEXCLAVE_API_URL_SERVER: {
|
|
allowPublic: true,
|
|
deprecatedLegacyNames: ["SERVER_STACK_API_URL", "SERVER_HEXCLAVE_API_URL"],
|
|
},
|
|
HEXCLAVE_API_URL: {
|
|
allowPublic: true,
|
|
deprecatedLegacyNames: ["HEXCLAVE_URL", "STACK_URL"],
|
|
},
|
|
HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_HOSTED_HANDLER_URL_TEMPLATE: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_STRIPE_PUBLISHABLE_KEY: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_BOT_CHALLENGE_SITE_KEY: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_BOT_CHALLENGE_INVISIBLE_SITE_KEY: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_IS_LOCAL_EMULATOR: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_POSTHOG_KEY: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_SVIX_SERVER_URL: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_SENTRY_DSN: {
|
|
allowPublic: true,
|
|
},
|
|
HEXCLAVE_VERSION_ALERTER_SEVERE_ONLY: {
|
|
allowPublic: true,
|
|
},
|
|
NODE_ENV: {
|
|
allowPublic: false,
|
|
},
|
|
};
|
|
|
|
function getHexclaveEnvVarName(name: string): string | undefined {
|
|
if (!name.includes("STACK_")) {
|
|
return undefined;
|
|
}
|
|
return name.replace("STACK_", "HEXCLAVE_");
|
|
}
|
|
|
|
function getStackEnvVarName(name: string): string | undefined {
|
|
if (!name.includes("HEXCLAVE_")) {
|
|
return undefined;
|
|
}
|
|
return name.replace("HEXCLAVE_", "STACK_");
|
|
}
|
|
|
|
function getEnvVarSnippet(variableName: string) {
|
|
return deindent`
|
|
((typeof process !== "undefined" ? process.env.${variableName} : undefined) || import.meta.env?.${variableName})
|
|
`;
|
|
}
|
|
|
|
function getEnvVarCandidateSnippets(allVariables: string[]) {
|
|
const allVariablesSet = new Set(allVariables);
|
|
const emittedVariables = new Set<string>();
|
|
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.
|
|
getters.push(deindent`
|
|
get ${key}() {
|
|
return ${candidateSnippets.join("\n || ")} || undefined;
|
|
},
|
|
`);
|
|
}
|
|
return deindent`
|
|
// THIS FILE IS AUTO-GENERATED BY THE \`generate-env.ts\` SCRIPT.
|
|
// 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")}
|
|
};
|
|
` + "\n";
|
|
}
|
|
|
|
writeFileSyncIfChanged("src/generated/env.ts", generateEnvVarsConstSnippet());
|