Files
deepseek-harness/docs/cookbook/adding-a-vendored-package.md
T

4.0 KiB

Cookbook: adding a vendored package

English | 中文

When the harness needs another upstream Cordis package (e.g. @cordisjs/plugin-http), it is vendored as pinned source under vendor/, not added as an npm dependency — see the vendoring decision for why. vendor/README.md covers updating an already-vendored package; this guide is the file-by-file checklist for adding a new one. (Verified against the existing vendored set; if it drifts, fix it here.)

1. Copy the source in

vendor/<dir>/
  package.json     # from upstream; set "private": true, keep name/exports/type
  tsconfig.json    # extends ../../tsconfig.base.json (see shape below)
  src/             # the upstream src/ verbatim
  README.md LICENSE # if upstream ships them

tsconfig.json mirrors the other vendored packages — rootDir: src, outDir: lib/types, the strictness relaxations upstream code needs, and a references entry for every other vendored package it imports:

{
  "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 invariants: "private": true (vendored packages are never published), keep upstream's name/version/exports/type, point declaration metadata at lib/types, publish .d.ts and .d.ts.map declaration outputs, and list its cordis deps in peerDependencies (matching the upstream manifest). Transitive upstream deps must themselves be vendored or already present — vendoring one package often means vendoring its dependency tree (e.g. @cordisjs/plugin-http pulls @cordisjs/fetch-file).

Local relative imports/exports in vendored TypeScript source use explicit .ts specifiers after copying. This is a repo-local build-shape divergence from upstream: rewriteRelativeImportExtensions emits .js runtime imports while declarations keep explicit .ts specifiers that NodeNext/Node16 TypeScript consumers can resolve.

2. Register it in the root configs

File Change
tsconfig.base.json add "<npm-name>": ["./vendor/<dir>/src"] to paths
tsconfig.host.json add { "path": "./vendor/<dir>" } to references (before the packages/* entries; vendored code enters the graph through the host aggregate only)
vendor/README.md add a manifest table row (dir, npm name, version, upstream repo, commit SHA) and log any local modifications
scripts/publint-all.ts only if the vendored package is itself published from here (vendored deps normally are not — skip)

Covered automatically by globs — no edits needed: root package.json workspaces (vendor/*), tsdown.config.ts, vitest.config.ts, .oxlintrc.json. A per-package vendor/<dir>/tsdown.config.ts is needed ONLY if the build shape diverges from the root default (dual ESM/CJS or multiple entries — see vendor/schemastery and vendor/logger-console); its entry should read the JS emitted under lib/types.

3. Mind the manifest guard

scripts/check-vendor-manifest.sh (a pre-commit hook) fails if anything under vendor/*/src is staged without vendor/README.md also staged. Stage the manifest update alongside the source so the commit passes.

4. Verify

pnpm install        # registers the workspace
pnpm run typecheck
pnpm run build && pnpm run constraints

Run the behavior checks selected by the testing policy. The source paths map lives once in tsconfig.base.json and serves every graph. The important isolation boundary is the project-reference graph: vendored source must be referenced through its own vendor/<dir>/tsconfig.json, not pulled into an aggregate's strict program (layout).