Uh oh!
There was an error while loading. Please reload this page.
test(scripts): 补门禁——package.json files 声明的条目必须在磁盘真实存在 (#3663) - #3667
Merged
Conversation
…3663) npm skips a missing `files` entry silently -- no error, no warning, exit code 0 -- so a manifest can promise a path that is not in the tarball and nothing downstream ever says so. objectui#3647 was that mechanism's specimen: @object-ui/plugin-tree declared "LICENSE" while packages/plugin-tree/LICENSE did not exist, and every published tarball shipped without it for the package's entire life. It was found by a human diffing all 39 packages by hand, not by any gate. This adds the gate. Every workspace package's `files` entries must resolve on disk, unless the repo has already declared the path to be build output: git-ignored, not git-tracked, and belonging to a package with a `build` script. That criterion is derived from git rather than hand-listed, so the only way to exempt a path is to state in .gitignore that a build produces it -- an edit that silences a real defect cannot masquerade as one that teaches the guard about a new build directory. Measured on main@dae1ac41e: 40 packages declare `files`, 159 entries, 42 of them legitimately-absent build output. Two entries are real pre-existing defects -- @object-ui/cli and @object-ui/create-plugin both declare a `templates` directory that has never existed in this repo's history. They are carried in a KNOWN_MISSING ratchet keyed to objectui#3665, in the shape scripts/i18n-call-site-key-baseline.json established: a new violation fails the build and a stale entry fails it too, so the map can only shrink. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
The latest updates on your projects. Learn more about Vercel for GitHub. |
yinlianghui
marked this pull request as ready for review
August 7, 2026 18:04
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 7, 2026
Closed
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#3663
问题
npm 对
package.jsonfiles里不存在的条目是静默跳过的:不报错、不警告、退出码 0。于是「声明要打包 X、而 X 不存在」这种 manifest 与事实不符的状态,npm publish、npm pack、CI 全都发现不了。没有输出可读,所以这类漂移不是「不容易注意到」,而是不可见。#3647 就是该机制的标本:
@object-ui/plugin-tree的files写着"LICENSE",而packages/plugin-tree/LICENSE根本不存在 —— 已发布的每一个 tarball 在整个包生命周期里都缺这份 MIT 文本。它是靠人工逐包核对 39 个包的files才被翻出来的(#3622 的排查副产物),不是靠门禁。PR #3662 补回了文件,本 PR 补的是让下一次不可能漏掉的那道门禁。改动
只新增一个文件:
scripts/__tests__/package-files-exist.test.ts(纯静态 vitest 断言)。不新开 workflow:
scripts/**/*.test.ts已被vitest.config.mts的unitproject 收进(第 166 行),随既有 CI 分片跑;.ts也已被tsconfig.scripts.json的scripts/**/*.tsglob 覆盖,pnpm type-check:scripts自动纳管。仓内同类门禁(如workspace-peer-dependency-edges.test.ts)正是这个形态,从先例。形态测量(origin/main @ dae1ac4)
先量后定策。
pnpm-workspace.yaml的四个根(packages/*、apps/*、examples/*、docs)共 40 个包声明了files,合计 159 条条目。条目类型全集(无一条使用 glob 或
!取反,全是字面路径):distREADME.mdCHANGELOG.mdLICENSEsrctemplatessrc/styles.csssrc/schemasplugin.tsplugin.jsplugin.d.ts合计 159 条:117 条在磁盘存在,42 条是合法缺席的构建产物,2 条是真实违规。
豁免策略的取舍
不能简单地「路径必须存在」:159 条里 42 条是构建产物,在未构建的 worktree(CI 跑这条测试时的状态、以及任何人刚 clone 完的状态)里合法缺席。要求它们存在会让门禁对所有人恒红,而恒红的门禁会被删掉或
.skip。所以缺席条目只在仓库已经就别的理由声明过该路径是生成物时才豁免,三个条件同时成立:
.gitignore说构建会写这里),且build脚本(一个什么都不构建的包,不可能正要生成什么)。关键取舍:这个判据是「推导」出来的,不是手写清单。 备选方案是在测试文件里维护一张「受祝福的条目名」白名单(
dist、templates、plugin.js…)。否决它的理由是:白名单必须靠人编辑才能保持正确,而**「掩盖一个真实缺陷」的那次编辑,和「教会门禁认识一个新构建目录」的那次编辑,长得一模一样**。改成推导,则豁免一条路径的唯一途径是在.gitignore里声明构建会生成它 —— 这是一个可评审的主张,错的时候错得很显眼。按 PM 裁定,npm 恒包含的条目(LICENSE / README)不豁免 —— 门禁断言的是「声明与磁盘一致」,与 npm 是否兜底无关。#3662 已经证明:npm 的静默兜底正是潜伏机制本身。
本门禁刻意不证明的事
不证明构建产物真的会被生成。证明那个需要真的跑一次构建,不是静态检查的职责,
dist在这里是采信了仓库的说法。这个缺口窄且被写明:#3647 那一整族缺陷 —— 源文件(LICENSE、README、CHANGELOG、src、templates)被声明却不存在 —— 全部在门禁射程之内,而漂移实际发生的正是那里。另外,
files支持 glob 与!取反,今天一条都没用。单独有一条断言在第一条 glob 落地时报「教会这个 guard 展开 glob」,而不是把它误报成缺失文件。全量扫描复核后,除 #3647 外另有 2 条同类存量,均为残留声明:
@object-ui/clitemplatesdist/src/commands/init.ts:17的对象字面量@object-ui/create-plugintemplatesdist/src/index.ts:115的templateDir赋值后再无引用今天没有用户会撞上(模板已内联,功能正常;npm 又静默跳过),失真的只有 manifest 本身。已按仓规开单 #3665(
finding标签,观察类)。**处置为「挂账」,并说明原因:**本单文件面明确 ⛔ 不碰任何
package.json,而「先清」的正确修法恰恰是从两个package.json的files里删掉这两行 —— 越出文件面。因此这里采用仓内既有的 ratchet 形态(scripts/i18n-call-site-key-baseline.json确立、其自述为「A RATCHET, not an allowlist」):KNOWN_MISSING挂账 2 条并指向 #3665,新增违规会红,而条目一旦被修好、变成陈旧条目也会红,逼着把该行删掉。所以这张表只能缩小,不会沉淀成永久白名单。若 PM 复裁为「先清」(单独一个 PR 删掉那两行
files条目),则本 PR 的KNOWN_MISSING应随之清空 —— 两种路径都收敛到同一个终态。逆向验证(先预测,后跑)
五个方向,含一个反直觉的:
files塞一条不存在的"NOTICE"1 failed | 4 passed,报@object-ui/plugin-tree declares "NOTICE" ... packages/plugin-tree/NOTICE does not existpackages/plugin-tree/LICENSE(重造 #3647 本体)2 failed | 3 passed,两条都点名packages/cli/templates/(即修好#3665)1 failed | 4 passed,红的正是the objectui#3647 baseline only shrinks,报A KNOWN_MISSING entry is stale@object-ui/auth的build脚本dist失去豁免 → 红,证明判据第 3 条是承重的1 failed | 4 passed,报... packages/auth/dist does not exist (and the package has no 'build' script that could create it)Test Files 1 passed, Tests 5 passedC 值得单独说:修好一个缺陷会让测试套件转红,直到把基线里那一行也删掉。 这是 ratchet 的设计意图(缺了这一半,陈旧条目会永远躺在那里,下一个读者会把它当成活着的缺陷),因为反直觉所以显式钉住。
反空绿
门禁自身也可能「什么都没检查却报绿」—— 这恰恰是 npm 静默跳过的同构失败。故有一条发现量下限断言:声明
files的包数 ≥ 38、条目数 ≥ 150(实测 40 / 159),并按名字断言@object-ui/plugin-tree仍在扫描范围内且其files仍含LICENSE。另外git ls-files/git check-ignore任一失败都抛错而非返回空集 —— 返回空集会让所有条目看起来「未跟踪」,从而悄悄放宽豁免,正是本文件存在的意义所反对的。验证
另:写这个文件时踩到了
tsconfig.scripts.json头注记载的同一个坑 —— 在块注释里写一个含*加/的 glob 会提前闭合注释。当时是用零宽空格绕开的,自查(逐码点扫 U+200B/U+FEFF/U+2028 等)发现后已改写措辞消除,不留不可见字符;上面check-control-bytes的 3667 是把新文件git add后的计数,确认它确实被扫到。关于 changeset:判定为不加
AGENTS.md 第 151 行 —— 功能改进(feature)需写 changeset,纯 bug 修复不需要。本 PR 是纯测试/门禁改动,不改任何发布产物、不产生任何用户可见变化,连 bug 修复都不是。CI 也无「每个 PR 必须带 changeset」的门禁(
changeset-guard.yml只在.changeset/**变更时触发)。🤖 Generated with Claude Code
https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Generated by Claude Code