AG-UI 仓库 npm 包许可证策略校验:package-licenses 工具链与 policy.json 详解

原创2026-09-21 18:41:351,619 阅读
文章标签:人工智能AI Agent后端

AG-UI 仓库 npm 包许可证策略校验:package-licenses 工具链与 policy.json 详解

导读:本文聚焦 AG-UI 仓库(Agent-User Interaction Protocol)中用于治理公共 npm 包许可证合规性的 scripts/package-licenses 工具链,围绕 policy.json 策略文件、check.mjs 实现、test / check / pack / archive 四种运行模式展开讲解。读完本文,你将掌握 AG-UI 是如何以“策略文件驱动”的方式统一校验源码清单与真实打包产物的 SPDX 字段和完整 LICENSE 文本,并理解其在发布前检查、标签修复流程中的落地方式,可直接复用于你自己的多包 Monorepo 仓库。

一、什么是 package-licenses:策略与检查分离的许可证治理

在 AG-UI 这类包含数十个 TypeScript 包(SDK、中间件、框架集成)的 Monorepo 中,许可证合规是发布前的硬性门槛。scripts/package-licenses/ 目录下维护了一套不依赖第三方工具的自研检查脚本,核心设计原则是:

  • 策略独立:policy.json 记录每个公共 npm 包及其“预期许可证”,该文件独立于各包 package.json 中的 license 字段——也就是说,策略是权威基准,包清单中的字段是待检对象,两者不一致即视为失败。
  • 新增包必须登记:引入新的公共包时,必须同步在 policy.json 中添加条目;同时,已有第三方包的许可证文本必须原样保留,不得擅自替换。

该目录共包含五个文件:

文件 职责
policy.json 公共 npm 包与预期许可证、许可文本路径的映射表
check.mjs 检查主程序,支持 check / pack / archive 三种模式
check.test.mjs 基于 Node 内置 node:test 的单元测试
project.json Nx 工程定义,声明 test / check / pack 三个 target
README.md 使用说明(本文所依据的主体文档)

二、policy.json:28 个包的许可证策略基准

policy.json 是一个 JSON 数组,当前包含 28 个条目,覆盖三类包:TypeScript SDK 包(sdks/typescript/packages/*)、中间件(middlewares/*)以及各框架集成(integrations/*/typescript)。每个条目包含四个字段:

{
  "directory": "integrations/a2a/typescript",
  "names": ["@ag-ui/a2a"],
  "license": "MIT",
  "notice": "integrations/a2a/typescript/LICENSE"
}
字段 含义 说明
directory 包所在目录(相对仓库根) 用于定位该包的 package.json,也是 archive 模式匹配策略条目的依据
names 允许的包名列表 数组形式,可覆盖同一目录下包名可能变化的情况;verifyArchive 会校验归档内 manifest.name 必须命中该列表
license 预期 SPDX 许可证标识 必须与包 package.json 的 license 字段完全一致
notice 审批通过的 LICENSE 文本文件路径 检查时会将该文件内容与打包产物内的 package/LICENSE 做逐字符(归一化后)比对

从当前策略可以看出许可证分布:

  • 绝大多数包使用 MIT(如 @ag-ui/client、@ag-ui/core、@ag-ui/a2a、@ag-ui/mastra 之外的集成等);
  • Apache-2.0 用于 integrations/community/spring-ai/typescript(@ag-ui/spring-ai)和 integrations/mastra/typescript(@ag-ui/mastra);
  • CLI 包以 create-ag-ui-app 为包名,登记于 sdks/typescript/packages/cli 条目。

值得强调的是,MIT 与 Apache-2.0 在策略中保持互不混同:即使某个包实际写入了合法的 Apache-2.0 标识,只要策略预期是 MIT,检查依然失败(这一点有对应测试用例佐证,见下文第五节)。

三、三个核心命令:test / check / pack

仓库根目录下(Nx 工作区)直接运行即可:

pnpm nx run package-licenses:test
pnpm nx run package-licenses:check
pnpm nx run package-licenses:pack

三个 target 在 project.json 中均通过 nx:run-commands 执行器定义,且全部关闭了 Nx 缓存("cache": false),保证每次运行都真实校验:

target 实际执行的命令 作用
test node --test scripts/package-licenses/check.test.mjs 运行 Node 内置测试运行器,验证 verifyArchive 的各类通过/拒绝分支
check node scripts/package-licenses/check.mjs check 纯源码级检查:扫描所有公共包,核对 package.json 的包名、许可证字段与策略一致
pack node scripts/package-licenses/check.mjs pack 在临时目录真实执行 pnpm pack 产出归档,再逐包校验 SPDX 字段与完整 LICENSE 文本

pack 模式的关键语义

  • 真实产物校验:pack 目标在临时目录(os.tmpdir() 下创建 package-licenses-*)中为每个策略条目执行 pnpm pack --pack-destination <临时目录>,然后对生成的 .tgz 归档运行 verifyArchive,比对的是打包后的 SPDX 字段与完整的 LICENSE 文件,而非源码文件。
  • 不发布:整个过程只打包、不执行任何 npm publish / pnpm publish,可安全地在发布前运行。
  • CLI 包的前置构建:由于 create-ag-ui-app 需要先编译,README 明确要求先在构建环境中执行 CLI_ENV=local pnpm nx build cli,再运行 pack;其他纯源码包无需构建即可用该目标检查源码打包形态。
  • 发布路径的二次把关:正式发布流程在最终产物发布前,也会对构建出的归档再执行一次检查(“The release paths also check the final built archive immediately before publication”),形成“源码检查 + 打包检查 + 发布前检查”的三层防线。

四、check.mjs 实现剖析:verifyArchive 与 checkSources

check.mjs 是全部检查逻辑的实现,可拆为两个核心函数加一个主入口。

4.1 verifyArchive:归档内容校验

verifyArchive(archive, expected) 负责校验单个 .tgz 归档,流程如下:

  1. 清单完整性:通过 tar -tzf 列出归档内文件,强制要求 package/package.json 与 package/LICENSE 恰好各存在一份,多一个或少一个都直接抛错。
  2. 安全读取:通过 tar -xOf <归档> <文件> 将目标文件内容读到标准输出,不落盘、不解压、不执行任何包内代码;同时用 maxBuffer: 1024 * 1024 对单文件读取做上限约束(文件清单读取上限为 16 MB),从实现层面规避了解压炸弹与恶意脚本风险。
  3. 包名校验:解析归档内 package.json,manifest.name 必须命中策略条目的 names 数组,否则抛出 Unexpected package name。
  4. SPDX 字段校验:manifest.license 必须与策略中的 license 完全相等,缺失或不同都会失败。
  5. 完整 LICENSE 文本校验:将归档内 package/LICENSE 与策略 notice 指向的源码文件内容做比对,比较前先经过 normalize()(把 \r\n 统一为 \n 并 trim())——注意,这只是归一化行尾与首尾空白,不会对许可文本本身做任何宽容处理,文本不完整(如只有 MIT 三个字母)或与审批文本不同都会被拒绝。

4.2 checkSources:源码级策略强制

checkSources(entries) 负责扫描源码树,做两件事:

  • 公共包强制登记:用 git ls-files -z "**/package.json" 列出所有被 Git 跟踪的 package.json(-z 以 NUL 分隔,安全处理含空格路径),逐个解析。只要包非私有(无 "private": true)且包名命中 @copilotkit/*、@copilotkitnext/*、@ag-ui/* 命名空间或等于 copilotkit / create-ag-ui-app,就必须存在对应的策略条目,否则抛出 Public package ... needs a license policy entry。这从制度上杜绝了“新增公共包却忘记登记许可证”的漏网情况。
  • 策略条目反向校验:对 policy.json 中每个条目,读取其 directory/package.json,确认包名命中 names、license 字段与策略一致,且 notice 指向的文件非空(空许可证文本文件也会报错)。

主入口 main([mode, archive, directory]) 根据 mode 分发到 archive、check、pack 三条路径;pack 路径对每个条目在其 packDirectory ?? directory 目录下执行 pnpm pack,并在 finally 中清理临时目录。脚本还通过 process.argv[1] 与 import.meta.url 比对,仅在作为主程序直接执行时运行 main,因此 verifyArchive 可被测试文件干净地导入复用。

五、archive 模式:检查单个已有归档

对已经存在的归档文件,无需走完整 pack 流程,可直接用单归档模式:

node scripts/package-licenses/check.mjs archive /absolute/path/package.tgz <policy-directory>

其中 <policy-directory> 必须是策略条目中登记的 directory(如 sdks/typescript/packages/client),脚本据此定位对应的策略条目与 notice 文本。该模式同样遵守“只读不执行”原则:仅通过 tar 流式读取元数据与文本,不提取、不执行任何包内代码。

archive 模式会拒绝以下任一情况:

  • 未知的包名(不在 names 列表中);
  • license 字段缺失或与策略不符;
  • 归档内缺少 package/LICENSE;
  • LICENSE 文本不完整或与审批文本不一致。

六、测试用例:verifyArchive 的行为契约

check.test.mjs 使用 Node 内置 node:test + assert/strict 编写,不依赖任何第三方测试框架。每个测试通过 setup() 在临时目录里手工构造一个“真实 npm 形状”的归档(tar -czf 打出 package/ 目录),随后直接调用导入的 verifyArchive。8 个用例完整覆盖了通过/拒绝矩阵:

测试用例 预期行为
预期许可证 + 完整 LICENSE 文本 通过
有 LICENSE 文件但 SPDX 字段缺失 拒绝(license must be MIT)
合法但不同的 SPDX 值(如 Apache-2.0 预期 MIT) 拒绝
缺少 LICENSE 文件 拒绝(LICENSE)
文本截断或不同(如只写 MIT) 拒绝(notice)
归档属于其他包 拒绝(name)
预期即为 Apache-2.0 通过(证明各许可证保持独立、不被替换)
商用包以 UNLICENSED + 完整 notice 通过

这套测试把“MIT 与 Apache-2.0 互不混淆”“UNLICENSED 商用包可单独放行”“缺字段即失败”等关键策略固化为可回归的行为契约,任何对 verifyArchive 的改动都必须先通过这些用例。

七、发布与标签修复的合规流程

README 对发布环节给出了明确的许可证治理纪律:

  • 发布修复闭环:通过正常发布流程发布新版本后,必须核对 registry 上的元数据并下载其归档再次检查,确认无误后才可将该版本标记为“已修复”。
  • 标签只指向已验证版本:仅允许把受支持的标签(如 latest)更新到经过验证的版本;操作时需要记录旧标签与新标签各自的指向,以便追溯。
  • 旧标签不轻易删除:未经评审不得移除旧标签;同时,历史版本一旦发布即不可变,即使后续用修正后的版本更新了默认标签,历史不可变版本仍可能保留缺失的元数据——即“修正默认标签 ≠ 历史版本自动合规”,需要在发布说明中如实反映。

这一节本质上是把“许可证检查”与“发布事故的标签修复”串成一条治理流程:检查工具负责发现与验证,修复流程负责在符合可追溯、可评审的前提下收敛默认标签指向。

八、适用边界与注意事项

最后,README 给出了一句重要的边界说明:许可证检查不能替代完整的依赖或安全审计("A license check does not replace a full dependency or security audit")。这提醒使用者:

  • 本工具只解决“包名 + SPDX 字段 + LICENSE 文本与策略一致”的合规问题,不扫描依赖漏洞、不分析许可证的传染性条款;
  • check 模式仅覆盖 git ls-files 跟踪的 package.json,未纳入 Git 的新增包不会被扫描;
  • pack 模式依赖本机 pnpm 与 tar 环境,且 CLI 包需先构建(CLI_ENV=local pnpm nx build cli)。

对于需要在自有多包仓库复刻这一方案的团队,最小落地路径是:拷贝 policy.json 的结构维护策略表,用 check.mjs 的 checkSources + verifyArchive 两个函数分别守住“源码登记”与“产物文本”两道闸门,再以 check.test.mjs 的用例集保证检查逻辑本身的稳定性——这正是 AG-UI 仓库在数十个公共包场景下维持许可证一致性的可复用工程实践。

登录后查看全文
ag-ui