Skip to content

chore(devx): check:i18n 把「CLI 没 build」判成一个前置条件,不再报成 9 个 bundle 问题 (#5217) - #5868

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5217-i18n-bundles-prereq
Aug 6, 2026
Merged

chore(devx): check:i18n 把「CLI 没 build」判成一个前置条件,不再报成 9 个 bundle 问题 (#5217)#5868
os-zhuang merged 2 commits into
mainfrom
claude/issue-5217-i18n-bundles-prereq

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5217

前提复核(Prime Directive #6)

先在未构建的 worktree 里逐字复现了 issue 正文的现场,前提成立:9 条 ERROR + check-i18n-bundles: 9 bundle problem(s) + 9 条 extract failed — no output,exit 1。

顺带把分诊评论里那条「前提修正」核对了一遍,它本身是误判,原 issue 的成因判断没有被推翻:

  • 08-04 评论第 2 步用的是 pnpm --filter '@objectstack/cli^...' build —— ^... 只建依赖,不建 @objectstack/cli 自己,所以那一步之后 packages/cli/dist 依然不存在,复现完全是同一个成因;
  • 第 3 步的阴性对照(未改动的共享主检出)同理:我实测 /home/user/objectstack/packages/cli/dist 也不存在。同一个缺失产物,不是「容器/工具链层状态」。

所以判据锚「构建产物是否存在」是站得住的。但分诊那句提醒的价值仍然成立(不能假定成因只有一种),因此实现没有二选一,而是两条腿各管各的场景 —— 见下。

改了什么

scripts/check-i18n-bundles.mjs 一个文件。文件头那句 Requires the workspace build (it runs the built CLI)声明变成强制

未构建的工作区此前付了两次代价,第二次比第一次更糟:

入口 改前 改后
--check 9 条 ERROR + 9 bundle problem(s),措辞("bundle"/"extract")把读者指向 i18n 配置 1 条前置条件 + 1 句修法,exit 1
--write 9 次 regenerated,exit 0 —— 什么都没写的全绿运行 同上,exit 1
--self-test 绿(无需构建) 绿(无需构建,行为不变)

--write 那条是本轮顺带发现的:同一个成因在写入路径上是假绿,比假红更危险,由同一次前置判定一并收住。

两条腿,以及为什么不是二选一

issue 给了两条路线(探产物 / 认 oclif 签名)。两条都实现了,因为它们各自覆盖对方看不见的场景:

  1. checkCliBuildPrerequisite() —— per-package 循环之前,零次 CLI spawn。
    • 探的是 oclif 真正要加载的那个命令文件,而 packages/cli/bin/run.js 只是四行源码 stub(未构建时也在,探它等于没探)。
    • 路径从 CLI 自己的 oclif.commands.target 推导,不硬编码:理由和「extract 的 flag 从各 config 的 docstring 里读」一样 —— 别人挪了 dist 布局之后,硬编码的探针会一直探旧路径,绿着却什么都没查。
    • 探的是命令文件而不是 dist/ 目录:中断的构建会留下目录,而留下目录正好复现要修的九连报。
  2. 循环内的签名网 —— 认 oclif 自己的 command … not found,首个包命中即整体退出(不再累积第 2..9 条)。
    • 它管探针看不见的场景:陈旧/半成品 dist(命令文件在、命令解析不到)、以及 package.json 形状变了导致推导读不出来时的兜底。
    • 正因为有这张网,探针在读不出声明时可以「出声后放行」,而不必把一个构建正确的工作区判红 —— 强制力在网上,探针只是便宜的早答。

一个真实的坑:oclif 会把那句话拦腰折断

签名匹配前先把整段文本压平(去掉 前缀再合并)。oclif 按配置路径长度硬换行,真实语料里同时存在:

 ›   Error: command
 ›   i18n:extract:packages/platform-objects/scripts/i18n-extract.config.ts not
 ›   found

把路径本身断开的两行式(…/i18n + -extract.config.ts)。逐行正则 —— 最自然、最像对的那个写法 —— 两种都匹配不到。 两段语料都按原样收进了 --self-test

另外,证据行取的是正则匹配到的那一句,不是整段压平文本:陈旧 dist 场景 oclif 会在错误上面再叠一段 node Warning: 块,取整段会把那段无关噪音印进报告、真正那句话反而被截断挤掉 —— 这个是本轮自己踩了一次才改对的,也钉进了语料。

验证

改前 / 改后对照(未构建 worktree,实测)

改前(= issue 正文现场):

  platform-objects               ERROR
  … 共 9 条 …
check-i18n-bundles: 9 bundle problem(s)
  • platform-objects: extract failed — no output
  … 共 9 条 …            EXIT=1

改后:

check-i18n-bundles: PREREQUISITE NOT MET — the workspace CLI is not built

  This gate runs the BUILT CLI. packages/cli/bin/run.js is only a source stub that hands
  off to oclif, which resolves `os i18n extract` from the compiled
  output — and that command is not there:

    packages/cli/dist/commands/i18n/extract.js

  Fix:  pnpm exec turbo run build --filter=@objectstack/cli

  Nothing was checked: no bundle was compared and no config was parsed, so this
  result says NOTHING about whether the committed translation bundles are in sync.
  (Exit code 1 — but piping this gate reports the PIPE's status, so
  `pnpm check:i18n | tail -4` reads green either way. Use `echo "EXIT=$?"`.)

「什么都没查」那句是刻意的:AGENTS.md「Absence must be loud / prefer failing to falling back」—— 前置失败是硬失败,且不许读成「bundle 没问题」。管道吞退出码那句来自 issue 正文的使用侧观察(分诊评论允许写进文案)。

零回归(构建后)

pnpm check:i18n 绿,9 包 in sync;与改动前同一命令的输出 sha256 一致、stderr 同为空、exit 0 一致:

827c14e8260cb8cfa114b133e121bc8b84720ee6f491b31e151402d83c5ce6ab  before-built.stdout
827c14e8260cb8cfa114b133e121bc8b84720ee6f491b31e151402d83c5ce6ab  after-built.stdout

合入 origin/main(含 #5857 能力词表、#5849 ActionSession)并重建后再跑一次,仍逐字节一致。

唯一刻意改动的既有输出是 --self-test 的成功行(现在点名第三个分类器)—— 留着旧文案等于对覆盖面说谎,故一并更新,在此明示。

反向验证(方向先判后跑,结果与预判一致但需要说明)

  1. 把压平换成逐行正则(那个"看起来最对"的写法)→ 新语料 4 条转红,预判方向命中:
✗ check:i18n --self-test — 4 failure(s)
  #5217 wrapped 3-line — oclif line wrapping must not hide the signature
  #5217 wrapped mid-token — a path split mid-token must still match
  #5217 flattens for the message …
  #5217 stale dist quotes the sentence, not the noise …
  1. 「把修复摘掉就该看到 9 条」这个预设在本 PR 只有条件成立 —— 照实说明,不套模板:

    • 只摘探针、留签名网 → 仍是 1 条前置条件(走第二个 headline,经由网命中),不是 9 条;
    • 两条腿都摘掉 → 才逐字回到改前的 9 条 ERROR + 9 bundle problem(s)

    也就是说两条腿对「纯未构建」这一种场景各自独立充分,探针额外买到的是:零次 spawn、以及一条文件系统级准确的报错(点名缺哪个文件)。

  2. 签名网的真实场景可达性(不是只有语料证明):把 dist/commands/i18n/extract.js 清空(探针通过、oclif 解析不到),预判「网在第一个包命中」→ 命中:

check-i18n-bundles: PREREQUISITE NOT MET — the built CLI cannot resolve the command this gate runs
  platform-objects's extract exited 2 with oclif's own "command not found":
    Error: command i18n:extract:packages/platform-objects/scripts/i18n-extract.config.ts not found

其它

  • node scripts/check-nul-bytes.mjs 绿;改动文件 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 零命中。
  • npx eslint scripts/check-i18n-bundles.mjs 干净。
  • 本文件不在任何 package 的 tsconfig / vitest 覆盖面内,它的测试面就是自带的 --self-test(本 PR 扩了第三个分类器的语料);故没有可跑的 pnpm test 与之相关,不虚报。

CI 行为不变的证明

.github/workflows/lint.ymltypecheck job(name: TypeScript Type Check,L402)里:

  • L633-634 - name: Build workspace packagesrun: pnpm exec turbo run build --filter='./packages/*' --filter='./examples/*^...'
  • L754-755 - name: Check generated translation bundles are in sync with the schemarun: 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 入口)同轮另派,未改对方任何文件、未等对方落地。措辞与修法提示格式按同一形状写,便于对齐:

[门禁名]: PREREQUISITE NOT MET — [一句话说清缺什么]

  [为什么这一步依赖它]

  Fix:  [一条可直接粘贴的命令]

  [这次判定什么都没查 —— 不许读成内容没问题]

#5795 那边的修法命令是 pnpm build,本条是 pnpm exec turbo run build --filter=@objectstack/cli(只需要 CLI)。

范围外发现

变更集

.changeset/*.md:改动是 scripts/ 下单个内部门禁脚本,不发布任何包、无用户可见行为变化 —— 走 skip-changeset 标签路线(与 #4804 那次同一门禁的改动同例)。


Generated by Claude Code

claude added 2 commits August 6, 2026 09:46
)

`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
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 9:58am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 10:03
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 111695a Aug 6, 2026
24 of 25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5217-i18n-bundles-prereq branch August 6, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-i18n-bundles 在工作区未构建时把「CLI 没 build」报成 9 个包各自的 bundle 问题

2 participants