Consolidate env at repo root

- Move all application env vars to repository-root .env (with .env.example
  as the committed template) and drop the per-app .env.example files.
- Add @code/env subpaths (agent, convex) and consume validated exports
  everywhere instead of reading process.env directly.
- Wire every dev/build/start script to load ../../.env via bun --env-file
  and point vite.config.ts envDir at the repo root.
This commit is contained in:
-Puter
2026-07-19 17:29:11 +05:30
parent 87ddc2b73a
commit 6de7c3065e
15 changed files with 100 additions and 40 deletions

32
.env.example Normal file
View File

@@ -0,0 +1,32 @@
# Shared application endpoints
CONVEX_DEPLOYMENT=dev:example-deployment
CONVEX_URL=https://example.convex.cloud
CONVEX_SITE_URL=https://example.convex.site
SITE_URL=http://localhost:5173
NATIVE_APP_URL=code://
# Browser and native public endpoints
VITE_CONVEX_URL=https://example.convex.cloud
VITE_CONVEX_SITE_URL=https://example.convex.site
EXPO_PUBLIC_CONVEX_URL=https://example.convex.cloud
EXPO_PUBLIC_CONVEX_SITE_URL=https://example.convex.site
# Daemon identity and control plane
DAEMON_ID=local-macbook
DAEMON_NAME=Local MacBook
DAEMON_VERSION=0.0.0
DAEMON_HEARTBEAT_MS=15000
DAEMON_COMMAND_LEASE_MS=60000
# RIVET_ENDPOINT=http://localhost:6420
# Flue persistence adapter
FLUE_DB_TOKEN=replace-with-a-long-random-token
# Agent model provider
AGENT_MODEL_PROVIDER=cheaptricks
AGENT_MODEL_NAME=minimax-m3
AGENT_MODEL_API=openai-completions
AGENT_MODEL_BASE_URL=https://ai.example.com/v1
AGENT_MODEL_API_KEY=replace-with-provider-api-key
AGENT_MODEL_CONTEXT_WINDOW=262000
AGENT_MODEL_MAX_TOKENS=131072

3
.gitignore vendored
View File

@@ -11,9 +11,10 @@ build
# Generated files # Generated files
apps/web/src/routeTree.gen.ts apps/web/src/routeTree.gen.ts
# Environment variables # Environment variables live at the repository root
.env .env
.env*.local .env*.local
!.env.example
# IDEs and editors # IDEs and editors
.vscode/* .vscode/*

View File

@@ -1,7 +0,0 @@
CONVEX_URL=https://example.convex.cloud
DAEMON_ID=local-macbook
DAEMON_NAME=Local MacBook
DAEMON_VERSION=0.0.0
DAEMON_HEARTBEAT_MS=15000
DAEMON_COMMAND_LEASE_MS=60000
# RIVET_ENDPOINT=http://localhost:6420

View File

@@ -5,9 +5,9 @@
"type": "module", "type": "module",
"module": "src/index.ts", "module": "src/index.ts",
"scripts": { "scripts": {
"dev": "bun run --watch src/index.ts", "dev": "bun --env-file=../../.env run --watch src/index.ts",
"build": "bun build src/index.ts --compile --outfile dist/code-daemon", "build": "bun --env-file=../../.env build src/index.ts --compile --outfile dist/code-daemon",
"start": "./dist/code-daemon", "start": "bun --env-file=../../.env ./dist/code-daemon",
"check-types": "tsc --noEmit" "check-types": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {

View File

@@ -4,12 +4,12 @@
"private": true, "private": true,
"main": "expo-router/entry", "main": "expo-router/entry",
"scripts": { "scripts": {
"start": "expo start", "start": "bun --env-file=../../.env expo start",
"dev": "expo start --clear", "dev": "bun --env-file=../../.env expo start --clear",
"android": "expo run:android", "android": "bun --env-file=../../.env expo run:android",
"ios": "expo run:ios", "ios": "bun --env-file=../../.env expo run:ios",
"prebuild": "expo prebuild", "prebuild": "bun --env-file=../../.env expo prebuild",
"web": "expo start --web" "web": "bun --env-file=../../.env expo start --web"
}, },
"dependencies": { "dependencies": {
"@better-auth/expo": "catalog:", "@better-auth/expo": "catalog:",
@@ -22,7 +22,6 @@
"@tanstack/react-form": "catalog:", "@tanstack/react-form": "catalog:",
"better-auth": "catalog:", "better-auth": "catalog:",
"convex": "catalog:", "convex": "catalog:",
"dotenv": "catalog:",
"expo": "~57.0.1", "expo": "~57.0.1",
"expo-constants": "~57.0.2", "expo-constants": "~57.0.2",
"expo-font": "~57.0.0", "expo-font": "~57.0.0",

View File

@@ -19,7 +19,6 @@
"@tanstack/react-form": "catalog:", "@tanstack/react-form": "catalog:",
"better-auth": "catalog:", "better-auth": "catalog:",
"convex": "catalog:", "convex": "catalog:",
"dotenv": "catalog:",
"isbot": "^5.1.44", "isbot": "^5.1.44",
"lucide-react": "catalog:", "lucide-react": "catalog:",
"next-themes": "catalog:", "next-themes": "catalog:",

View File

@@ -1,8 +1,11 @@
import path from "node:path";
import { reactRouter } from "@react-router/dev/vite"; import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite-plus"; import { defineConfig } from "vite-plus";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
envDir: path.resolve(import.meta.dirname, "../.."),
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
}); });

View File

@@ -4,11 +4,6 @@
"workspaces": { "workspaces": {
"": { "": {
"name": "code", "name": "code",
"dependencies": {
"@code/env": "workspace:*",
"dotenv": "catalog:",
"zod": "catalog:",
},
"devDependencies": { "devDependencies": {
"@code/config": "workspace:*", "@code/config": "workspace:*",
"@types/bun": "latest", "@types/bun": "latest",
@@ -67,7 +62,6 @@
"@tanstack/react-form": "catalog:", "@tanstack/react-form": "catalog:",
"better-auth": "catalog:", "better-auth": "catalog:",
"convex": "catalog:", "convex": "catalog:",
"dotenv": "catalog:",
"expo": "~57.0.1", "expo": "~57.0.1",
"expo-constants": "~57.0.2", "expo-constants": "~57.0.2",
"expo-font": "~57.0.0", "expo-font": "~57.0.0",
@@ -130,7 +124,6 @@
"@tanstack/react-form": "catalog:", "@tanstack/react-form": "catalog:",
"better-auth": "catalog:", "better-auth": "catalog:",
"convex": "catalog:", "convex": "catalog:",
"dotenv": "catalog:",
"isbot": "^5.1.44", "isbot": "^5.1.44",
"lucide-react": "catalog:", "lucide-react": "catalog:",
"next-themes": "catalog:", "next-themes": "catalog:",
@@ -158,7 +151,10 @@
"name": "@code/agents", "name": "@code/agents",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@code/env": "workspace:*",
"@flue/runtime": "latest", "@flue/runtime": "latest",
"convex": "catalog:",
"hono": "^4.12.30",
}, },
"devDependencies": { "devDependencies": {
"@code/config": "workspace:*", "@code/config": "workspace:*",
@@ -171,10 +167,10 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@better-auth/expo": "catalog:", "@better-auth/expo": "catalog:",
"@code/env": "workspace:*",
"@convex-dev/better-auth": "catalog:", "@convex-dev/better-auth": "catalog:",
"better-auth": "catalog:", "better-auth": "catalog:",
"convex": "catalog:", "convex": "catalog:",
"dotenv": "catalog:",
"zod": "catalog:", "zod": "catalog:",
}, },
"devDependencies": { "devDependencies": {
@@ -192,7 +188,6 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@t3-oss/env-core": "^0.13.11", "@t3-oss/env-core": "^0.13.11",
"dotenv": "catalog:",
"zod": "catalog:", "zod": "catalog:",
}, },
"devDependencies": { "devDependencies": {

View File

@@ -34,6 +34,7 @@
"hooks:setup": "vp config", "hooks:setup": "vp config",
"dev:native": "vp run --filter native dev", "dev:native": "vp run --filter native dev",
"dev:web": "vp run --filter web dev", "dev:web": "vp run --filter web dev",
"dev:agents": "vp run --filter @code/agents dev",
"dev:desktop": "vp run --filter desktop dev:hmr", "dev:desktop": "vp run --filter desktop dev:hmr",
"build:desktop": "vp run --filter desktop build:stable", "build:desktop": "vp run --filter desktop build:stable",
"build:desktop:canary": "vp run --filter desktop build:canary", "build:desktop:canary": "vp run --filter desktop build:canary",
@@ -42,15 +43,12 @@
"dev:setup": "vp run --filter @code/backend dev:setup", "dev:setup": "vp run --filter @code/backend dev:setup",
"dev:daemon": "vp run --filter daemon dev", "dev:daemon": "vp run --filter daemon dev",
"build:daemon": "vp run --filter daemon build", "build:daemon": "vp run --filter daemon build",
"build:agents": "vp run --filter @code/agents build",
"docs:update": "bun run scripts/update-docs.ts", "docs:update": "bun run scripts/update-docs.ts",
"subtree": "bun run scripts/subtree.ts", "subtree": "bun run scripts/subtree.ts",
"fix": "ultracite fix" "fix": "ultracite fix"
}, },
"dependencies": { "dependencies": {},
"@code/env": "workspace:*",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": { "devDependencies": {
"@code/config": "workspace:*", "@code/config": "workspace:*",
"@types/bun": "latest", "@types/bun": "latest",

View File

@@ -4,13 +4,14 @@
"private": true, "private": true,
"type": "module", "type": "module",
"exports": { "exports": {
"./web": "./src/web.ts", "./agent": "./src/agent.ts",
"./convex": "./src/convex.ts",
"./native": "./src/native.ts", "./native": "./src/native.ts",
"./server": "./src/server.ts" "./server": "./src/server.ts",
"./web": "./src/web.ts"
}, },
"dependencies": { "dependencies": {
"@t3-oss/env-core": "^0.13.11", "@t3-oss/env-core": "^0.13.11",
"dotenv": "catalog:",
"zod": "catalog:" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {

17
packages/env/src/agent.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
import { z } from "zod";
const agentEnvSchema = z.object({
AGENT_MODEL_API: z.literal("openai-completions"),
AGENT_MODEL_API_KEY: z.string().min(1),
AGENT_MODEL_BASE_URL: z.url(),
AGENT_MODEL_CONTEXT_WINDOW: z.coerce.number().int().positive(),
AGENT_MODEL_MAX_TOKENS: z.coerce.number().int().positive(),
AGENT_MODEL_NAME: z.string().min(1),
AGENT_MODEL_PROVIDER: z.string().min(1),
});
export type AgentEnv = z.infer<typeof agentEnvSchema>;
export const parseAgentEnv = (
runtimeEnv: Record<string, string | undefined>
): AgentEnv => agentEnvSchema.parse(runtimeEnv);

13
packages/env/src/convex.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
emptyStringAsUndefined: true,
runtimeEnv: process.env,
server: {
CONVEX_SITE_URL: z.url(),
FLUE_DB_TOKEN: z.string().min(1),
NATIVE_APP_URL: z.string().min(1).default("code://"),
SITE_URL: z.url(),
},
});

View File

@@ -14,6 +14,7 @@ export const env = createEnv({
DAEMON_VERSION: z.string().default("0.0.0"), DAEMON_VERSION: z.string().default("0.0.0"),
DAEMON_HEARTBEAT_MS: z.coerce.number().int().positive().default(15_000), DAEMON_HEARTBEAT_MS: z.coerce.number().int().positive().default(15_000),
DAEMON_COMMAND_LEASE_MS: z.coerce.number().int().positive().default(60_000), DAEMON_COMMAND_LEASE_MS: z.coerce.number().int().positive().default(60_000),
FLUE_DB_TOKEN: z.string().min(1),
RIVET_ENDPOINT: z.url().optional(), RIVET_ENDPOINT: z.url().optional(),
}, },
runtimeEnv: process.env, runtimeEnv: process.env,

9
packages/env/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
interface ImportMetaEnv {
readonly [key: string]: string | number | boolean | undefined;
readonly VITE_CONVEX_SITE_URL: string;
readonly VITE_CONVEX_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -7,12 +7,11 @@ const convexUrlSchema = (exampleHost: string) =>
}); });
export const env = createEnv({ export const env = createEnv({
clientPrefix: "VITE_",
client: { client: {
VITE_CONVEX_URL: convexUrlSchema("example.convex.cloud"),
VITE_CONVEX_SITE_URL: convexUrlSchema("example.convex.site"), VITE_CONVEX_SITE_URL: convexUrlSchema("example.convex.site"),
VITE_CONVEX_URL: convexUrlSchema("example.convex.cloud"),
}, },
runtimeEnv: (import.meta as any).env, clientPrefix: "VITE_",
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
runtimeEnv: import.meta.env,
}); });