From ec9b093cb0a4e4d7e323fdde9c06bcdf72e640c2 Mon Sep 17 00:00:00 2001 From: imccyu Date: Wed, 17 Jun 2026 23:31:38 +0800 Subject: [PATCH] build: two-step for packages/vendor build and README --- AGENTS.md | 4 ++-- docs/cookbook/adding-a-package.md | 6 +++--- docs/cookbook/adding-a-vendored-package.md | 14 +++++++------- docs/development.md | 2 +- package.json | 1 + packages/README.md | 2 +- pnpm-lock.yaml | 20 ++++++++++++++++++-- tsconfig.base.json | 12 +++++------- tsdown.config.ts | 10 +++++----- vendor/logger-console/tsdown.config.ts | 11 ++++++----- vendor/schemastery/tsdown.config.ts | 8 ++++---- 11 files changed, 53 insertions(+), 37 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0533c4cf0a..e7a6c9e7e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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-` (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`). diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index ed40f242e4..9ae8033fe3 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -7,7 +7,7 @@ The file-by-file checklist for a new `@deepseek-ai/dsh-` package. (Verifie ``` packages// 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, + ../ for each dsh dependency) src/index.ts # service default export or plugin (name/inject/apply/Config) @@ -15,14 +15,14 @@ packages// 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-": ["./packages//src"]` to `paths` | -| `tsconfig.typecheck.json` | same entry (this file overrides the map wholesale) | +| `tsconfig.json` | add `{ "path": "./packages/" }` to `references` | | `tsconfig.build.json` | add `{ "path": "./packages/" }` to `references` | | `scripts/publint-all.ts` | add `'packages/'` 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`) | diff --git a/docs/cookbook/adding-a-vendored-package.md b/docs/cookbook/adding-a-vendored-package.md index fa3c754cb8..fb32d60d06 100644 --- a/docs/cookbook/adding-a-vendored-package.md +++ b/docs/cookbook/adding-a-vendored-package.md @@ -12,13 +12,13 @@ vendor// 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// } ``` -`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 `"": ["./vendor//src"]` to `paths` | -| `tsconfig.typecheck.json` | add `"": ["./vendor//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/" }` to `references` | | `tsconfig.build.json` | add `{ "path": "./vendor/" }` 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//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//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//tsconfig.json`, not pulled into a root strict program. diff --git a/docs/development.md b/docs/development.md index 2270aa0436..ff81ac4079 100644 --- a/docs/development.md +++ b/docs/development.md @@ -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 ``` diff --git a/package.json b/package.json index 50319023cf..f8f79ee63e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/README.md b/packages/README.md index b9fe1aa5f1..25aabd038e 100644 --- a/packages/README.md +++ b/packages/README.md @@ -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//tests/*.spec.ts`. Every registry needs an HMR-safety test. Err on the side of more tests. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5026104cd9..d44608e415 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/tsconfig.base.json b/tsconfig.base.json index d26ab0d56d..56ccab1ccc 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], diff --git a/tsdown.config.ts b/tsdown.config.ts index 3b9039cc36..d6c3cca603 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -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 `/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', diff --git a/vendor/logger-console/tsdown.config.ts b/vendor/logger-console/tsdown.config.ts index 3d9b213b6b..c85dad4a28 100644 --- a/vendor/logger-console/tsdown.config.ts +++ b/vendor/logger-console/tsdown.config.ts @@ -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'] }, ]) diff --git a/vendor/schemastery/tsdown.config.ts b/vendor/schemastery/tsdown.config.ts index 43b4384a06..b16c217750 100644 --- a/vendor/schemastery/tsdown.config.ts +++ b/vendor/schemastery/tsdown.config.ts @@ -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',