Skip to content

fix(scripts): check-i18n-coverage 的构建前置改为真检查,不再让第一个 example 顶罪 - #6032

Queued
hotlong wants to merge 1 commit into
mainfrom
claude/issue-5862-i18n-coverage-precondition
Queued

fix(scripts): check-i18n-coverage 的构建前置改为真检查,不再让第一个 example 顶罪#6032
hotlong wants to merge 1 commit into
mainfrom
claude/issue-5862-i18n-coverage-precondition

Conversation

@hotlong

@hotlong hotlong commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5862

问题

scripts/check-i18n-coverage.mjs#5217 修的门禁是 lint.yml 里紧邻的两步,并且带着同一句只写在注释里的前置声明("Requires the workspace build (it runs the built CLI)")。在装完依赖但未构建的 worktree 里实跑,它给出的是一个未捕获异常加一段 node 栈:

Error: os lint produced no output for examples/app-crm/objectstack.config.ts
    at countI18nIssues (…/check-i18n-coverage.mjs:101:28)

成因只有一个,而且不在那个文件里:门禁跑的是构建产物 packages/cli/bin/run.js,oclif 从 dist/commands 解析 os lint,CLI 没 build 时什么也不输出。examples/app-crm/objectstack.config.ts 只是恰好第一个被处理的配置,替整个环境顶了罪 —— 排障时的第一手读数在说谎。

CI 永远命中不到(Build workspace packages 排在这两步之前),这也正是它能活下来的原因:唯一会遇到它的人,是在本地复现红色 i18n CI 的人,恰好是错误首诊代价最高的时刻。

改动(与 #5217 落地形状同构)

  1. 进入 per-config 循环之前一次前置判定 —— 探测 oclif 真正要加载的命令文件(从 CLI 自己的 oclif.commands.target 推导,而不是写死),失败时给一条前置结论 + 一句修法,并明确声明什么都没测量、baseline 一字未动(含 --update)。
  2. 循环内保留签名安全网 —— 覆盖前置探测结构上看不见的情形(stale / partial dist:命令文件在、但构建产物已答不上这个 id)。首个 config 即中止,并且消息里点名该 config 不是成因,正好把原缺陷反过来。
  3. --self-test —— 语料逐字录自本仓未构建实跑,并按仓内惯例接进 package.jsoncheck:i18n-coverage(--self-test && 门禁,与 check:i18n / check:nul-bytes / check:doc-authoring 一致)。不接线的话自测永远不跑,本身就是这仓反复付过学费的 phantom check。

为什么选「提取共享」而不是「照抄两份」

两个纯函数提取到新的 scripts/cli-build-prerequisite.mjs,check-i18n-bundles.mjs 改为 import。理由:

  • 它们编码的知识不显然,而且只会被改一边。核心是 oclif 会把那句 command … not found 硬换行成两三行(本仓实测,长路径会拆到第三行),逐行正则一条也匹配不到 —— 这正是「读起来正确」的那个实现。两份拷贝就是两套事实来源(check:durability-log-level 结构性看不见「读接缝把故障答成空值」这一类 —— #4825 / #5108 全家都在闸门盲区里 #5186 先例)。
  • 接线成本很低:check-i18n-bundles.mjs 只少了两个函数体、多了一条 import,其余行为逐字未变(见下方实测的前置消息比对)。
  • 反向验证给了这次提取一个直接的证据:把共享实现临时换成朴素的逐行版,两个门禁的自测同时变红(coverage 2 条、bundles 4 条)。一处写错、两套语料一起报警 —— 这是两份拷贝拿不到的。

申报文件面的扩面说明(认领时已预期):scripts/check-i18n-bundles.mjs(改 import)与新增的 scripts/cli-build-prerequisite.mjs 属于共享路线的必要扩面;package.json 一行是 --self-test 的接线,否则新增自测不进 CI。⛔ 未碰 lint.yml

一处诚实的修正:修法不能只说一半

按 issue 建议的 pnpm exec turbo run build --filter=@objectstack/cli 实跑后,前置确实清掉了(app-crm 正常计数),但墙只是往后挪了一格 —— 这个门禁不同于隔壁的 bundles 门禁,它还要 lint examples/*,而 example 配置会按名字 import 其它 workspace 包:

Error: os lint failed for examples/app-showcase/objectstack.config.ts:
  Cannot find module '…/@objectstack/connector-mcp/dist/index.mjs'

所以修法按两个宽度给:CLI_BUILD_FIX 是清掉「本次实际检查了什么」的最小命令(探测只测了 CLI,就不许替整棵树背书);紧跟一行说明从未构建过的树要 pnpm build。少这一行,新消息就变成「同一个缺陷、晚一步发生」。

实测

环境 命令 结果
未构建(改动前,origin/main 代码) node scripts/check-i18n-coverage.mjs 未捕获异常 + node 栈,点名 app-crm(EXIT=1)
未构建(改动后) 同上 一条 PREREQUISITE NOT MET + 修法,不点名任何 config(EXIT=1)
未构建 + --update … --update 同上;baseline sha 前后一致,未写入
partial dist(命令文件在、内容废掉) 同上 安全网触发,并写明 app-crm NOT at fault(EXIT=1)
已构建(full pnpm build) pnpm check:i18n-coverage OK (12 config(s), 660 baselined untranslated string(s), none new).(EXIT=0)
已构建 pnpm check:i18n(#5217 门禁) OK (9 package(s) …)(EXIT=0)
未构建 pnpm check:i18n 前置消息 与提取前逐字相同
任意 --self-test ×2 均通过;朴素逐行实现下两边同时变红

pnpm lint(CI ESLint job 首步)、pnpm check:nul-bytes 均为 EXIT=0。

Changeset

scripts-only,根包 private,不发布任何东西 —— 走 skip-changeset 标签(照 #5986 先例),不写 changeset。


Generated by Claude Code

`scripts/check-i18n-coverage.mjs` 与 #5217 修的门禁是 lint.yml 里紧邻的两步,
带着同一句只写在注释里的前置("Requires the workspace build")。在装完依赖但
未构建的 worktree 里,它抛一个未捕获异常 + node 栈:

    Error: os lint produced no output for examples/app-crm/objectstack.config.ts

成因只有一个,而且不在那个文件里:门禁跑的是构建产物,oclif 从 dist/commands
解析 `os lint`,CLI 没 build 时什么也不输出 —— app-crm 只是恰好第一个被处理。
诊断把读者送进一个完全无辜的示例配置。

改动与 #5217 落地形状同构:
- 进入 per-config 循环之前一次前置判定(探测 oclif 真正要加载的命令文件),
  失败时一条前置结论 + 一句修法,并声明「什么都没测量」;
- 循环内保留签名安全网,覆盖探测看不见的 stale/partial dist,首个 config 即
  中止,并明说该 config 不是成因;
- 两个纯分类器(oclifCommandFileFor / looksLikeMissingCliCommand)提取到
  scripts/cli-build-prerequisite.mjs 共享,check-i18n-bundles.mjs 改为 import
  —— 两份拷贝就是两套事实来源(#5186),而 oclif 硬换行这类知识只会被改一边;
- 补 --self-test(语料逐字录自本仓未构建实跑),并按仓内惯例接进
  package.json 的 check:i18n-coverage,否则自测永远不跑。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@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 3:15pm

Request Review

@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file size/m labels Aug 6, 2026
@hotlong
hotlong marked this pull request as ready for review August 6, 2026 15:28
@hotlong
hotlong added this pull request to the merge queue Aug 6, 2026
Any commits made after this event will not be merged.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31117146174 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 17 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Copy link
Copy Markdown
Contributor

队列管家原样重投(Routine trig_012yeNVMdFwXET5zvYQtUKXA,锚点 #5810

⛔ 未改代码、未切 ready/draft、未重跑(rerun 复用原合并 ref,对此类红无意义)。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants