Explorar o código

Replace dumble with tsdown for JS bundling

dumble (0.2.x, ~530 dl/wk, single-maintainer) was a bus-factor risk as
the load-bearing bundler. tsdown (rolldown-based, ~2.5M dl/wk, actively
maintained) replaces it while output stays list-identical, verified by
snapshot diff: 17 JS bundles, externals preserved, schemastery dual
.mjs/.cjs and logger-console node+browser entries intact.

Root tsdown.config.ts uses workspace globs ['vendor/*', 'packages/*']
(explicit, so examples/* stays excluded); two per-package overrides in
vendor/ cover the special shapes and are logged in vendor/README.md as
ours (not upstream sync surface). scripts/build.ts (dumble
orchestration) is deleted; yarn build = tsc -b && tsdown. tsc -b keeps
owning declarations (dts: false, clean: false).

Rationale recorded in ADR 0008 (also covers the direct-esbuild and
pkgroll alternatives).

Gates: lint, typecheck, 134 tests, hygiene (knip/publint/constraints),
demo smoke all green.
Tianyi Cui hai 3 meses
pai
achega
630bbddf9a

+ 4 - 2
AGENTS.md

@@ -32,7 +32,9 @@ examples/    Runnable demos (not workspaces). echo-agent = mock model + echo
 docs/        architecture.md — the design doc. adr/ — decision records (the
              why behind vendoring, event-sourcing, the schema DSL, …).
              rfc/ — proposals for substantial future work.
-scripts/     build.ts — dumble JS bundling for all packages.
+scripts/     repo maintenance scripts (vendor-manifest guard, publint runner).
+             JS bundling is tsdown (root tsdown.config.ts + two per-package
+             overrides in vendor/).
 ```
 
 ## Commands
@@ -41,7 +43,7 @@ scripts/     build.ts — dumble JS bundling for all packages.
 yarn install        # Yarn 4 workspaces (node-modules linker), node >= 24
 yarn test           # vitest run (packages/*/tests/**/*.spec.ts)
 yarn typecheck      # tsc -b tsconfig.build.json (declarations only)
-yarn build          # typecheck + dumble JS bundles into each package's lib/
+yarn build          # typecheck + tsdown JS bundles into each package's lib/
 yarn demo           # run examples/echo-agent (needs --expose-internals, the
                     # script passes it; type "echo hi" to see a tool call)
 ```

+ 52 - 0
docs/adr/0008-tsdown-over-dumble.md

@@ -0,0 +1,52 @@
+# ADR 0008: tsdown for JS bundling instead of dumble
+
+Status: accepted (2026-06-11)
+
+## Context
+
+The initial build used **dumble**, the cordiverse zero-config esbuild wrapper
+that upstream Cordis itself builds with — maximum alignment with the vendored
+packages' conventions (it reads each package.json and infers entries/formats
+from the `exports` field). But dumble is a liability as a load-bearing tool in
+this repo: v0.2.x, ~530 npm downloads/week, effectively one maintainer, and we
+were invoking it through a custom orchestration script (`scripts/build.ts`)
+because it has no workspace mode.
+
+Build output currently matters only for `yarn build` + publint (nothing
+publishes yet; dev/test/demo run unbuilt via tsx), so the switching cost is at
+its lowest now and only grows once packages publish.
+
+## Decision
+
+Replace dumble with **tsdown** (rolldown-based, ~2.5M downloads/week,
+VoidZero-backed, actively released):
+
+- Root `tsdown.config.ts` with `workspace: ['vendor/*', 'packages/*']`
+  (explicit globs, not `workspace: true`, which would also pick up
+  `examples/*` — they have package.json files but are not yarn workspaces).
+- Shared shape: entry `src/index.ts`, `outDir: 'lib'`, ESM, `platform: node`,
+  `target: es2024`, `fixedExtension: false` (keeps `.js` for
+  `"type": "module"` packages), `dts: false` (tsc -b owns declarations),
+  `clean: false` (lib/ holds tsc's .d.ts output).
+- Two per-package overrides in vendor/ (ours, like the regenerated tsconfigs;
+  logged in vendor/README.md): schemastery (dual `.mjs`/`.cjs` via
+  `outExtensions`), logger-console (two single-entry passes so the shared
+  base class is inlined into each entry instead of a hash-named chunk,
+  matching upstream's published shape).
+- `scripts/build.ts` deleted; `yarn build` = `tsc -b && tsdown`.
+
+Alternatives considered: **direct esbuild script** (most established engine,
+zero wrapper risk, but hand-maintains the per-package spec table tsdown's
+workspace mode gives us); **pkgroll** (closest drop-in philosophically, but
+78k dl/wk and Rollup-based — strictly weaker maintenance story than tsdown);
+**keep dumble** (perfect upstream alignment, unacceptable bus factor).
+
+## Consequences
+
+Output file lists are byte-for-byte-list identical to dumble's (verified by
+snapshot diff at migration time); externals still come from each package's
+dependencies/peerDependencies. We give up dumble's exports-field inference —
+new packages with non-default shapes need a per-package `tsdown.config.ts`
+instead of just package.json fields. Future option: tsdown could also absorb
+declaration bundling (isolatedDeclarations) if `tsc -b` ever becomes the
+bottleneck; that would be a new ADR.

+ 1 - 0
docs/adr/README.md

@@ -17,3 +17,4 @@ with a new one and cross-link.
 | [0005](0005-custom-schema-dsl-over-schemastery.md) | Custom typed tool-schema DSL instead of schemastery | accepted |
 | [0006](0006-tool-schemas-in-prompt-assembly.md) | Tool schemas are part of the system-prompt assembly | accepted |
 | [0007](0007-quality-gates.md) | Mechanical quality gates over prose guidelines | accepted |
+| [0008](0008-tsdown-over-dumble.md) | tsdown for JS bundling instead of dumble | accepted |

+ 1 - 0
eslint.config.mjs

@@ -24,6 +24,7 @@ export default tseslint.config(
       'vendor/**', // vendored source keeps upstream style and idioms
       '**/*.js',
       '**/*.mjs',
+      '*.config.ts', // root tool configs (vitest, tsdown) — no project service
     ],
   },
 

+ 2 - 2
package.json

@@ -12,7 +12,7 @@
     "packages/*"
   ],
   "scripts": {
-    "build": "tsc -b tsconfig.build.json && tsx scripts/build.ts",
+    "build": "tsc -b tsconfig.build.json && tsdown",
     "typecheck": "tsc -b tsconfig.build.json && tsc -p tsconfig.typecheck.json",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix",
@@ -29,11 +29,11 @@
     "@types/node": "^25.3.5",
     "@vitest/coverage-v8": "^4.1.8",
     "@yarnpkg/types": "^4.0.1",
-    "dumble": "^0.2.3",
     "eslint": "^10.4.1",
     "knip": "^6.16.1",
     "lefthook": "^2.1.9",
     "publint": "^0.3.21",
+    "tsdown": "^0.22.2",
     "tsx": "^4.22.4",
     "typescript": "^6.0.3",
     "typescript-eslint": "^8.61.0",

+ 0 - 29
scripts/build.ts

@@ -1,29 +0,0 @@
-import { execFileSync } from 'node:child_process'
-import { existsSync } from 'node:fs'
-import { resolve } from 'node:path'
-
-// Bundle JS for every workspace package with dumble (tsc -b has already
-// produced the .d.ts files; dumble reads each package's tsconfig/package.json).
-const packages = [
-  'vendor/cosmokit',
-  'vendor/schemastery',
-  'vendor/cordis',
-  'vendor/loader',
-  'vendor/include',
-  'vendor/group',
-  'vendor/timer',
-  'vendor/hmr',
-  'vendor/logger-console',
-  'packages/llm',
-  'packages/session',
-  'packages/system-prompt',
-  'packages/tools',
-  'packages/agent',
-  'packages/agent-loop',
-]
-
-const root = resolve(import.meta.dirname, '..')
-for (const path of packages) {
-  if (!existsSync(resolve(root, path, 'tsconfig.json'))) continue
-  execFileSync('node_modules/.bin/dumble', [path], { cwd: root, stdio: 'inherit' })
-}

+ 26 - 0
tsdown.config.ts

@@ -0,0 +1,26 @@
+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).
+ *
+ * Per-package shape overrides live in `<package>/tsdown.config.ts`
+ * (schemastery: dual ESM+CJS; logger-console: extra browser entry).
+ */
+export default defineConfig({
+  // Explicit globs: `workspace: true` would also discover examples/* (any
+  // package.json), but only vendor/* and packages/* are yarn workspaces.
+  workspace: ['vendor/*', 'packages/*'],
+  entry: ['src/index.ts'],
+  outDir: 'lib',
+  format: ['esm'],
+  platform: 'node',
+  target: 'es2024',
+  // All packages set "type": "module"; fixedExtension false keeps ESM output
+  // at .js (not .mjs), matching the package.json main/exports fields.
+  fixedExtension: false,
+  dts: false,
+  clean: false,
+})

+ 5 - 0
vendor/README.md

@@ -49,6 +49,11 @@ Keep this log exhaustive — every divergence from upstream must be listed.
    peer-dependency ranges preserved.
 3. **All `tsconfig.json` files**: regenerated to extend the repo-root
    `tsconfig.base.json` and declare project references.
+4. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**:
+   ours, not upstream files — per-package build-shape overrides (dual
+   ESM+CJS output; separate node/browser entries) for the repo-root tsdown
+   build. Like the regenerated tsconfigs, they are not part of the upstream
+   sync surface.
 
 ## Sync procedure
 

+ 23 - 0
vendor/logger-console/tsdown.config.ts

@@ -0,0 +1,23 @@
+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.
+ */
+const shared = {
+  outDir: 'lib',
+  format: ['esm'],
+  platform: 'node',
+  target: 'es2024',
+  fixedExtension: false,
+  dts: false,
+  clean: false,
+} as const
+
+export default defineConfig([
+  { ...shared, entry: ['src/index.ts'] },
+  { ...shared, entry: ['src/browser.ts'] },
+])

+ 18 - 0
vendor/schemastery/tsdown.config.ts

@@ -0,0 +1,18 @@
+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.
+ */
+export default defineConfig({
+  entry: ['src/index.ts'],
+  outDir: 'lib',
+  format: ['esm', 'cjs'],
+  platform: 'node',
+  target: 'es2024',
+  outExtensions: ({ format }) => ({ js: format === 'es' ? '.mjs' : '.cjs' }),
+  dts: false,
+  clean: false,
+})

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 393 - 194
yarn.lock


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio