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.
This commit is contained in:
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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' })
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
Vendored
+5
@@ -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,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'] },
|
||||
])
|
||||
Vendored
+18
@@ -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,
|
||||
})
|
||||
Reference in New Issue
Block a user