Files
deepseek-harness/docs/cookbook/adding-a-vendored-package.md
Tianyi Cui 07f4047ff0 Use explicit ts specifiers for declarations
Restore explicit .ts relative specifiers in source and enable rewriteRelativeImportExtensions so emitted JS uses .js while declarations keep explicit .ts specifiers. Add a NodeNext declaration-consumer gate to prevent extensionless declaration regressions.
2026-06-22 06:11:00 +08:00

3.9 KiB

Cookbook: adding a vendored package

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.json add { "path": "./vendor/<dir>" } to references
tsconfig.build.json add { "path": "./vendor/<dir>" } to references (before the packages/* entries)
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, eslint.config.mjs. 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 test && pnpm run constraints

The source paths map is shared by build and root typecheck configs. The important isolation boundary is the project-reference graph: vendored source must be referenced through its own vendor/<dir>/tsconfig.json, not pulled into a root strict program.