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 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 17 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 18 | import { ChildProcess } from "effect/unstable/process"; | 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 | const BASE_SERVER_PORT = 13773; | 24 | const BASE_SERVER_PORT = 13773; |
| 21 | const BASE_WEB_PORT = 5733; | 25 | const BASE_WEB_PORT = 5733; |
| 22 | const MAX_HASH_OFFSET = 3000; | 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 | } | ||