docs: add ADR TSC-first Build and One TSConfig
This commit is contained in:
@@ -60,6 +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 |
|
||||
|
||||
## Rejected
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# RFC: TSC-first build and one tsconfig
|
||||
|
||||
Status: implemented (accepted 2026-06-20)
|
||||
|
||||
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
|
||||
|
||||
## Context
|
||||
|
||||
The current TypeScript build and typecheck setup had these issues:
|
||||
|
||||
- `build` used `tsc` to transform `.ts` to `.d.ts` files for `packages/*` and `vendor/*`, and then used `tsdown` to transform `.ts` to bundled `.js` files. This made two tools do TypeScript transform.
|
||||
- `typecheck` tended to validate packages, vendor source, examples, tests, and scripts through one root typecheck config.
|
||||
|
||||
The goal is to make build and typecheck use matching tsconfig boundaries and TypeScript resolution/transform behavior. Build should generate `.js`, `.d.ts`, `.js.map`, and `.d.ts.map` through one compiler and config, so publish output and type validation stay consistent.
|
||||
|
||||
Validation found several concrete technical issues and possible routes:
|
||||
|
||||
- `tsdown` uses `oxc` to transform TypeScript, which is not the same behavior as `tsc`.
|
||||
- Bundled `.d.ts` emitted by `tsdown` conflicts with Cordis' internal relative module augmentation shape.
|
||||
- The tsc output is affected by `allowImportingTsExtensions`, so we need to ensure that generated `.js` files do not import `.ts` files and generated `.d.ts` files do not import `.js` files. Therefore, we need to adjust the import specifiers to extensionless in the TypeScript source.
|
||||
- Bundled `.js` emitted by `tsdown` is not the same behavior as per-file `.js` emitted by `tsc -b`, such as decorator transform behavior.
|
||||
- `vendor/*/src`, examples, tests, and scripts cannot all be plain-included in one root strict program.
|
||||
- Directly typechecking `vendor/*/src` under the root strict config triggers many type errors outside this project's ownership.
|
||||
- `package/*` dependencies on `vendor` are resolved to the `vendor/*/lib` for different tsconfig strictness.
|
||||
|
||||
|
||||
## Decision
|
||||
|
||||
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`
|
||||
- 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.
|
||||
- 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:
|
||||
|
||||
```sh
|
||||
pnpm run build:
|
||||
tsc -b tsconfig.build.json
|
||||
tsdown
|
||||
|
||||
pnpm run typecheck:
|
||||
tsc -b tsconfig.json
|
||||
```
|
||||
|
||||
`pnpm run demo:*` still runs `src` directly through tsx and root paths, without a compile step.
|
||||
|
||||
## Consequences
|
||||
|
||||
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/*.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.
|
||||
|
||||
The Cordis vendor copy now has one more type-structure divergence from upstream. During upstream sync, that divergence must be reapplied or explicitly retired.
|
||||
Reference in New Issue
Block a user