build: two-step for packages/vendor build and README

This commit is contained in:
imccyu
2026-06-19 23:35:47 +08:00
parent 279e9f17eb
commit ec9b093cb0
11 changed files with 53 additions and 37 deletions
+2 -2
View File
@@ -88,7 +88,7 @@ pnpm run typecheck # tsc -b tsconfig.build.json (declarations) + tsc -p
# tsconfig.typecheck.json (tests/examples typecheck too)
pnpm run lint # eslint .
pnpm run lint:fix # eslint . --fix
pnpm run build # tsc -b tsconfig.build.json && tsdown (JS bundles into lib/)
pnpm run build # tsc emits lib/typings, then tsdown bundles runtime lib/index.*
pnpm run knip # dead-code / unused-dependency check
pnpm run publint # package.json publish-correctness check (publishable packages/*)
pnpm run hygiene # knip + publint + workspace constraints
@@ -126,7 +126,7 @@ Dev/test/demo run **unbuilt** via tsx + the `paths` map in the root `tsconfig.js
## Conventions
- **Package naming**: every npm package in this repo is `@deepseek-ai/dsh-<name>` (vendored packages keep their upstream names and are `private: true`).
- **ESM everywhere** (`"type": "module"`); imports between workspace packages use package names, never relative paths across package boundaries. In-package imports use explicit `.ts` extensions (allowImportingTsExtensions).
- **ESM everywhere** (`"type": "module"`); imports between workspace packages use package names, never relative paths across package boundaries. In-package relative imports are extensionless so generated `.d.ts` files stay extensionless; `lib/typings/**/*.js` is a bundler-only intermediate, not a Node ESM entrypoint.
- **`cordis` is a peerDependency** (+ devDependency) of every harness package, mirroring upstream convention.
- **Registrations are effects**: anything a plugin contributes (adapter, tool, section, agent, event listener) goes through `ctx.effect()` / `ctx.on()` so disposal and HMR work. If you write a registry, `register()` must return the disposer.
- **Typed events via declaration merging**: services declare their events in `declare module 'cordis' { interface Events { … } }`, and their ctx key in `interface Context`. Extensible unions use the merge-extensible-map pattern (see `ContentBlockMap`, `MessageSourceMap`).
+3 -3
View File
@@ -7,7 +7,7 @@ The file-by-file checklist for a new `@deepseek-ai/dsh-<name>` package. (Verifie
```
packages/<name>/
package.json # copy from packages/tools, adjust name/description/deps
tsconfig.json # extends ../../tsconfig.base.json, rootDir src, outDir lib,
tsconfig.json # extends ../../tsconfig.base.json, rootDir src, outDir lib/typings,
# references: vendor/cosmokit, vendor/cordis (+ vendor/schemastery
# if you use Config, + ../<dep> for each dsh dependency)
src/index.ts # service default export or plugin (name/inject/apply/Config)
@@ -15,14 +15,14 @@ packages/<name>/
README.md # service API, events, extension points, design notes
```
package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop.
package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/typings/index.d.ts"`, `exports["."].types: "./lib/typings/index.d.ts"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/typings/**/*.d.ts`, and `src`; do not publish `lib/typings` JS/map intermediates or stale root declaration files.
## 2. Register it in the root configs
| File | Change |
|---|---|
| `tsconfig.base.json` | add `"@deepseek-ai/dsh-<name>": ["./packages/<name>/src"]` to `paths` |
| `tsconfig.typecheck.json` | same entry (this file overrides the map wholesale) |
| `tsconfig.json` | add `{ "path": "./packages/<name>" }` to `references` |
| `tsconfig.build.json` | add `{ "path": "./packages/<name>" }` to `references` |
| `scripts/publint-all.ts` | add `'packages/<name>'` to the array |
| `knip.json` | only if the package has non-`*.spec.ts` entries (e.g. `*.e2e.ts` → add a per-workspace override like `packages/llm-deepseek`) |
+7 -7
View File
@@ -12,13 +12,13 @@ vendor/<dir>/
README.md LICENSE # if upstream ships them
```
`tsconfig.json` mirrors the other vendored packages — `rootDir: src`, `outDir: lib`, the strictness relaxations upstream code needs, and a `references` entry for every other vendored package it imports:
`tsconfig.json` mirrors the other vendored packages — `rootDir: src`, `outDir: lib/typings`, the strictness relaxations upstream code needs, and a `references` entry for every other vendored package it imports:
```jsonc
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src", "outDir": "lib",
"rootDir": "src", "outDir": "lib/typings",
"noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false,
"noImplicitOverride": false, "noUnusedLocals": false, "noUnusedParameters": false
},
@@ -27,19 +27,19 @@ vendor/<dir>/
}
```
`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, 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`).
`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, point declaration metadata at `lib/typings`, 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`).
## 2. Register it in the root configs
| File | Change |
|---|---|
| `tsconfig.base.json` | add `"<npm-name>": ["./vendor/<dir>/src"]` to `paths` |
| `tsconfig.typecheck.json` | add `"<npm-name>": ["./vendor/<dir>/lib"]` — this file points at built declarations, not src. If the package's `types` entry isn't `lib/index.d.ts`, point at that built file instead (e.g. `logger-console` maps to `./vendor/logger-console/lib/shared`, matching its `"types": "lib/shared.d.ts"`). |
| `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`).
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/typings`.
## 3. Mind the manifest guard
@@ -49,8 +49,8 @@ Covered automatically by globs — no edits needed: root `package.json` workspac
```sh
pnpm install # registers the workspace
pnpm run typecheck # the base→lib path split means: run once after a fresh add
pnpm run typecheck
pnpm run build && pnpm run test && pnpm run constraints
```
Note the `tsconfig` two-map split (called out in [AGENTS.md](../../AGENTS.md) § Secrets/.env): `lint`'s type-aware rules resolve vendored packages through their built `lib/` declarations, so run `pnpm run typecheck` (which builds them) once after adding the package or lint reports unresolved-type errors.
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.
+1 -1
View File
@@ -98,7 +98,7 @@ pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README
pnpm run doc-sync # doc-typecheck, event taxonomy, and markdown wrap verification
pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps
pnpm run verify-module-graph # fail if docs/module-graph.md is stale
pnpm run build # build declarations and JS bundles
pnpm run build # emit lib/typings intermediates, then bundle lib/index.* runtime files
pnpm run hygiene # knip, publint, and workspace constraints
```
+1
View File
@@ -13,6 +13,7 @@
],
"scripts": {
"build": "tsc -b tsconfig.build.json && tsdown",
"clean:build": "rm -rf .typecheck packages/*/lib vendor/*/lib *.tsbuildinfo",
"typecheck": "tsc -b tsconfig.build.json && tsc -p tsconfig.typecheck.json",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
+1 -1
View File
@@ -60,5 +60,5 @@ Each package has its own `README.md` with purpose, service API, events, extensio
- **Declaration merging for events and ctx**: services declare their events in `declare module 'cordis' { interface Events { ... } }` and their ctx key in `interface Context`.
- **Waterfall semantics**: `ctx.waterfall` listeners receive `(...args, next)` and MUST call `next()` to delegate; returning without it short-circuits (the veto mechanism).
- **Extensible unions**: `ContentBlockMap`, `MessageSourceMap`, `FinishReasonMap`, `TurnTriggerMap`, `TurnEndReasonMap`, and `SessionEventMap` use the merge-extensible-map pattern so plugins can add variants via declaration merging.
- **ESM everywhere**; imports use package names across package boundaries, `.ts` extensions within a package.
- **ESM everywhere**; imports use package names across package boundaries and extensionless relative specifiers within a package.
- **Tests**: vitest, colocated under `packages/<name>/tests/*.spec.ts`. Every registry needs an HMR-safety test. Err on the side of more tests.
+18 -2
View File
@@ -49,7 +49,7 @@ importers:
version: 0.3.21
tsdown:
specifier: ^0.22.2
version: 0.22.2(oxc-resolver@11.20.0)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3)
version: 0.22.2(oxc-resolver@11.20.0)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1)
tsx:
specifier: ^4.22.4
version: 4.22.4
@@ -2620,6 +2620,16 @@ packages:
unist-util-visit@5.1.0:
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
unrun@0.3.1:
resolution: {integrity: sha512-onIck/oNnCaytwths1ZVp1LK2Gq2hPoyFhiHebObuUXqR3S0uHuLLaBK8K6mRRgV7Ptip8AnNvaUsgzwWwBZuA==}
engines: {node: ^22.13.0 || >=24.0.0}
hasBin: true
peerDependencies:
synckit: ^0.11.11
peerDependenciesMeta:
synckit:
optional: true
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -4933,7 +4943,7 @@ snapshots:
optionalDependencies:
typescript: 6.0.3
tsdown@0.22.2(oxc-resolver@11.20.0)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3):
tsdown@0.22.2(oxc-resolver@11.20.0)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1):
dependencies:
ansis: 4.3.1
cac: 7.0.0
@@ -4954,6 +4964,7 @@ snapshots:
publint: 0.3.21
tsx: 4.22.4
typescript: 6.0.3
unrun: 0.3.1
transitivePeerDependencies:
- '@ts-macro/tsc'
- '@typescript/native-preview'
@@ -5015,6 +5026,11 @@ snapshots:
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
unrun@0.3.1:
dependencies:
rolldown: 1.1.1
optional: true
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
+5 -7
View File
@@ -4,12 +4,12 @@
"module": "esnext",
"moduleResolution": "bundler",
"declaration": true,
"emitDeclarationOnly": true,
"sourceMap": true,
"declarationMap": true,
"composite": true,
"incremental": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": false,
"strict": true,
"noUncheckedIndexedAccess": true,
@@ -19,11 +19,9 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": ["node"],
// Source-level resolution for the build graph: without this, a fresh
// checkout's first `tsc -b` resolves sibling vendor plugins through their
// package.json types (vendor/*/lib/*.d.ts) which don't exist yet — TS2307
// until a second run. Derived configs that want lib resolution
// (tsconfig.typecheck.json) override this map wholesale.
// Source-level resolution for every repo-local graph. Project references,
// not declaration path aliases, keep each package/vendor source compiled
// under its own tsconfig boundary.
"paths": {
"cordis": ["./vendor/cordis/src"],
"cosmokit": ["./vendor/cosmokit/src"],
+5 -5
View File
@@ -1,10 +1,10 @@
import { defineConfig } from 'tsdown'
/**
* JS bundling for all workspace packages (vendor/* + packages/*).
* Declarations are NOT produced here — `tsc -b tsconfig.build.json` owns
* .d.ts output (composite project references); hence `dts: false` and
* `clean: false` (lib/ already holds tsc's declarations).
* Runtime bundling for all workspace packages (vendor/* + packages/*).
* TypeScript source is compiled first by `tsc -b tsconfig.build.json`; tsdown
* reads only the emitted JS under lib/typings and writes lib/index.* runtime
* bundles. Declarations are NOT produced here, hence `dts: false`.
*
* Per-package shape overrides live in `<package>/tsdown.config.ts`
* (schemastery: dual ESM+CJS; logger-console: extra browser entry).
@@ -13,7 +13,7 @@ export default defineConfig({
// Explicit globs: `workspace: true` would also discover examples/* (any
// package.json), but only vendor/* and packages/* are pnpm workspaces.
workspace: ['vendor/*', 'packages/*'],
entry: ['src/index.ts'],
entry: ['lib/typings/index.js'],
outDir: 'lib',
format: ['esm'],
platform: 'node',
+6 -5
View File
@@ -3,9 +3,10 @@ import { defineConfig } from 'tsdown'
/**
* logger-console ships two entries: the node exporter (index) and the
* browser exporter (browser), selected via package.json `exports`
* conditions. They are built as two single-entry passes so the shared
* base class is inlined into each (matching upstream's published shape)
* instead of split into a hash-named chunk.
* conditions. The entries are JS emitted by tsc under lib/typings and are
* bundled as two single-entry passes so the shared base class is inlined into
* each (matching upstream's published shape) instead of split into a hash-named
* chunk.
*/
const shared = {
outDir: 'lib',
@@ -18,6 +19,6 @@ const shared = {
} as const
export default defineConfig([
{ ...shared, entry: ['src/index.ts'] },
{ ...shared, entry: ['src/browser.ts'] },
{ ...shared, entry: ['lib/typings/index.js'] },
{ ...shared, entry: ['lib/typings/browser.js'] },
])
+4 -4
View File
@@ -2,12 +2,12 @@ import { defineConfig } from 'tsdown'
/**
* schemastery has no `"type": "module"` and publishes dual-format output
* (package.json: main → lib/index.cjs, module → lib/index.mjs). Pin the
* extensions explicitly — the defaults for a CommonJS package would emit
* .mjs/.js instead.
* (package.json: main → lib/index.cjs, module → lib/index.mjs). The entry is
* the JS emitted by tsc under lib/typings; pin the bundled extensions
* explicitly because the defaults for a CommonJS package would emit .mjs/.js.
*/
export default defineConfig({
entry: ['src/index.ts'],
entry: ['lib/typings/index.js'],
outDir: 'lib',
format: ['esm', 'cjs'],
platform: 'node',