- 更新 .env.example、env.ts、vite.config.ts 默认端口 - 同步更新 sidecar.rs Rust 端口常量 - 更新 README、AGENTS.md 等文档中的端口引用
146 lines
3.5 KiB
TypeScript
146 lines
3.5 KiB
TypeScript
/**
|
|
* 环境变量配置模块
|
|
*
|
|
* 使用 @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<string, string> => {
|
|
const result: Record<string, string> = {}
|
|
|
|
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<string, string> => {
|
|
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<string, string | undefined> => {
|
|
const fileEnv = loadEnvFromFile()
|
|
const merged: Record<string, string | undefined> = { ...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,
|
|
})
|