fix: address build config review findings

This commit is contained in:
Tianyi Cui
2026-06-20 00:21:57 +08:00
parent c7e55fc0b1
commit ed94daed9e
36 changed files with 88 additions and 21 deletions
+7 -9
View File
@@ -33,22 +33,20 @@ jobs:
- name: Constraints
run: pnpm run constraints
# Before lint: the type-aware ESLint config resolves vendor packages via
# their built declarations (tsconfig.typecheck.json -> vendor/*/lib),
# which `pnpm run typecheck` emits. Lint on a fresh checkout would otherwise
# see unresolved types and erupt with no-unsafe-* errors.
# Before lint: root typecheck validates the package/vendor reference graph
# and refreshes TSC intermediates so type-aware ESLint sees the same project
# boundaries as the build.
- name: Typecheck (src + tests + examples)
run: pnpm run typecheck
- name: Lint
run: pnpm run lint
# Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the fenced ts blocks in
# the docs and resolves vendor packages via their built declarations, which
# the typecheck step above emits — so it runs after typecheck. The event
# taxonomy check and the markdown wrap check only read source. Same
# Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the
# fenced ts blocks against the root project-reference graph. The event
# taxonomy, markdown wrap, and markdown link checks only read source. Same
# `doc-sync` script the pre-push hook runs (quality-gates RFC: one source of truth).
- name: Doc-sync gates (doc code blocks + event taxonomy + markdown wrap)
- name: Doc-sync gates (doc code blocks + event taxonomy + markdown)
run: pnpm run doc-sync
# Module-graph freshness: regenerate docs/module-graph.md from the
+1 -1
View File
@@ -15,7 +15,7 @@ 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`, `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.
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"`, `exports["."].default: "./lib/index.js"`, `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`, `lib/typings/**/*.d.ts.map`, and `src`; do not publish `lib/typings` JS/map intermediates or stale root declaration files.
## 2. Register it in the root configs
+1 -1
View File
@@ -27,7 +27,7 @@ vendor/<dir>/
}
```
`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`).
`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, point declaration metadata at `lib/typings`, 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`).
## 2. Register it in the root configs
+1 -1
View File
@@ -60,7 +60,7 @@ Do NOT write one for a mechanical or local choice (a variable name, a one-file r
| [Rich ACP bash rendering — the terminal card (`_meta`) and command classification](implemented/2026-06-18-acp-terminal-and-tool-rendering.md) | 2026-06-18 |
| [ACP snapshot tests — record-once / replay-deterministic](implemented/2026-06-19-acp-snapshot-tests.md) | 2026-06-19 |
| [Real-API e2e in CI against the external DeepSeek API](implemented/2026-06-19-real-api-e2e-ci.md) | 2026-06-19 |
| [TSC-first build and one tsconfig](implemented/2026-06-20-ts-build-config.md) | 2026-06-20 |
| [TSC-first build and one tsconfig](implemented/2026-06-17-ts-build-config.md) | 2026-06-17 |
## Rejected
@@ -15,12 +15,12 @@ Build output currently matters only for `pnpm run build` + publint (nothing publ
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 pnpm 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).
- Shared shape: entry `lib/typings/index.js`, `outDir: 'lib'`, ESM, `platform: node`, `target: es2024`, `fixedExtension: false` (keeps `.js` for `"type": "module"` packages), `dts: false` (tsc -b owns declarations), `clean: false` (lib/ also holds TSC's `lib/typings` intermediate tree). The entry was originally `src/index.ts`; the [TSC-first build RFC](2026-06-17-ts-build-config.md) later moved tsdown to bundling TSC-emitted JS so TypeScript transform behavior comes from one compiler.
- 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; `pnpm run build` = `tsc -b && tsdown`.
- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b tsconfig.build.json && 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 RFC.
Runtime bundle outputs still follow the dumble-era public entry shape (`lib/index.js`, plus package-specific variants such as `schemastery`'s `lib/index.mjs`/`lib/index.cjs` and `logger-console`'s `lib/browser.js`); declarations now live under `lib/typings` per the [TSC-first build RFC](2026-06-17-ts-build-config.md). 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 RFC.
@@ -30,14 +30,14 @@ In-package relative imports are extensionless.
`pnpm run build` is a two-stage build:
- Stage 1: `tsc -b tsconfig.build.json` emits publishable per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/typings`. This is the authoritative TypeScript compilation result. For publish we should keep `.d.ts` and ignore `.js` / `.js.map` / `.d.ts.map`
- Stage 1: `tsc -b tsconfig.build.json` emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/typings`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`.
- The build project uses the project-reference graph that `tsc -b` compiles. For example, root `tsconfig.build.json` references package and vendor tsconfigs. It validates and emits package/vendor build results.
- Stage 2: a bundler reads the emitted JS under `lib/typings` and writes the bundled runtime entry as `lib/index.js` or `lib/index.mjs` (follow current behavior). This stage is bundling only. It must not read TypeScript source or emit declarations.
`tsdown` is no longer the owner of TypeScript compilation or declaration output.
`pnpm run typecheck` runs build mode over the root `tsconfig.json`.
- The root `tsconfig.json` is the single development/typecheck project. It has `noEmit` for demos, examples, tests, and scripts, and validates package/vendor source through references.
- The root `tsconfig.json` is the single development/typecheck project. It typechecks examples, tests, and scripts with `noEmit`, and validates package/vendor source through references.
- Referenced package/vendor projects keep the same emit behavior as build, so typecheck can refresh their `lib/typings` outputs instead of using a separate no-emit graph. Project-specific strictness changes live in the owning `packages/*/tsconfig.json` or `vendor/*/tsconfig.json`.
The command orchestration shape is:
@@ -59,7 +59,7 @@ Build responsibilities are clearer:
- Each module under `packages/*` and `vendor/*` has one local tsconfig for build, typecheck, and tools that run source directly, such as `tsx` and `vitest`.
- The `build` command uses `tsconfig.build.json`. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`.
- `lib/typings/*.d.ts` is the publish declaration output.
- `lib/typings/*.d.ts` and `.d.ts.map` are the publish declaration output.
- `lib/typings/*.js` is only a bundler input and must not be used as a runtime entry or public import target.
- `lib/index.*` is the publish runtime output and is generated by the bundler, currently `tsdown`.
- The `typecheck` command uses `tsconfig.json`. Examples, tests, and scripts are checked by the root no-emit project, while packages and vendor modules keep the same emit behavior as `build`. Package and vendor source stays behind project-reference boundaries.
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
+35
View File
@@ -28,6 +28,15 @@ interface PackageManifest {
version?: string
private?: boolean
type?: string
main?: string
types?: string
exports?: {
'.'?: {
types?: string
default?: string
}
}
files?: string[]
peerDependencies?: Record<string, string>
devDependencies?: Record<string, string>
}
@@ -58,6 +67,17 @@ function workspaceManifests(): WorkspaceManifest[] {
return manifests
}
const dshPackageFiles = [
'lib/index.js',
'lib/typings/**/*.d.ts',
'lib/typings/**/*.d.ts.map',
'src',
] as const
function sameStringList(actual: readonly string[] | undefined, expected: readonly string[]): boolean {
return !!actual && actual.length === expected.length && actual.every((value, index) => value === expected[index])
}
function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
const errors: string[] = []
const label = manifest.name ?? dir
@@ -85,6 +105,21 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
if (manifest.type !== 'module') {
errors.push(`${label}: package.json must set "type": "module"`)
}
if (manifest.main !== 'lib/index.js') {
errors.push(`${label}: package.json must set "main": "lib/index.js"`)
}
if (manifest.types !== 'lib/typings/index.d.ts') {
errors.push(`${label}: package.json must set "types": "lib/typings/index.d.ts"`)
}
if (manifest.exports?.['.']?.types !== './lib/typings/index.d.ts') {
errors.push(`${label}: package.json exports["."].types must be "./lib/typings/index.d.ts"`)
}
if (manifest.exports?.['.']?.default !== './lib/index.js') {
errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`)
}
if (!sameStringList(manifest.files, dshPackageFiles)) {
errors.push(`${label}: package.json files must be ${JSON.stringify(dshPackageFiles)}`)
}
}
return errors.map(error => `${relative(root, join(root, dir, 'package.json'))}: ${error}`)
+8 -1
View File
@@ -30,6 +30,13 @@ interface Block {
code: string
}
/** Strip JSONC comments from checked-in tsconfig files before JSON.parse. */
function stripJsonComments(raw: string): string {
return raw
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/.*$/gm, '$1')
}
/** Extract every ```ts / ```ts ignore-check block from one Markdown file. */
function extractBlocks(absPath: string): Block[] {
const text = readFileSync(absPath, 'utf8')
@@ -62,7 +69,7 @@ function extractBlocks(absPath: string): Block[] {
/** Reuse the repo typecheck graph references from a temp project one directory below root. */
function workspaceReferences(): { path: string }[] {
const raw = readFileSync(join(root, 'tsconfig.json'), 'utf8')
const { references } = JSON.parse(raw) as { references: { path: string }[] }
const { references } = JSON.parse(stripJsonComments(raw)) as { references: { path: string }[] }
return references.map(({ path }) => {
const relativeToTemp = path.startsWith('./') ? `../${path.slice(2)}` : `../${path}`
return { path: relativeToTemp }
+2 -2
View File
@@ -31,9 +31,9 @@ Intentionally **not** vendored (verified unused by this set): `reggol`, `@cordis
Keep this log exhaustive — every divergence from upstream must be listed.
1. **`hmr/src/index.ts`**: removed the `./locales/en-US.yml` / `./locales/zh-CN.yml` imports, the `.i18n({...})` call on the `Config` schema, and the `src/locales/` directory. Rationale: those imports require a runtime YAML loader hook (`@cordisjs/unyaml`) that we do not vendor; the i18n texts only localize config descriptions.
2. **All `package.json` files**: regenerated — added `private: true`, added precise `files` entries for bundled runtime files and `lib/typings/**/*.d.ts`, preserved `src` in `files` only for packages whose previous file list already shipped it, added a `./src/*` export where missing, pointed declaration metadata at `lib/typings`, and removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency.
2. **All `package.json` files**: regenerated — added `private: true`, added precise `files` entries for bundled runtime files and `lib/typings/**/*.d.ts` / `.d.ts.map`, preserved `src` in `files` only for packages whose previous file list already shipped it, added a `./src/*` export where missing, pointed declaration metadata at `lib/typings`, and removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency.
3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json`, emit TypeScript intermediates to `lib/typings`, and declare project references.
4. **`loader/src/config/isolate.ts`**: changed the internal declaration merge specifier from `declare module './entry.ts'` to `declare module './entry'` so generated declarations are extensionless and no declaration postprocess is needed.
4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from explicit `.ts` / `.js` specifiers to extensionless specifiers so generated `.js` and `.d.ts` intermediates are extensionless and no declaration postprocess is needed. This includes `loader/src/config/isolate.ts` changing `declare module './entry.ts'` to `declare module './entry'`.
5. **`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. They read the JS emitted under `lib/typings` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
## Sync procedure
+1
View File
@@ -19,6 +19,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"bin.js"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -19,6 +19,7 @@
"lib/index.js",
"lib/browser.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -10,6 +10,7 @@
"lib/index.mjs",
"lib/index.cjs",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",
+1
View File
@@ -17,6 +17,7 @@
"files": [
"lib/index.js",
"lib/typings/**/*.d.ts",
"lib/typings/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",