fix(cli): os doctor 按 os serve 的环境载入 objectstack.config.ts (#5397) - #5402
Merged
Conversation
…*` overlay (#5397) #5387 taught doctor to READ serve's `.env*` cascade but applied the overlay to one reader only — the env-derived checks. `loadConfig()` stayed outside it, so both commands still bundled the user's config under two different environments: a config reading `process.env` at top level saw different values, and a config that throws on a missing value landed in the wide config-analysis `catch` and printed "Could not load config for analysis" — #5382's misattribution surviving in the one path #5387 deliberately left alone — while `os serve` booted the same directory. `loadConfig()` now runs under the same `dotenvReading` resolved once at the top of `run()`. A new `withDotenvOverlayAsync` is required rather than cosmetic: a config's top level runs inside `bundleRequire`'s dynamic `import()`, so the synchronous wrapper's `finally` fires while the promise is still pending — overlay applied, then removed before anything reads it. Both wrappers share one apply/revert (dotenv-flow's own `unload()` test), so the policy is not restated. Config-check verdicts that change are the fix, not a side effect: a config that previously failed to load now loads and its checks RUN, so warnings never printed before can appear. A genuinely broken config still warns — the fix must not be a silencer. `environmentSourcesCheck` remains the single place reporting the cascade and now states that these files also reach the config load; sources only, never values, which matters more now that the overlay carries whatever keys the config chose rather than a declared subset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 5, 2026 08:13
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5397
前提核验:成立(在
origin/main98369a8 上重跑,不照抄 issue)#5398 几分钟前才合入,所以第一件事是确认它的零件确实在 main 上、而
loadConfig()确实还光着:真机复现 —— 临时目录,
.env里一个变量,配置文件顶层读它并在缺值时抛错:同一目录、同一份配置,只把那个值改从 shell 传入(即 serve 载入
.env后的等效环境):issue 的事实面逐条成立。注意第二段里那条
Object "account"警告:它不是新增的检查,而是此前整块被跳过、一条都看不到的那组 config 检查终于跑起来了 —— 下面「影响面」一节的实物。
改了什么
1)
loadConfig()套上run()顶部已解析好的同一份dotenvReading。不是第二次
readDotenvFiles():一轮 doctor 只解析一次 cascade,否则Environment files那一行就未必是 config 载入真正看到的那份,两次读取之间还可能分歧。
2)新增
withDotenvOverlayAsync,这是必需而非装饰。 配置文件的顶层跑在bundleRequire的动态
import()里,同步版的finally会在loadConfig()交回 pending promise 的那一刻就把 overlay 摘掉 —— 套上了,又在被读之前摘掉,等于没套,而且不会报错。所以这条不是
注释能担保的,单独钉了一例(见下反向验证 B)。两个 wrapper 共用同一套 apply/revert
(dotenv-flow 自己
unload()的判定:只删掉仍然等于写入值的那些),而不是抄一份 —— 「doctor允许动哪些变量、什么时候放回去」写两遍就是 #5387 花了整篇篇幅反对的那种漂移。
3)文件自述与报告同步更正。 文件头原本写着 overlay 之外「nothing downstream — the config
bundle … silently inherits a different environment」,现在 config bundle 正是第二个窗口,那句话
若留着就是文档说自己没看某处而其实看了 —— 与 #5398 退役 posture 那句自陈是同一件事。
environmentSourcesCheck仍是报告 cascade 的唯一一处,并说明这些文件同样施加于配置载入:来源口径不变,而且这次更吃重:overlay 现在携带的是配置文件想读的任意变量,不再是
DOCTOR_ENV_INPUTS这个声明过的子集,而.env正是密钥的常见住处。变量名无法预先枚举,提供它们的文件可以 —— 有测试钉住报告里既不出现变量名也不出现值。
影响面:判定变化是修复内容,不是副作用
按 PM 裁定(与 #5398 对 D5e 建议同一姿态)如实列出可观察差异,三类:
那一整组 config 检查(spec 版本 / 循环依赖 / 未引用对象 / 孤儿视图 / 仪表盘完整性)开始
运行,因此可能新增此前从未打印过的 warning。上面真机复现里的
⚠ Object "account" is defined but not referenced …就是实物:它在修复前不是「通过」,而是根本没跑。
os serve一致。这是安静的那一半 —— 配置两边都载入得了,只是声明了不同的形状,没有任何warning 会浮现出来,所以它只能靠差分测试证明。
Could not load config for analysis (config checks skipped)照旧触发。套上 cascade 之后仍然载入不了的配置,
os serve同样载入不了 —— 这句话从此归因正确,而不是被消音。一并静默等于用「没有 warning」换掉「归因错误的 warning」,是本单窄读法的失败模式,
单独钉了一例。
overlay 的边界也如实写在代码里:它在配置文件载入期间有效,不常驻整轮运行。doctor 分析的
一切都是模块求值时读出的普通值,所以这覆盖了真实的读取;把环境读取推迟到 lazy getter 的配置
会落在窗口外 —— 那是与同步版一致的克制(doctor 绝不给后面跑的东西留下一个合并过的环境),
不是疏漏。
测试
新增
packages/cli/src/commands/doctor-config-env-overlay.test.ts(10 例):withDotenvOverlayAsync跨 await 仍可见 / 之后摘除 / shell 优先 / 回调 reject 时仍摘除(配置抛错正是本单的主线路径,
不是边角)/ 不误删回调改写的值;报告只报来源不报值、且说明配置载入也用这份 cascade;以及三例
进程内跑真实命令的端到端差分,覆盖上节三类影响。端到端用例按仓库约定显式
}, 60_000),并在临时 cwd 里预置
node_modules/(否则Dependencies自己就 error+exit 1,断言会因为无关原因通过—— PR #5390 写下、#5398 继承的那个坑)。
反向验证(两个方向,都在跑之前就写下了预测)。
方向 A —— 把调用点还原成裸
await loadConfig()(即 #5397 之前的状态)。预测:两例依赖「配置看得见.env」的端到端用例转红;「配置确实坏掉」那例保持绿(它从来不依赖 overlay);helper 单元测试保持绿(它们直接测 helper,不经过调用点)。实跑:
红的方式正是缺陷本身,红的范围也与预测逐条一致。
方向 B —— 保留 overlay 但把调用点换成同步的
withDotenvOverlay。预测:同样那两例转红,而且输出与「完全不套 overlay」别无二致 —— 这正是这个变体必须存在的理由:写错了不会报错,只会
静默地套上一个没人读的 overlay。实跑,同样两例、同样两条断言转红,输出逐字同 A。
整包(桩已撤除,
flock串行 +--maxWorkers=2):(#5398 合入时是 75 files / 705 tests,新增即本 PR 的 1 文件 10 例。)
另跑:
check:nul-bytesOK(扫描 5420 个文件)、check:role-wordOK、check:error-code-casingOK、check:wildcard-fallthroughOK、check:adr-anchorsOK、eslint 干净;控制字符自查(
grep -naP扫\x00-\x08\x0b\x0c\x0e-\x1f)三个改动文件均无裸控制字节。DOCTOR_ENV_INPUTS漂移守卫仍绿 —— 本 PR 未在doctor.ts里新增任何OS_变量名(注释里也刻意不写具体变量名,否则守卫会把它当成新的 env 输入)。
未做的事
serve.ts/dev.ts/start.ts/env.ts(派发口径的禁区),也没有改content/docs/releases/。Could not load config for analysis这句文案本身:套上 cascade 之后它已经归因正确,改写文案属于另一处输出契约变化,不在本单判定面内。
🤖 Generated with Claude Code
https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
Generated by Claude Code