fix(cli): os validate / os build 展开 invalid_union,联合分支里的处方到达终端 (#5341) - #5391
Merged
Conversation
…ion reaches the terminal (#5341) Zod folds every branch of a failed union into ONE top-level issue whose own message is the literal "Invalid input"; each branch's real rejection sits in `issue.errors[]`. The CLI's `formatZodErrors` walked only the top level, so `os validate`, `os build` (compile) and `os plugin build` — all three print through that one function — showed `invalid_union: Invalid input` and dropped the branch that says WHICH key is wrong. Third consumer of the same defect after `formatZodError` (#4971, PR #5342) and `zodIssuesToFields` (#5014, PR #5362). The branch-selection policy is reused rather than re-derived: because the terminal needs exactly the string spec already exports, this one is a plain `formatZodIssue` import instead of a third copy of the ranking. Strictly additive: the union's own lines still print, non-union issues render unchanged, the footer still counts `error.issues`, and the `--json` path is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
…-format-zod-union
|
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 04:26
baozhoutao
enabled auto-merge
August 5, 2026 04:26
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 #5341
问题
zod 把一个失败 union 的所有分支折进一条顶层 issue,它自己的
message就是字面量"Invalid input";每个分支真正的判决(必填缺失、未知键处方)挂在issue.errors[]上,路径相对于 union 自身。CLI 的
formatZodErrors(packages/cli/src/utils/format.ts)只遍历顶层 issue —— 整个函数没有任何一处读issue.errors。os validate/os build(compile) /os plugin build三个命令都从这一个函数出终端,所以作者读到的是:而分支里那条
direction→order的处方,每次都被生成、每次都没送达。终端因此是 #4001 战役里唯一一个策展散文到不了的读者面 —— 旁边的--jsonpayload 一直是全的。修好之后:
形态:复用,不是第三份实现
同一个缺陷的第三个消费者,三份各自独立的代码:
formatZodError(spec,defineStack抛错走它)packages/spec/src/shared/error-map.zod.tszodIssuesToFields(REST wire)packages/rest/src/rest-server.tsformatZodErrors(CLI 终端)packages/cli/src/utils/format.ts分支挑选策略沿用前两次已落地的那套,不重新发明:丢弃只说「值的种类不对」的分支、按 issue 最少挑最接近的那支、
unrecognized_keys破平局、绝对路径、深度上限。区别在于复用方式。#5362 当时是抄一份而不是 import,理由写在它自己的注释里:spec 只导出字符串渲染器,而 wire 要的是结构化的
{field, code, message}。终端要的恰好就是那个字符串,所以这里是一行import { formatZodIssue } from '@objectstack/spec'—— 不改 spec,只消费它。一个错误不会因为作者是从终端发布还是 POST 到 API 而拿到三份不同的处方。实现只有一个小函数
unionBranchLines():非 union 直接返回空;union 则取formatZodIssue的渲染结果,丢掉第 0 行(那是 union 自己的判决,本文件已用自己的排版打过了),其余行整体缩进 4 格挂到invalid_union: Invalid input之下。严格增量
✗ path与invalid_union: Invalid input两行照旧;code:/expected:/received:都还在);N validation error(s) total仍然数error.issues—— 一个 union 无论用多少行解释都还是一条 issue,这正是页脚与旁边--jsonpayload 保持一致的原因;--json路径一个字节没动,它透传error.issues,本来就是全的。测试
新增
packages/cli/test/format-zod-union.test.ts(10 条):error-map.test.ts里留的那颗钉);not.toContain会因为「什么都没产出」而空绿;views[].list.sort是z.union([z.string(), z.array(严格 sort entry)]),entry 声明了 一个被静默丢弃的排序键对外部调用方仍然是静默的:direction该 400 还是继续被丢掉(#4674 第 4 项) #4721 的direction→order别名(写错会静默反转排序)。先用 schema 层控制断言「这个 stack 只失败一条,且就是这个 union」,再断言os validate终端里出现处方;--json断言 payload 仍是 1 条 issue、分支树完整、没有被展平成多行errors[]。反向验证(方向先声明,后执行)
把展开那一行去掉,预测 6 红 4 绿。实测完全一致:
保持绿的 4 条是按设计该绿的:非 union 渲染、页脚计数、schema 层控制、
--json透传(第三条验收标准量的就是一条本 PR 不碰的路径)。#5380 回归守卫
刚合并的
packages/cli/test/metadata-type-schema-gate.test.ts(B 组断言os validate输出含顶层unrecognized_keys的处方文本)全绿,反向验证那一轮里也全绿 —— 它量的是顶层路径,与 union 展开正交,本 PR 的输出形状变化没碰到它。命令
(已合并
origin/main,含刚落地的 #5381 —— 同包不同文件,按 AGENTS.md §10 在合并树上重跑了整包。)前提核对(以
origin/main为准)issue 的核心论断成立:
formatZodErrors确实只遍历顶层,三个调用点行号也没漂(validate.ts:101/compile.ts:162/plugin/build.ts:114)。一处需要更正:issue 正文举的
compareTo例子今天点不着 —— #5011 已经把compareTo从 union 收敛成 plain strict object(dashboard-compareto.test.ts里有「产生 NO invalid_union issue」的钉),正是为了绕开这个缺陷。缺陷本身与那个例子无关,所以本 PR 换用仍然活着的views[].list.sort作标本复现。顺带
issue 正文里那条休眠注记(
invalid_key/invalid_element把真实 issue 挂在issue.issues上,三个消费者一个都不下降;今天z.record(...)的键 schema 全是z.string()或 enum,所以点不着)已单独存档为 #5389(finding,未认领),免得随本单关闭一起埋掉。不在本 PR 范围。🤖 Generated with Claude Code
https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
Generated by Claude Code