chore(devx): check:i18n 把「CLI 没 build」判成一个前置条件,不再报成 9 个 bundle 问题 (#5217) - #5868
Merged
Conversation
) `scripts/check-i18n-bundles.mjs` 跑的是构建产物 —— `packages/cli/bin/run.js` 只是四行源码 stub,oclif 从 `dist/commands` 解析命令 —— 而这个前置条件此前 只写在文件头注释里("Requires the workspace build"),没有任何一步检查它。 未构建的 worktree 因此付了两次代价: - `--check` 报 "9 bundle problem(s) … extract failed":一个环境前置被呈现 成九个内容问题,且 "bundle"/"extract" 两个词正好把读者指向 i18n 配置; - `--write` 打印九次 "regenerated" 并**退出 0** —— 一次什么都没写的全绿运行。 per-package 循环之前加一次前置判定,两种形态收敛成一条前置条件 + 一句修法。 判定分两条腿,各管各的场景: - `checkCliBuildPrerequisite()` 探 oclif 真正要加载的那个命令文件,路径从 CLI 自己的 `oclif.commands.target` 推导(不硬编码,否则别人挪了 dist 布局 后这个探针会一直探旧路径、绿着却什么都没查)。探到缺失就一条前置报告, 零次 CLI spawn。探的是命令文件本身而不是 `dist/` 目录:中断的构建会留下 目录,而留下目录正好复现要修的九连报。 - 循环内的签名网:认 oclif 自己的 `command … not found`,首个包命中即整体 退出。它管探针看不见的场景 —— 陈旧/半成品 dist(命令文件在但命令解析 不到)、以及 package.json 形状变了导致推导读不出来时的兜底。正因为有这 张网,探针在读不出声明时可以出声后放行,而不必把一个构建正确的工作区判红。 签名匹配前先把文本压平:oclif 会按配置路径长度把那一句硬换行成两三行,真实 语料里同时存在三行式和**把路径本身拦腰断开**的两行式,逐行正则(最自然、最 像对的那个写法)两种都匹配不到。`--self-test` 收了两种真实语料 + 陈旧 dist 场景的 node Warning 噪音语料,并钉住两个方向:换行不得漏判,drift / 未声明键 / 无关失败不得被误判成「没构建」。反向验证:把压平换成逐行正则,新语料 4 条转红。 CI 行为零变化:`lint.yml` 的 typecheck job 里 `Build workspace packages` 在 `pnpm check:i18n` 之前,该分支在 CI 永远命中不到。判定通过时输出与退出码 逐字节不变(9 包 in sync 路径 sha256 一致)。 Fixes #5217
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 6, 2026 10:03
This was referenced Aug 6, 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 #5217
前提复核(Prime Directive #6)
先在未构建的 worktree 里逐字复现了 issue 正文的现场,前提成立:9 条
ERROR+check-i18n-bundles: 9 bundle problem(s)+ 9 条extract failed — no output,exit 1。顺带把分诊评论里那条「前提修正」核对了一遍,它本身是误判,原 issue 的成因判断没有被推翻:
pnpm --filter '@objectstack/cli^...' build——^...只建依赖,不建@objectstack/cli自己,所以那一步之后packages/cli/dist依然不存在,复现完全是同一个成因;/home/user/objectstack/packages/cli/dist也不存在。同一个缺失产物,不是「容器/工具链层状态」。所以判据锚「构建产物是否存在」是站得住的。但分诊那句提醒的价值仍然成立(不能假定成因只有一种),因此实现没有二选一,而是两条腿各管各的场景 —— 见下。
改了什么
scripts/check-i18n-bundles.mjs一个文件。文件头那句Requires the workspace build (it runs the built CLI)从声明变成强制。未构建的工作区此前付了两次代价,第二次比第一次更糟:
--check9 bundle problem(s),措辞("bundle"/"extract")把读者指向 i18n 配置--writeregenerated,exit 0 —— 什么都没写的全绿运行--self-test--write那条是本轮顺带发现的:同一个成因在写入路径上是假绿,比假红更危险,由同一次前置判定一并收住。两条腿,以及为什么不是二选一
issue 给了两条路线(探产物 / 认 oclif 签名)。两条都实现了,因为它们各自覆盖对方看不见的场景:
checkCliBuildPrerequisite()—— per-package 循环之前,零次 CLI spawn。packages/cli/bin/run.js只是四行源码 stub(未构建时也在,探它等于没探)。oclif.commands.target推导,不硬编码:理由和「extract 的 flag 从各 config 的 docstring 里读」一样 —— 别人挪了 dist 布局之后,硬编码的探针会一直探旧路径,绿着却什么都没查。dist/目录:中断的构建会留下目录,而留下目录正好复现要修的九连报。command … not found,首个包命中即整体退出(不再累积第 2..9 条)。一个真实的坑:oclif 会把那句话拦腰折断
签名匹配前先把整段文本压平(去掉
›前缀再合并)。oclif 按配置路径长度硬换行,真实语料里同时存在:和把路径本身断开的两行式(
…/i18n+-extract.config.ts)。逐行正则 —— 最自然、最像对的那个写法 —— 两种都匹配不到。 两段语料都按原样收进了--self-test。另外,证据行取的是正则匹配到的那一句,不是整段压平文本:陈旧 dist 场景 oclif 会在错误上面再叠一段 node
Warning:块,取整段会把那段无关噪音印进报告、真正那句话反而被截断挤掉 —— 这个是本轮自己踩了一次才改对的,也钉进了语料。验证
改前 / 改后对照(未构建 worktree,实测)
改前(= issue 正文现场):
改后:
「什么都没查」那句是刻意的:AGENTS.md「Absence must be loud / prefer failing to falling back」—— 前置失败是硬失败,且不许读成「bundle 没问题」。管道吞退出码那句来自 issue 正文的使用侧观察(分诊评论允许写进文案)。
零回归(构建后)
pnpm check:i18n绿,9 包 in sync;与改动前同一命令的输出 sha256 一致、stderr 同为空、exit 0 一致:合入
origin/main(含 #5857 能力词表、#5849 ActionSession)并重建后再跑一次,仍逐字节一致。唯一刻意改动的既有输出是
--self-test的成功行(现在点名第三个分类器)—— 留着旧文案等于对覆盖面说谎,故一并更新,在此明示。反向验证(方向先判后跑,结果与预判一致但需要说明)
「把修复摘掉就该看到 9 条」这个预设在本 PR 只有条件成立 —— 照实说明,不套模板:
9 bundle problem(s)。也就是说两条腿对「纯未构建」这一种场景各自独立充分,探针额外买到的是:零次 spawn、以及一条文件系统级准确的报错(点名缺哪个文件)。
签名网的真实场景可达性(不是只有语料证明):把
dist/commands/i18n/extract.js清空(探针通过、oclif 解析不到),预判「网在第一个包命中」→ 命中:其它
node scripts/check-nul-bytes.mjs绿;改动文件grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'零命中。npx eslint scripts/check-i18n-bundles.mjs干净。--self-test(本 PR 扩了第三个分类器的语料);故没有可跑的pnpm test与之相关,不虚报。CI 行为不变的证明
.github/workflows/lint.yml的typecheckjob(name: TypeScript Type Check,L402)里:- name: Build workspace packages→run: pnpm exec turbo run build --filter='./packages/*' --filter='./examples/*^...'- name: Check generated translation bundles are in sync with the schema→run: pnpm check:i18n构建在同一 job 内、早 120 行,且该步骤自己的注释就写着「Reads the built @objectstack/spec dist through the extract configs, so it belongs after the build step」。CI 里
packages/cli/dist永远已存在,新增分支永远命中不到;命中不到时新增代码只是一次existsSync+ 每包一次不匹配的正则。全仓另外只有 L770 的check:i18n-coverage引用同族脚本,不是本文件。同族同构
#5795(根
dev入口)同轮另派,未改对方任何文件、未等对方落地。措辞与修法提示格式按同一形状写,便于对齐:#5795 那边的修法命令是
pnpm build,本条是pnpm exec turbo run build --filter=@objectstack/cli(只需要 CLI)。范围外发现
finding,未入pm:queue,未指派):scripts/check-i18n-coverage.mjs—— CI 里紧邻的下一步 —— 带着同一句声明式前置(L31)且同样没检查,未构建时抛未捕获异常 + node 栈,并把成因指向examples/app-crm/objectstack.config.ts这个无辜配置。同类不同形、文件面不同,不落在 check-i18n-bundles 在工作区未构建时把「CLI 没 build」报成 9 个包各自的 bundle 问题 #5217(派发词已把文件面限定为本文件一个)或 根dev脚本没有任何一步确认工作区已构建 —— 前置检查只有 check:console-sha,未构建时放行进入 12 段噪音现场 #5795 的完成范围内,故独立立单,未在本 PR 内修。变更集
无
.changeset/*.md:改动是scripts/下单个内部门禁脚本,不发布任何包、无用户可见行为变化 —— 走skip-changeset标签路线(与 #4804 那次同一门禁的改动同例)。Generated by Claude Code