Showing the changes to scripts (3 of 38 files). Show all
Unified · Split
| @@ -17,6 +17,10 @@ import * as Schema from "effect/Schema"; · 16 unmodified lines | ||
| 17 | 17 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 18 | 18 | import { ChildProcess } from "effect/unstable/process"; |
| 19 | 19 | |
| 20 | +import { loadRepoEnv } from "./lib/public-config.ts"; | |
| 21 | + | |
| 22 | +Object.assign(process.env, loadRepoEnv()); | |
| 23 | + | |
| 20 | 24 | const BASE_SERVER_PORT = 13773; |
| 21 | 25 | const BASE_WEB_PORT = 5733; |
| 22 | 26 | const MAX_HASH_OFFSET = 3000; |
| @@ -0,0 +1,74 @@ | ||
| 1 | +// @effect-diagnostics nodeBuiltinImport:off - Tests exercise root env file precedence directly. | |
| 2 | +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; | |
| 3 | +import { tmpdir } from "node:os"; | |
| 4 | +import { join } from "node:path"; | |
| 5 | +import { afterEach, describe, expect, it } from "vitest"; | |
| 6 | + | |
| 7 | +import { DEFAULT_PUBLIC_CONFIG, loadRepoEnv, resolvePublicConfig } from "./public-config.ts"; | |
| 8 | + | |
| 9 | +const temporaryDirectories: string[] = []; | |
| 10 | + | |
| 11 | +afterEach(() => { | |
| 12 | + for (const directory of temporaryDirectories.splice(0)) { | |
| 13 | + rmSync(directory, { recursive: true, force: true }); | |
| 14 | + } | |
| 15 | +}); | |
| 16 | + | |
| 17 | +describe("loadRepoEnv", () => { | |
| 18 | + it("projects checked-in public defaults into Vite and Expo aliases", () => { | |
| 19 | + const env = loadRepoEnv({ baseEnv: {}, repoRoot: makeTemporaryDirectory() }); | |
| 20 | + | |
| 21 | + expect(env.T3CODE_CLERK_PUBLISHABLE_KEY).toBe(DEFAULT_PUBLIC_CONFIG.clerkPublishableKey); | |
| 22 | + expect(env.VITE_CLERK_PUBLISHABLE_KEY).toBe(DEFAULT_PUBLIC_CONFIG.clerkPublishableKey); | |
| 23 | + expect(env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY).toBe(DEFAULT_PUBLIC_CONFIG.clerkPublishableKey); | |
| 24 | + expect(env.T3_RELAY_URL).toBe(DEFAULT_PUBLIC_CONFIG.relayUrl); | |
| 25 | + expect(env.VITE_T3_RELAY_URL).toBe(DEFAULT_PUBLIC_CONFIG.relayUrl); | |
| 26 | + }); | |
| 27 | + | |
| 28 | + it("applies process, root local, root, and checked-in precedence in that order", () => { | |
| 29 | + const repoRoot = makeTemporaryDirectory(); | |
| 30 | + writeFileSync( | |
| 31 | + join(repoRoot, ".env"), | |
| 32 | + "T3CODE_CLERK_PUBLISHABLE_KEY=pk_root\nT3_RELAY_URL=https://root.example.test\n", | |
| 33 | + ); | |
| 34 | + writeFileSync( | |
| 35 | + join(repoRoot, ".env.local"), | |
| 36 | + "T3CODE_CLERK_PUBLISHABLE_KEY=pk_local\nT3_RELAY_URL=https://local.example.test\n", | |
| 37 | + ); | |
| 38 | + | |
| 39 | + expect(loadRepoEnv({ baseEnv: {}, repoRoot }).T3_RELAY_URL).toBe("https://local.example.test"); | |
| 40 | + expect( | |
| 41 | + loadRepoEnv({ | |
| 42 | + baseEnv: { | |
| 43 | + T3CODE_CLERK_PUBLISHABLE_KEY: "pk_ci", | |
| 44 | + T3_RELAY_URL: "https://ci.example.test", | |
| 45 | + }, | |
| 46 | + repoRoot, | |
| 47 | + }), | |
| 48 | + ).toMatchObject({ | |
| 49 | + T3CODE_CLERK_PUBLISHABLE_KEY: "pk_ci", | |
| 50 | + VITE_CLERK_PUBLISHABLE_KEY: "pk_ci", | |
| 51 | + EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: "pk_ci", | |
| 52 | + T3_RELAY_URL: "https://ci.example.test", | |
| 53 | + VITE_T3_RELAY_URL: "https://ci.example.test", | |
| 54 | + }); | |
| 55 | + }); | |
| 56 | + | |
| 57 | + it("accepts legacy framework aliases as root overrides", () => { | |
| 58 | + expect( | |
| 59 | + resolvePublicConfig({ | |
| 60 | + VITE_CLERK_PUBLISHABLE_KEY: "pk_legacy", | |
| 61 | + VITE_T3_RELAY_URL: "https://legacy.example.test", | |
| 62 | + }), | |
| 63 | + ).toEqual({ | |
| 64 | + clerkPublishableKey: "pk_legacy", | |
| 65 | + relayUrl: "https://legacy.example.test", | |
| 66 | + }); | |
| 67 | + }); | |
| 68 | +}); | |
| 69 | + | |
| 70 | +function makeTemporaryDirectory() { | |
| 71 | + const directory = mkdtempSync(join(tmpdir(), "t3code-public-config-")); | |
| 72 | + temporaryDirectories.push(directory); | |
| 73 | + return directory; | |
| 74 | +} | |
| @@ -0,0 +1,78 @@ | ||
| 1 | +// @effect-diagnostics nodeBuiltinImport:off - Build bootstrap reads optional root env files before an Effect runtime exists. | |
| 2 | +import * as NodeFS from "node:fs"; | |
| 3 | +import * as NodePath from "node:path"; | |
| 4 | +import * as NodeURL from "node:url"; | |
| 5 | +import * as NodeUtil from "node:util"; | |
| 6 | + | |
| 7 | +import { DEFAULT_T3_CLERK_PUBLISHABLE_KEY, DEFAULT_T3_RELAY_URL } from "@t3tools/shared/relayAuth"; | |
| 8 | + | |
| 9 | +export interface T3CodePublicConfig { | |
| 10 | + readonly clerkPublishableKey: string; | |
| 11 | + readonly relayUrl: string; | |
| 12 | +} | |
| 13 | + | |
| 14 | +type Environment = Readonly<Record<string, string | undefined>>; | |
| 15 | + | |
| 16 | +const REPO_ROOT = NodePath.dirname( | |
| 17 | + NodePath.dirname(NodePath.dirname(NodeURL.fileURLToPath(import.meta.url))), | |
| 18 | +); | |
| 19 | + | |
| 20 | +// These values are intentionally public. Client builds embed them so a fresh clone can use | |
| 21 | +// T3 Cloud without local setup. Use root .env files or CI environment variables to override them. | |
| 22 | +export const DEFAULT_PUBLIC_CONFIG: T3CodePublicConfig = { | |
| 23 | + clerkPublishableKey: DEFAULT_T3_CLERK_PUBLISHABLE_KEY, | |
| 24 | + relayUrl: DEFAULT_T3_RELAY_URL, | |
| 25 | +}; | |
| 26 | + | |
| 27 | +export function loadRepoEnv({ | |
| 28 | + baseEnv = process.env, | |
| 29 | + repoRoot = REPO_ROOT, | |
| 30 | +}: { | |
| 31 | + readonly baseEnv?: Environment; | |
| 32 | + readonly repoRoot?: string; | |
| 33 | +} = {}): Record<string, string | undefined> { | |
| 34 | + const rootEnv = readEnvFile(NodePath.join(repoRoot, ".env")); | |
| 35 | + const localEnv = readEnvFile(NodePath.join(repoRoot, ".env.local")); | |
| 36 | + const config = resolvePublicConfig(baseEnv, localEnv, rootEnv); | |
| 37 | + | |
| 38 | + return { | |
| 39 | + ...rootEnv, | |
| 40 | + ...localEnv, | |
| 41 | + ...baseEnv, | |
| 42 | + T3CODE_CLERK_PUBLISHABLE_KEY: config.clerkPublishableKey, | |
| 43 | + T3_RELAY_URL: config.relayUrl, | |
| 44 | + VITE_CLERK_PUBLISHABLE_KEY: config.clerkPublishableKey, | |
| 45 | + VITE_T3_RELAY_URL: config.relayUrl, | |
| 46 | + EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: config.clerkPublishableKey, | |
| 47 | + }; | |
| 48 | +} | |
| 49 | + | |
| 50 | +export function resolvePublicConfig(...sources: readonly Environment[]): T3CodePublicConfig { | |
| 51 | + return { | |
| 52 | + clerkPublishableKey: | |
| 53 | + firstNonEmpty( | |
| 54 | + sources, | |
| 55 | + "T3CODE_CLERK_PUBLISHABLE_KEY", | |
| 56 | + "VITE_CLERK_PUBLISHABLE_KEY", | |
| 57 | + "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", | |
| 58 | + ) ?? DEFAULT_PUBLIC_CONFIG.clerkPublishableKey, | |
| 59 | + relayUrl: | |
| 60 | + firstNonEmpty(sources, "T3_RELAY_URL", "VITE_T3_RELAY_URL") ?? DEFAULT_PUBLIC_CONFIG.relayUrl, | |
| 61 | + }; | |
| 62 | +} | |
| 63 | + | |
| 64 | +function firstNonEmpty(sources: readonly Environment[], ...names: readonly string[]) { | |
| 65 | + for (const source of sources) { | |
| 66 | + for (const name of names) { | |
| 67 | + const value = source[name]?.trim(); | |
| 68 | + if (value) { | |
| 69 | + return value; | |
| 70 | + } | |
| 71 | + } | |
| 72 | + } | |
| 73 | + return undefined; | |
| 74 | +} | |
| 75 | + | |
| 76 | +function readEnvFile(path: string): Record<string, string | undefined> { | |
| 77 | + return NodeFS.existsSync(path) ? NodeUtil.parseEnv(NodeFS.readFileSync(path, "utf8")) : {}; | |
| 78 | +} | |