ByteNoteByteNote
把上游源码搬进仓库:vendored 包添加清单
字

字节笔记本

2026年10月6日 · 约 9 分钟读完

把上游源码搬进仓库:vendored 包添加清单

API中转
¥120

DeepSeek Harness 建在 Cordis 框架之上,而 Cordis core 在仓库起步时还只是一个 release candidate。框架的 fiber 生命周期、effect 清理、瀑布派发这些内部行为,直接决定 agent 循环的正确性,上游一次 RC 升级就可能在没有本地修复路径的情况下打破它们。所以这个仓库定下了一条规矩:需要引入新的上游 Cordis 包(比如 @cordisjs/plugin-http)时,不把它加成 npm 依赖,而是把源码按固定版本整个 vendor 进 vendor/ 目录,把框架层握在自己手里:可审计、可打补丁、版本钉死。vendor/README.md 记录的是如何更新已有的 vendored 包,本文对应另一半工作:从零添加一个全新的 vendored 包,要逐个文件动哪些地方。这份清单已对照仓库现有的 vendored 集合验证过。

DeepSeek Harness 添加 vendored 包四步流程总览

第一步:复制源码

新 vendored 包的目录结构是固定的:package.json 来自上游,但要设为私有、改写 scope 并保留 exports 与 type;tsconfig.json 继承仓库根上的基础配置;src/ 下是原样复制的上游源码;上游自带 README 和 LICENSE 就一并保留。

text
vendor/<dir>/
  package.json     # 来自上游:设 "private": true,改写 scope,保留 exports/type
  tsconfig.json    # extends ../../tsconfig.base.json
  src/             # 上游 src/ 原样复制
  README.md LICENSE # 上游自带则保留

tsconfig.json 要与其他 vendored 包保持一致:rootDir 指向 src,outDir 指向 lib/types,按上游代码的需要放宽若干严格性开关,并为它导入的每个其他 vendored 包添加 references 条目:

jsonc
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src", "outDir": "lib/types",
    "noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false,
    "noImplicitOverride": false, "noUnusedLocals": false, "noUnusedParameters": false
  },
  "include": ["src"],
  "references": [{ "path": "../cordis" }, { "path": "../cosmokit" }]
}

package.json 有几条不变式:private 固定为 true,vendored 包永不发布;name 按仓库的映射表改写 scope(比如 @cordisjs/plugin-loader 变成 @deepseek-ai/cordis-plugin-loader),上游的 version、exports、type 原样保留;声明相关的元数据指向 lib/types,构建输出 .d.ts 与 .d.ts.map;peerDependencies 列出它依赖的 Cordis 包,与上游 manifest 保持一致。改名字只动 key 不动范围:依赖条目里的版本范围一个字符都不改,由工作区的 linkWorkspacePackages 把它们解析到钉死的 vendored 版本上。

还有一条容易被忽略:传递依赖也要跟着进来。vendor 一个包往往意味着 vendor 它的整条依赖树,比如 @cordisjs/plugin-http 会拉入 @cordisjs/fetch-file,后者要么一并 vendor,要么已经存在于仓库中。

vendored TypeScript 源码里的本地相对导入与导出,复制之后要写成显式的 .ts 后缀。这是仓库本地构建与上游唯一的差异点:rewriteRelativeImportExtensions 让运行时输出 .js 导入,而声明文件保留显式 .ts 后缀,NodeNext/Node16 模式下的 TypeScript 消费方才能正确解析。

vendored 包目录结构与隔离边界

第二步:在根配置中注册

文件修改内容
tsconfig.base.json在 paths 中添加 "<npm-name>": ["./vendor/<dir>/src"]
tsconfig.host.json在 references 中添加 { "path": "./vendor/<dir>" }(置于 packages/* 条目之前,vendored 代码只经 host 聚合进图)
vendor/README.md添加一行 manifest 表格行(目录、npm 名、版本、上游仓库、commit SHA),并记录所有本地修改
scripts/publint-all.ts仅当该 vendored 包本身要从这个仓库发布时才需要;vendored 依赖通常不发布,直接跳过

另一批文件由 glob 自动覆盖,无需手动编辑:根 package.json 的 workspaces(vendor/*)、tsdown.config.ts、vitest.config.ts、.oxlintrc.json。只有当构建配置与根默认值不同(比如双 ESM/CJS 输出或多入口,vendor/schemastery 和 vendor/logger-console 就是两个现成例子),才需要单独写 vendor/<dir>/tsdown.config.ts,其入口应读取 lib/types 下输出的 JS。

第三步:留意 manifest 守卫

scripts/check-vendor-manifest.sh 是一个 pre-commit 钩子:当 vendor/*/src 下有暂存的改动,而 vendor/README.md 没有一起暂存时,提交会直接失败。换句话说,源码动到哪里,manifest 的表格行与本地修改记录就要跟到哪里,两者必须在同一个提交里。这个守卫保证 manifest 永远可信:每个包对应的上游仓库与 commit SHA 可查,每一次本地偏移都有迹可循。

第四步:验证

text
pnpm install        # 把新包注册进 workspace
pnpm run typecheck
pnpm run build && pnpm run constraints

在此基础上,再按仓库的测试要求补上选定的行为检查。源码 paths 映射在 tsconfig.base.json 里只有一份,服务所有 TypeScript 程序;真正的隔离边界是 project reference 图:vendored 源码必须通过它自己的 vendor/<dir>/tsconfig.json 被引用,而不是被拉进某个聚合了严格检查配置的 TypeScript 程序。

为什么值得抄这套做法

这套清单的价值不止于 DeepSeek Harness 自身。任何依赖行为敏感、上游节奏不可控的 TypeScript 项目,都可以用同样的组合把关键依赖变成自己的代码:源码进仓库并把版本钉死,manifest 记录每一次与上游的偏移,pre-commit 钩子保证记录不缺席。四步走完,新包同时拿到上游的维护成果和本地修补的自由,代价只是几处机械的配置注册,以及一个把源码和 manifest 放进同一提交的习惯。

相关文章

分享: