/** * 环境变量配置模块 * * 使用 @t3-oss/env-core 进行类型安全的环境变量验证。 * 支持从同目录的 .env 文件加载配置(优先级低于系统环境变量)。 */ import { existsSync, readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { createEnv } from '@t3-oss/env-core' import { z } from 'zod' /** Token 使用量 API 的默认地址 (按模型分组) */ const DEFAULT_TOKEN_USAGE_URL = 'http://10.0.1.1:8318/api/usage/model' /** 服务器端口默认值 */ const DEFAULT_SERVER_PORT = '13098' /** * 判断当前是否为打包后的可执行文件运行环境 * * @returns 是否为打包后的二进制文件 */ const isBundledExec = (): boolean => { const execPath = process.execPath return !execPath.includes('node') && !execPath.includes('bun') } /** * 获取配置文件的基础目录 * * - 打包后的 sidecar: 使用可执行文件所在目录 * - 开发模式: 使用项目根目录 * * @returns 基础目录路径 */ const getBaseDir = (): string => isBundledExec() ? dirname(process.execPath) : process.cwd() /** * 解析 .env 文件内容 * * 支持: * - 空行和 # 开头的注释 * - KEY=value 格式 * - 只设置系统环境变量中不存在的变量 * * @param content - .env 文件内容 * @returns 解析后的环境变量对象 */ const parseEnvContent = (content: string): Record => { const result: Record = {} for (const line of content.split('\n')) { const trimmed = line.trim() // 跳过空行和注释 if (!trimmed || trimmed.startsWith('#')) continue const eqIndex = trimmed.indexOf('=') if (eqIndex <= 0) continue const key = trimmed.slice(0, eqIndex).trim() const value = trimmed.slice(eqIndex + 1).trim() // 只设置系统环境变量中不存在的变量 if (!process.env[key]) { result[key] = value } } return result } /** * 从同目录的 .env 配置文件读取环境变量 * * 优先级: * 1. 系统环境变量 (process.env) * 2. 可执行文件同目录的 .env 文件 * 3. 默认值 * * @returns 从文件解析的环境变量 */ const loadEnvFromFile = (): Record => { const envPath = join(getBaseDir(), '.env') if (!existsSync(envPath)) return {} try { const content = readFileSync(envPath, 'utf-8') return parseEnvContent(content) } catch { // 忽略读取错误(权限问题等) return {} } } /** * 构建合并后的环境变量对象 * * 合并顺序: process.env > fileEnv > 默认值 */ const buildMergedEnv = (): Record => { const fileEnv = loadEnvFromFile() const merged: Record = { ...process.env } // 从文件填充缺失的变量 for (const [key, value] of Object.entries(fileEnv)) { if (!merged[key]) { merged[key] = value } } // 设置默认值 merged.TOKEN_USAGE_URL ??= DEFAULT_TOKEN_USAGE_URL merged.PROJECT_SERVER_PORT ??= DEFAULT_SERVER_PORT return merged } /** * 类型安全的环境变量配置 * * 服务端变量: * - TOKEN_USAGE_URL: Token 使用量 API 地址 * * 客户端变量 (VITE_ 前缀): * - VITE_APP_TITLE: 应用标题 (可选) */ export const env = createEnv({ server: { TOKEN_USAGE_URL: z.string().url(), PROJECT_SERVER_PORT: z.coerce .number() .int() .min(1) .max(65535) .default(13098), }, clientPrefix: 'VITE_', client: { VITE_APP_TITLE: z.string().min(1).optional(), }, runtimeEnv: buildMergedEnv(), emptyStringAsUndefined: true, })