Uh oh!
There was an error while loading. Please reload this page.
ci(docs): 把 check-doc-links 接进 CI,并修好它一直在报的那条坏链 (#3213, #3292) - #3450
Merged
Conversation
scripts/check-doc-links.mjs 早就存在且能用,但 .github/ 下零调用 —— 它在未 改动的 origin/main 上就 exit 1,报 content/docs/core/enhanced-actions.mdx -> /docs/components/form 断链,却从来没有工作流跑过它。 两处改动: 1. 坏链改指 /docs/components/form/form(retarget,不补 index 页)。 content/docs/components/ 下 9 个分类目录全部没有 index 页,补一个会是 components 树里唯一的例外;components/index.md 自己也是用代表页链到分类 (如 /docs/components/form/button),从不链裸分类路由;form/meta.json 显式 列出 pages 且不含 index。而该句 "- [Form](...) - Form submission actions" 指向的正是 form/form.mdx(title: Form,description: Form container with validation and submission handling),不是 button。 2. ci.yml 的 docs job 加一步 Check docs links,放在 Setup Node.js 之后、 install 与站点构建之前 —— 脚本零依赖零网络,坏链几秒内失败,不必等一次 完整的 Next.js 构建。 已知缺口(不在本次裁决范围,已记为 #3448):ci.yml 的 paths-ignore 含 content/**,GitHub 又没有 per-job path filter,所以纯 docs PR 根本不会启动这个 workflow。control-bytes.yml 撞过同一堵墙并靠独立 workflow 解决,文件头写明了 理由。本步骤覆盖 docs+代码混合 PR 与 push to main。 check-links.yml(Lychee)按裁决维持 workflow_dispatch;有意未加 cron —— 它扫的 是 docs/**(15 个内部文件)而非 content/docs/**(183 个站点文档),范围修正前 加 cron 只会按时产出一份不覆盖站点文档的绿报告(已记为 #3449)。 验证:改前 node scripts/check-doc-links.mjs -> exit 1(1 broken link); 改后 -> exit 0 "Docs links are valid."
The latest updates on your projects. Learn more about Vercel for GitHub. |
commandment #2(docs-driven):ci.yml 的 docs job 多了一步 Check docs links, 该页的 job 表格原文只写了 turbo build,已同步。 另在 Link Checking 一节补一张对照表,写清两个检查器各管什么: check-doc-links.mjs 管站内 /docs/... 路由、不走网络、在 ci.yml 里跑; Lychee 管外链、走网络、只能手动触发。并写明两个已知缺口(#3448 纯 docs PR 不触发 ci.yml;#3449 Lychee 扫的是 docs/ 而非 content/docs/),以免读者从这一页 得出「文档链接已被完全守住」的结论。 未改该页既有的其它内容(如 job 数量与 dev-server 行的既存漂移),另行记录。 验证:pnpm exec vitest run scripts/__tests__/ -> 8 files / 120 tests passed (含 ci-cd-pipeline-doc.test.ts 的 9 条,它正是钉这一页的)。
yinlianghui
marked this pull request as ready for review
August 6, 2026 05:43
Uh oh!
There was an error while loading. Please reload this page.
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 6, 2026
…ctstack-ai#3448) (objectstack-ai#3458) `check-doc-links.mjs` 由 PR objectstack-ai#3450 接进 CI 时,落点是 `ci.yml` 的 `docs` job —— 而 `ci.yml` 的 `paths-ignore` 列着 `'**/*.md'`、`content/**`、`docs/**`、 `apps/site/**`。GitHub 的 `paths-ignore` 语义是「改动文件全部命中即整个 workflow 不启动」,且没有 per-job path filter;站点文档全在 `content/docs/**`。所以**只改 文档的 PR 根本不会启动 ci.yml**,这道门禁看不见的恰恰是最可能改坏内链的那一类 PR。它此前只覆盖「文档+代码」的混合 PR 和 push 到 main —— 坏链能经纯 docs PR 合 进 main,直到下一个无关作者推代码时才把 main 弄红,归因还错人。 `control-bytes.yml` 撞过同一堵墙,头注写着结论:看不见 markdown-only PR 的门禁 "rebuilds the hole it exists to close";`changeset-guard.yml` 是同一形状的第二例。 本次是第三例。 改动: • 新增 `.github/workflows/docs-links.yml`,镜像 control-bytes.yml 的形状: push/PR to main+develop + workflow_dispatch,**无 paths / 无 paths-ignore**, checkout + setup-node + 一行 `node scripts/check-doc-links.mjs`,无 install 无网络。头注写清为什么不能加路径过滤。 • **从 ci.yml 的 docs job 删掉重复步骤**(最小重复原则):新 workflow 的触发集 是该 job 的严格超集,留着只会为同一条坏链多出一个红勾和一处会忘记同步的副本。 原地留注释说明它去哪了、为什么别加回来。 • 新增 `scripts/__tests__/docs-links-workflow.test.ts` 钉住形状:workflow 必须 存在、必须门禁 PR、必须既无 `paths` 也无 `paths-ignore`、必须是**唯一**跑该 脚本的 workflow;并以不变式表述「跑这个脚本的 workflow 必须是纯 docs PR 能 启动的」,即使将来改名搬家也成立。扫描前先剥掉整行注释 —— ci.yml 的说明注释 里仍然提到脚本名,不剥会误判成重复门禁。 • `content/docs/guide/ci-cd-pipeline.md`:这一条不是顺手改文档,是 `scripts/__tests__/ci-cd-pipeline-doc.test.ts` 的机械要求 —— 只加 workflow 不加章节,该测试立刻红,报「docs-links.yml 无对应标题」。补:清单表一行、新章节 (含为什么无路径过滤)、ci.yml 任务表里 docs 行的更正(它不再跑链接检查)、 以及 Link Checking 章节里 objectstack-ai#3448 那个「已知缺口」的收口。 反向验证(方向先判后跑):把删掉的步骤加回 ci.yml —— 新测试如期两处红: "is the only workflow that runs the link checker"(ci.yml + docs-links.yml)与 "every workflow that runs it is one a docs-only PR can start"(ci.yml 有 paths-ignore)。给 docs-links.yml 加 `paths: content/**` —— "carries NO path filter" 如期红。 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 10, 2026
…t path filtering into the jobs (objectstack-ai#3523 steps 1-2) (objectstack-ai#3722) * ci(step 1): subscribe ci/lint/control-bytes/docs-links to merge_group (objectstack-ai#3523) Step 1 of objectui#3523, on its own commit as the issue's ruling requires: this is a pure addition and changes nothing about which pull requests or pushes run CI. The merge queue is enforced on this repository by a ruleset (objectstack-ai#3243 measured a direct push to `main` returning 405 `Changes must be made through the merge queue`), but not one of the 22 workflows subscribed `merge_group` — repo-wide `event=merge_group` runs stood at total_count = 0. A queue nothing subscribes to can only carry an empty required-check set, so it rebuilt each PR on the current `main` and let it through without validating anything. On 2026-08-07 that was cashed in: objectstack-ai#3503 / objectstack-ai#3510 / objectstack-ai#3516 merged between 02:11Z and 02:15Z with `Type Check` at conclusion=failure, on a `main` poisoned by objectstack-ai#3498, and objectstack-ai#3505 had to hot-fix it. The four workflows named by the issue now carry the trigger. It is spelled `merge_group: types: [checks_requested]`; `checks_requested` is the only activity type GitHub defines for this event today, so this is equivalent to objectstack's bare `merge_group:` and merely refuses to inherit a second type added later. Two `ci.yml` jobs additionally needed to be told the third event exists — both changes are no-ops for `pull_request` and `push`: - `test` moves from `if: github.event_name == 'pull_request'` to `!= 'push'`, so a queue build actually runs the suite instead of skipping every shard. - `docs`'s `should_run` treated anything that is not `push` as a pull request and diffed `github.event.pull_request.*`, which is null on a queue build — an empty revision range, i.e. the site build silently skipped on the last check before `main`. Verified rather than assumed: `concurrency` needs no merge-queue case. On `merge_group` the group expression falls back to `github.ref`, which is the queue's own generation — measured on objectstack, whose 3552 queue runs report head_branch `gh-readonly-queue/main/pr-6594-251e888a…`. That can collide with neither a PR group (a bare number) nor a push group (`refs/heads/main`). Refs objectstack-ai#3523 * ci(step 2): move ci/lint path filtering out of the PR trigger into the jobs (objectstack-ai#3523) Step 2 of objectui#3523, deliberately a separate commit from step 1: step 1 adds runs that did not exist, this one changes which pull requests start CI, and mixing them would make that impossible to review apart. `on.pull_request.paths-ignore` skips the WHOLE workflow when every changed file matches, and GitHub has no per-job path filter. A docs-only or changeset-only PR therefore started neither `ci.yml` nor `lint.yml` — objectstack-ai#3509 measured zero check runs from them. A check that is never *created* does not fail a required-status-check rule, it leaves the pull request pending; inside the merge queue it fails on the ruleset's 60-minute status-check timeout. So none of `Lint`, `Type Check`, `Test (shard N/4)`, `Build & E2E` or `Changeset Fixed Group Check` could be made required while the filter lived on the trigger — which is why the queue's required set was empty to begin with. The filter is not deleted, it moves. `type-check`, `test`, `e2e` (ci.yml) and `lint` (lint.yml) each open with a `Decide whether this change needs a full run` step, and every step after it carries `if: steps.relevant.outputs.should_run == 'true'`. The job always runs and always reports; the paths decide only whether it does any work. That is the shape `ci.yml`'s `docs` job has used since objectstack-ai#3450 — not a new mechanism — and the exclusion lists are byte-for-byte the `paths-ignore` they replace, so which PRs pay for a full run is exactly as before. Two deliberate narrowings, both stated so they can be argued with: - `paths-ignore` stays on the `push` trigger. Branch protection and the merge queue judge pull requests and queue builds, never pushes to `main`, so the push lane gains nothing from losing it and would cost a full CI run on every docs merge. It also gives the ignore list one authored home, which `merge-queue-reporting.test.ts` then pins the in-job copies against. - `changeset-check` is not gated. It is a checkout and one `node` call, so short-circuiting it would cost more in complexity than it saves. The gate fails OPEN: an unresolvable diff runs everything rather than reporting green having built nothing (objectstack#4928's filter contract). Measured both ways against a fixture repository — as shipped an unreachable base sha yields should_run=true; rewritten with the `|| echo ""` spelling `ci.yml`'s older `docs` gate still uses, the same input yields should_run=false, i.e. a silent full skip. That pre-existing `docs` gate is reported separately rather than changed here. `scripts/__tests__/merge-queue-reporting.test.ts` pins both steps; against the pre-change workflows 9 of its 10 assertions go red, each naming its own regression. `lint-workflow.test.ts`'s TypeScript-exclusion tripwire now reads the in-job list as well as the trigger, which is where that list now lives. Refs objectstack-ai#3523 --------- Co-authored-by: Claude <noreply@anthropic.com>
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 freeto 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#3213
Fixes#3292
前提复核(在未改动的 origin/main 上)
三条前提全部复现,均成立:
check-links.yml的on:也确认只剩workflow_dispatch(push/pull_request被注释掉)。即:一个能跑的检查器没人跑,另一个只能手动 —— 能力声明了但没被执行。
改动一:坏链改指
/docs/components/form/form(选 retarget,不补 index 页)维护者裁决给了两个选项(补
components/form索引页 / 改指/docs/components/form/form,实施侧择优)。选 retarget,四条证据都指向它:content/docs/components/下 9 个分类目录(basic、complex、data-display、disclosure、feedback、form、layout、navigation、overlay)全部 0 个 index 文件。补一个会是 components 树里唯一的例外。content/docs/components/index.md里写的是### [Form Components](/docs/components/form/button)、### [Basic Components](/docs/components/basic/text)—— 一律用代表页,从不链裸分类路由。form/meta.json显式列出 pages 且不含 index,补页还得同步改 meta.json,进一步偏离约定。- [Form](/docs/components/form) - Form submission actions,讲的是表单提交;content/docs/components/form/form.mdx的 frontmatter 正是title: "Form"/description: "Form container with validation and submission handling"。是它,不是button。路由存在性也已核对:
apps/site/source.config.ts的dir: '../../content/docs'+apps/site/lib/source.ts的baseUrl: '/docs',故content/docs/components/form/form.mdx即/docs/components/form/form。改动二:
ci.yml的 docs job 加一步Check docs links按裁决「A 进 PR 门禁」。位置放在
Setup Node.js之后、Turbo Cache/Install dependencies/Build Site之前:content/docs/做existsSync解析),不需要 install,几秒跑完 —— 契合 ci.yml 既有的「便宜的结构性检查放 install 之前」写法(参考Verify type-check coverage那一步的注释)。should_run条件,与其余步骤风格一致,也避免因别人留下的坏链把无关 PR 判红。改动三:同步
content/docs/guide/ci-cd-pipeline.mdcommandment #2(docs-driven)。该页的
ci.ymljob 表格原文只写了turbo run build,加了门禁就不再准确,已同步;并在 Link Checking 一节补一张对照表,写清两个检查器的分工(站内路由 / 外链、走不走网络、在哪跑)与两个已知缺口 —— 免得读者从这一页得出「文档链接已被完全守住」的结论。ci.yml的on:在push和pull_request两处都带paths-ignore,其中含content/**与'**/*.md';GitHub 的paths-ignore是「改动文件全部命中则整个 workflow 不启动」,而且没有 per-job path filter。于是:
content/**)main(含非忽略路径)最可能改坏链接的那类 PR(纯 docs)恰恰不被覆盖。
仓库里已有针对同一堵墙的正确答案 ——
control-bytes.yml的文件头:check-doc-links.mjs与 control-bytes 属于完全相同的类别(守 markdown/content、零 install、零网络)。推荐后续按 #3448 的方案 A 把它挪成独立 workflow。 本 PR 没有自行这么做:裁决明确写的是「加进 ci.yml 的 docs 任务」,且改 workflow 触发策略属于 CI 成本决策,应由维护者拍板。缺口已写进该步骤上方的注释,不留给下一个人踩。check-links.yml(Lychee):维持 workflow_dispatch,有意未加 cron —— 已记为 #3449
裁决说 cron 可选、有疑虑就跳过。这里有实证疑虑:Lychee 扫的是
docs/**/*.md(x)+README.md,而站点文档在content/docs/**。范围修正之前加 cron,只会按时产出一份「检查了内部 ADR、没检查站点文档」的绿报告 —— 加噪声不加覆盖,还会让人误以为已发布文档的外链有人守。建议先修范围再谈 cron。
验证
反向验证(方向预判:恢复坏链 → 检查器转红)。 预判与实测一致:
新步骤在本 PR 上真实跑过,且是 executed 而非 skipped —— 取 Build Docs job 的 steps(commit
3c83a59):(skipped 的步骤 conclusion 会是
skipped,这里是success,所以确实执行了。本 PR 能触发ci.yml是因为它改了.github/workflows/ci.yml,该路径不在paths-ignore里;docs-changes又因content/有改动而置should_run=true。)其余:
python3 -c "yaml.safe_load(...)");docs job 步骤顺序为Checkout code → Check for docs changes → Enable Corepack → Verify pnpm version → Setup Node.js → Check docs links → Turbo Cache → Install dependencies → Build Site。pnpm exec vitest run scripts/__tests__/ --maxWorkers=2→8 passed (8) / 120 passed (120),其中ci-cd-pipeline-doc.test.ts9 条全绿(它正是钉ci-cd-pipeline.md的那个文件)。node scripts/check-control-bytes.mjs→OK (scanned 3640 tracked text file(s); skipped 85 binary);另对三个改动文件单独grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'→ 无命中。Build Docs已在本 PR 上完整构建站点(556 静态页)成功,顺带验证改后的链接在真实路由下成立。说明
pnpm --filter PKG test#3443 先例)。scripts/check-doc-links.mjs:任务是把它接上,不是重写它。