Repository Wiki
deepseek-ai/deepseek-harness

配置目录、环境变量与运行时覆盖

启动配置不仅取决于 cordis.yml:引导层还解析配置路径、读取调用目录与 Harness home 的 .env,并为 Loader 准备用户补丁层。本页聚焦这些入口和环境变量的安全边界。

Purpose and Scope

本文覆盖 @deepseek-ai/dsh-app-boot 中已核实的路径解析、环境变量分层读取与启动时覆盖的边界。配置 schema 的生成、profile 包安装、HMR 的文件监视及 Loader 内部的条目合并应分别参见相应专题;这里不推断它们的未读取实现。引导模块的说明明确提及从 ~/.dsh 取得可选用户补丁、向配置表达式暴露 home 路径解析器,并驱动 Cordis Loader 加载叶级 cordis.yml;下面只对本页读到的实现给出具体调用顺序。启动模块说明。

Overview

这里有两种不同的“覆盖”:一是配置文件名选择,resolveConfigPath 在 snapshot replay 模式下将匹配的 cordis.yml 或 cordis.yaml basename 换成 cordis.snapshot.yml;二是环境值来源,loadLayeredEnv 的接口注释规定继承环境优先于调用目录 .env,后者优先于 Harness-home .env,接受的文件值不得替换继承值。环境文件还受启动专用变量规则约束,不能把改变进程启动、模块加载、网络或信任范围的变量随项目文件带入。参见 路径解析 与 分层环境接口。

Architecture

Loading diagram...

Source: index.ts

图中将旧的单目录 loadEnv 与面向产品 CLI 的 loadLayeredEnv 分开:前者直接调用 Node 的 process.loadEnvFile;后者的已读实现先解析 home 并复制继承环境。readEnvLayer 是文件解析与拒绝危险名称的内部辅助函数;图不暗示未读取的后续调用或合并细节。两个入口、分层入口。

配置路径与文件名

resolveConfigPath(configPath: string, snapshotMode: string | undefined, cwd: string = process.cwd()): string 始终先用 resolve(cwd, configPath) 得到绝对路径。只有 snapshotMode === 'replay' 才对绝对路径的 basename 执行 /cordis\.ya?ml$/ 替换并在原目录重新解析;其他模式原样返回绝对路径。由于匹配的是尾缀而不是对整个 basename 做相等判断,调用者不应把它理解为“仅匹配文件名恰好等于 cordis.yml”;不匹配时替换不会改变文件名。实现。

typescript
1export function resolveConfigPath( 2 configPath: string, snapshotMode: string | undefined, cwd: string = process.cwd(), 3): string { 4 const absolute = resolve(cwd, configPath) 5 if (snapshotMode !== 'replay') return absolute 6 const dir = dirname(absolute) 7 const replayName = basename(absolute).replace(/cordis\.ya?ml$/, 'cordis.snapshot.yml') 8 return resolve(dir, replayName) 9}

Source: index.ts

环境文件的两个入口

loadEnv(binName: string, dir: string = process.cwd(), warn: (line: string) => void = ...): void 是直接加载指定目录 .env 的入口。缺文件 ENOENT 不报警,保留进程环境;其他读取异常交给 warn,默认写标准错误。它没有在此函数内执行 readEnvLayer 的启动变量限制;不要把它与受校验的分层入口混为一谈。实现。

typescript
1export function loadEnv( 2 binName: string, dir: string = process.cwd(), 3 warn: (line: string) => void = line => void process.stderr.write(line), 4): void { 5 try { 6 process.loadEnvFile(resolve(dir, '.env')) 7 } catch (error) { 8 if ((error as NodeJS.ErrnoException | null)?.code !== 'ENOENT') { 9 warn(`${binName}: failed to load .env: ${String(error)}\n`) 10 } 11 // ENOENT (no .env) is fine — rely on the ambient environment. 12 } 13}

Source: index.ts

loadLayeredEnv(binName: string, cwd: string = process.cwd(), warn: (line: string) => void = ...): LaunchEnvironmentSnapshot 的文档规定在应用任何文件值之前检查两个文件,并保留每个值来自哪个层。已读取的函数开头先 resolveDshHome(),随后浅复制 process.env 为 inherited;本次阅读未覆盖函数剩余部分,因此不在这里指定内部具体的写回顺序或 snapshot 字段结构。接口及开头。

typescript
1export function loadLayeredEnv( 2 binName: string, cwd: string = process.cwd(), 3 warn: (line: string) => void = line => void process.stderr.write(line), 4): LaunchEnvironmentSnapshot { 5 const home = resolveDshHome() 6 const inherited = { ...process.env } as Record<string, string>

Source: index.ts

校验先于物化

内部 readEnvLayer 从目录读取 .env,ENOENT 或其他读取失败均返回 undefined;仅后者调用 warn。成功后使用 parseEnv(content) 解析一次,再遍历实际解析出的名称:这一顺序让校验面对的键与随后返回的值相同。若名称属于引导专用集合,抛错而非静默忽略;唯一例外是 home 目录文件中的代理变量。错误文本包含文件路径、变量名与修正建议。解析和校验。

typescript
1 // Parse once so validation and materialization use exactly the same entries. 2 const values = parseEnv(content) as Record<string, string> 3 for (const name of Object.keys(values)) { 4 if (!isBootstrapOnly(name)) continue 5 const proxyName = HOME_LAYER_PROXY_NAMES.has(name.toUpperCase()) 6 if (isHome && proxyName) continue

Source: index.ts

实际顺序与异常分支

Loading diagram...

Source: index.ts

图展示两个已读局部片段:loadLayeredEnv 的入口和 readEnvLayer 的独立分支,并非宣称已核实这两个函数之间的完整调用链。

环境变量策略与配置选项

这里的“选项”是环境名称及函数参数,不是某个独立配置文件的 schema。isBootstrapOnly 将名称先转换为大写,再比较精确名称或前缀;因此大小写不同的拼写也会命中限制。被拒绝的类别包含启动/加载路径(如 PATH、NODE_OPTIONS)、脚本启动钩子、Git 与编辑器选择、网络目标、代理和 TLS/CA 信任设置;完整常量在实现中。精确名称及前缀。

选项 / 名称类型默认或优先级作用与边界
configPathstring无函数默认值相对路径以 cwd 为基准;解析为绝对路径。实现
snapshotModestring | undefined无函数默认值仅精确值 replay 触发配置 basename 替换。实现
cwd / dirstringprocess.cwd()分别作为配置路径解析基准、项目 .env 或单目录 .env 所在目录。路径、环境入口
warn(line: string) => void写入 process.stderr文件不可读且不是 ENOENT 时输出诊断。实现
DSH_SNAPSHOT环境字符串未在已读代码中规定默认值通过调用方传给 resolveConfigPath 的 snapshotMode 影响 replay 解析;不能从 .env 设置 DSH_ 前缀变量。注释及规则、前缀
DSH_、XDG_、DYLD_、BASH_FUNC_名称前缀无分层文件不得设置,必须使用继承环境。规则
HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY精确变量名仅 home .env 可例外项目 .env 不得设置;继承环境可设置。TLS/CA 名称没有此例外。策略

为何给 home 目录的代理变量开口?代码注释指出项目文件可能跟随 clone,而 home 文件属于用户;代理决定请求路由,不能由携带项目的文件改变。相反,CA 与 TLS 配置决定信任范围,所以即使 home 文件也不豁免。设计说明。

API Reference

API参数返回已核实的异常或诊断
resolveConfigPath(configPath: string, snapshotMode: string | undefined, cwd: string = process.cwd()): string要解析的路径、模式、基准目录配置文件绝对路径已读函数内未定义显式异常处理。实现
loadEnv(binName: string, dir: string = process.cwd(), warn: (line: string) => void = ...): void诊断前缀、.env 目录、警告接收器无ENOENT 静默;其他加载失败经 warn 报告。实现
loadLayeredEnv(binName: string, cwd: string = process.cwd(), warn: (line: string) => void = ...): LaunchEnvironmentSnapshot诊断前缀、调用目录、警告接收器记录来源层的环境快照(接口注释)接口说明:两文件任一声明禁止的 bootstrap 名称会抛错;home 代理例外。声明

readEnvLayer 和 isBootstrapOnly 没有 export 声明,是该模块的内部辅助函数,不属于上述导出 API。声明。

Failure Modes, Edge Cases & Operations

  • 缺文件不等于错误:loadEnv 和内部 readEnvLayer 都将 ENOENT 视为可缺省;后者在非 ENOENT 读取错误时也返回 undefined,但先发出警告。直接加载、解析入口。
  • 非法名称导致快速失败:readEnvLayer 抛出的消息说明将名称改为 shell export;代理变量还可迁入 home .env。两层文件在应用前接受检查的语义由 loadLayeredEnv 接口注释给出。错误分支。
  • 目录判别采用解析后路径:home 豁免条件是 resolve(dir) === home;此处没有显示额外的符号链接规范化。比较。
  • 运行中一致性:入口复制当时的 process.env,接口称返回本次运行的快照;所读片段未说明并发修改全局环境时的同步机制,不能据此声称线程安全或实时刷新。接口与复制。
  • 扩展时的边界:若要改变项目可加载的环境名称,需要同时审视 BOOTSTRAP_NAMES、BOOTSTRAP_PREFIXES 和 home 代理例外;只改 loadEnv 不会改变 readEnvLayer 的校验策略。规则与校验、解析。
  • 配置 schema 的生成入口见 config-schema 导出,应由配置 schema 专题介绍。
  • profile 与补丁相关导出见 profile 导出;用户补丁的合并及运行时 Loader 更新属于 profile/配置重载专题。
  • 本页所述启动时环境和路径入口见 引导模块。

Sources

(1 files)