fix: address review findings for the HEXCLAVE_* env rename

- e2e helpers: also expand the port-prefix placeholder in HEXCLAVE_*/
  NEXT_PUBLIC_HEXCLAVE_* vars (the renamed .env.development keys no longer
  matched the STACK_-only prefix filter, leaving literal ${...} in every URL).
- docker/local-emulator/generate-env-development.mjs: read source keys under
  the canonical HEXCLAVE_* name with STACK_* fallback and emit canonical keys
  (the exact-name lookups threw after the source env files were renamed).
- prisma.config.ts: resolve the datasource URL from
  HEXCLAVE_DATABASE_CONNECTION_STRING with legacy fallback (Prisma's env()
  helper only knew the legacy name); same for the psql-inner script.
- backend vitest: accept both env prefixes and dual-read the DB connection
  string in the auto-migration tests.
- getProcessEnv: empty-as-unset fallback (||), consistent with getEnvVariable —
  an empty HEXCLAVE_* template placeholder must not shadow a real legacy value.
- errors.tsx debugger flag and dashboard next.config emulator flag: dual-read
  the canonical name.
- Vite examples and docs snippets: VITE_STACK_* → VITE_HEXCLAVE_* (the old
  names were dead after their .env.development files were renamed).
This commit is contained in:
Bilal Godil 2026-06-11 16:47:19 -07:00
parent e55938308d
commit f87c9c92c1
16 changed files with 44 additions and 34 deletions

View File

@ -27,7 +27,7 @@
"codegen-route-info:watch": "pnpm run with-env tsx watch --clear-screen=false scripts/generate-route-info.ts",
"codegen": "pnpm run with-env pnpm run generate-migration-imports && pnpm run with-env bash -c 'if [ \"$STACK_ACCELERATE_ENABLED\" = \"true\" ]; then pnpm run prisma generate --no-engine; else pnpm run codegen-prisma; fi' && pnpm run generate-private-sign-up-risk-engine && pnpm run codegen-docs && pnpm run codegen-route-info",
"codegen:watch": "pnpm run generate-private-sign-up-risk-engine && concurrently -n \"prisma,private-risk-engine,docs,route-info,migration-imports\" -k \"pnpm run codegen-prisma:watch\" \"pnpm run generate-private-sign-up-risk-engine:watch\" \"pnpm run codegen-docs:watch\" \"pnpm run codegen-route-info:watch\" \"pnpm run generate-migration-imports:watch\"",
"psql-inner": "psql $(echo $STACK_DATABASE_CONNECTION_STRING | sed 's/\\?.*$//')",
"psql-inner": "psql $(echo ${HEXCLAVE_DATABASE_CONNECTION_STRING:-$STACK_DATABASE_CONNECTION_STRING} | sed 's/\\?.*$//')",
"clickhouse": "pnpm run with-env clickhouse-client --host localhost --port ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}37 --user stackframe --password PASSWORD-PLACEHOLDER--9gKyMxJeMx",
"psql": "pnpm run with-env:dev pnpm run psql-inner",
"prisma-studio": "pnpm run with-env:dev prisma studio --port ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}06 --browser none",

View File

@ -8,7 +8,10 @@ export default defineConfig({
seed: 'pnpm run db-seed-script',
},
datasource: {
url: env('STACK_DATABASE_CONNECTION_STRING'),
// Hexclave rebrand: prefer the canonical name, fall back to the legacy one
// (empty counts as unset — the checked-in .env templates define empty placeholders).
// eslint-disable-next-line no-restricted-properties
url: env(process.env.HEXCLAVE_DATABASE_CONNECTION_STRING ? 'HEXCLAVE_DATABASE_CONNECTION_STRING' : 'STACK_DATABASE_CONNECTION_STRING'),
},
experimental: {
externalTables: true,

View File

@ -8,9 +8,9 @@ const TEST_DB_PREFIX = 'stack_auth_test_db';
const getTestDbURL = (testDbName: string) => {
// @ts-ignore - ImportMeta.env is provided by Vite
const base = import.meta.env.STACK_DATABASE_CONNECTION_STRING.replace(/\/[^/]*$/, '');
const base = (import.meta.env.HEXCLAVE_DATABASE_CONNECTION_STRING || import.meta.env.STACK_DATABASE_CONNECTION_STRING).replace(/\/[^/]*$/, '');
// @ts-ignore - ImportMeta.env is provided by Vite
const query = import.meta.env.STACK_DATABASE_CONNECTION_STRING.split('?')[1] ?? '';
const query = (import.meta.env.HEXCLAVE_DATABASE_CONNECTION_STRING || import.meta.env.STACK_DATABASE_CONNECTION_STRING).split('?')[1] ?? '';
return {
full: `${base}/${testDbName}`,
base,

View File

@ -14,7 +14,7 @@ const TEST_DB_PREFIX = 'stack_migration_test';
const getTestDbURL = (testDbName: string) => {
// @ts-ignore - ImportMeta.env is provided by Vite
const connString: string = import.meta.env.STACK_DATABASE_CONNECTION_STRING;
const connString: string = (import.meta.env.HEXCLAVE_DATABASE_CONNECTION_STRING || import.meta.env.STACK_DATABASE_CONNECTION_STRING);
const base = connString.replace(/\/[^/]*(\?.*)?$/, '');
const query = connString.split('?')[1] ?? '';
return { full: `${base}/${testDbName}`, base, query };

View File

@ -21,6 +21,6 @@ export default mergeConfig(
}
},
envDir: __dirname,
envPrefix: 'STACK_',
envPrefix: ['HEXCLAVE_', 'STACK_'],
})
)

View File

@ -100,7 +100,7 @@ const nextConfig = {
},
async headers() {
const isLocalEmulator = process.env.NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR === "true";
const isLocalEmulator = (process.env.NEXT_PUBLIC_HEXCLAVE_IS_LOCAL_EMULATOR || process.env.NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR) === "true";
return [
{
source: "/(.*)",

View File

@ -299,7 +299,7 @@ function expandHexclavePortPrefix(value?: string | null) {
return prefix ? value.replace(/\$\{NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81\}/g, prefix) : value;
}
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith("STACK_") || key.startsWith("NEXT_PUBLIC_STACK_")) {
if (key.startsWith("STACK_") || key.startsWith("NEXT_PUBLIC_STACK_") || key.startsWith("HEXCLAVE_") || key.startsWith("NEXT_PUBLIC_HEXCLAVE_")) {
const replaced = expandHexclavePortPrefix(value ?? undefined);
if (replaced !== undefined) {
// eslint-disable-next-line no-restricted-syntax

View File

@ -41,23 +41,28 @@ const parseEnvFile = (filePath) => {
const backendEnv = parseEnvFile(backendEnvPath);
const dashboardEnv = parseEnvFile(dashboardEnvPath);
// Hexclave rebrand: the source env files use the canonical HEXCLAVE_* names,
// but accept the legacy STACK_* spelling as a fallback. Emitted keys are
// always canonical.
const toCanonicalKey = (key) => key.includes("STACK_") ? key.replace("STACK_", "HEXCLAVE_") : key;
const getRequiredEnvValue = (sourceName, envMap, key) => {
const value = envMap.get(key);
const value = envMap.get(toCanonicalKey(key)) ?? envMap.get(key);
if (value == null) {
throw new Error(`Missing ${key} in ${sourceName}; update the generator or source env file.`);
throw new Error(`Missing ${toCanonicalKey(key)} in ${sourceName}; update the generator or source env file.`);
}
return value;
};
const fromSource = (sourceName, envMap, key) => ({
type: "entry",
key,
key: toCanonicalKey(key),
value: getRequiredEnvValue(sourceName, envMap, key),
});
const literal = (key, value) => ({
type: "entry",
key,
key: toCanonicalKey(key),
value,
});

View File

@ -80,8 +80,8 @@ STACK_SECRET_SERVER_KEY=<your-secret-server-key>`,
language: 'JavaScript',
framework: 'React',
code: `# Store these in environment variables or directly in the client file during development
VITE_STACK_PROJECT_ID=<your-project-id>
VITE_STACK_PUBLISHABLE_CLIENT_KEY=<your-publishable-client-key>`,
VITE_HEXCLAVE_PROJECT_ID=<your-project-id>
VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY=<your-publishable-client-key>`,
highlightLanguage: 'bash',
filename: '.env'
},
@ -172,8 +172,8 @@ export const stackClientApp = new StackClientApp({
// import { useNavigate } from "@tanstack/react-router"; // TanStack Router
export const stackClientApp = new StackClientApp({
projectId: process.env.VITE_STACK_PROJECT_ID || "your-project-id",
publishableClientKey: process.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY || "your-publishable-client-key",
projectId: process.env.VITE_HEXCLAVE_PROJECT_ID || "your-project-id",
publishableClientKey: process.env.VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY || "your-publishable-client-key",
tokenStore: "cookie",
// redirectMethod: { useNavigate }, // Set this for non-Next.js frameworks
});`,

View File

@ -12,17 +12,17 @@ export const viteExamples = {
declare global {
interface ImportMeta {
env: {
VITE_STACK_API_URL: string;
VITE_STACK_PROJECT_ID: string;
VITE_STACK_PUBLISHABLE_CLIENT_KEY: string;
VITE_HEXCLAVE_API_URL: string;
VITE_HEXCLAVE_PROJECT_ID: string;
VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY: string;
};
}
}
export const stackClientApp = new StackClientApp({
baseUrl: import.meta.env.VITE_STACK_API_URL,
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
baseUrl: import.meta.env.VITE_HEXCLAVE_API_URL,
projectId: import.meta.env.VITE_HEXCLAVE_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY,
tokenStore: "cookie",
urls: {
oauthCallback: window.location.origin + "/oauth",

View File

@ -3,9 +3,9 @@
import { StackClientApp } from "@hexclave/js";
export const hexclaveClientApp = new StackClientApp({
baseUrl: import.meta.env.VITE_STACK_API_URL,
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
baseUrl: import.meta.env.VITE_HEXCLAVE_API_URL,
projectId: import.meta.env.VITE_HEXCLAVE_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY,
tokenStore: "cookie",
urls: {
oauthCallback: window.location.origin + "/oauth",

View File

@ -3,9 +3,9 @@ import { useNavigate } from "react-router-dom";
export const hexclaveClientApp = new StackClientApp({
tokenStore: "cookie",
baseUrl: import.meta.env.VITE_STACK_API_URL,
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
baseUrl: import.meta.env.VITE_HEXCLAVE_API_URL,
projectId: import.meta.env.VITE_HEXCLAVE_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY,
redirectMethod: {
useNavigate,
},

View File

@ -4,9 +4,9 @@ import { StackClientApp } from "@hexclave/react";
import { useNavigate } from "react-router-dom";
export const hexclaveClientApp = new StackClientApp({
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
baseUrl: import.meta.env.VITE_STACK_API_URL,
projectId: import.meta.env.VITE_HEXCLAVE_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY,
baseUrl: import.meta.env.VITE_HEXCLAVE_API_URL,
tokenStore: "cookie",
redirectMethod: {
useNavigate,

View File

@ -9,7 +9,7 @@ function replaceHexclavePortPrefix(value: string): string {
}
function getStackApiUrl(): string {
const configured = import.meta.env.VITE_STACK_API_URL as string | undefined;
const configured = import.meta.env.VITE_HEXCLAVE_API_URL as string | undefined;
return configured ? replaceHexclavePortPrefix(configured) : `http://localhost:${getPortPrefix()}02`;
}

View File

@ -114,6 +114,8 @@ export function getProcessEnv(name: string): string | undefined {
return undefined;
}
// Hexclave rebrand: prefer the HEXCLAVE_*-prefixed equivalent, fall back to the STACK_* name.
// Empty counts as unset — the checked-in .env templates define empty HEXCLAVE_* placeholders,
// which must not shadow a real value under the legacy name.
const hexclaveName = getHexclaveEnvVarName(name);
return (hexclaveName ? process.env[hexclaveName] : undefined) ?? process.env[name];
return (hexclaveName ? process.env[hexclaveName] : undefined) || process.env[name];
}

View File

@ -80,7 +80,7 @@ export class HexclaveAssertionError extends Error {
// Use literal dot-form (guarded with `typeof process`) so Next.js / webpack
// DefinePlugin can inline the value at build time. See getProcessEnv in ./env.
if ((typeof process !== "undefined" ? process.env.NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR : undefined) === "true") {
if ((typeof process !== "undefined" ? (process.env.NEXT_PUBLIC_HEXCLAVE_DEBUGGER_ON_ASSERTION_ERROR || process.env.NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR) : undefined) === "true") {
debugger;
}
}