Skip to content

build: migrate from tsc to tsdown - #395

Merged
dinwwwh merged 1 commit into
mainfrom
claude/charming-lamport-1i70iz
Nov 9, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/charming-lamport-1i70iz

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Nov 9, 2026

Copy link
Copy Markdown
Member

Why

Builds move from per-package tsc to tsdown with one shared root config, the same migration as middleapi/orpc#2448 in a simpler form. This repo needs neither oRPC's re-export hook for Expo tree shaking nor its runtime-module list. Output keeps tsc's shape (one .js and .d.ts per source module), so publishConfig.exports stays as it is.

Changes

  • One root tsdown.config.ts. tsdown finds it from each package directory, so every prepack is just tsdown. Entries come from each package's exports; unbundle keeps one file per module, and packages stay external (neverBundle: true).
  • Declarations come from oxc (dts: { generator: 'oxc' }), which needs explicit types on exports. All sources already had them, and a missing one now fails the build.
  • Undeclared imports fail the build. deps.onlyImport lists each package's dependencies and peerDependencies. src has no node: imports, so the platform stays neutral.
  • Bundler module resolution. The root tsconfig uses module: preserve with moduleResolution: bundler, and the .ts extension is dropped from relative imports in packages. dist still imports with .js extensions. verbatimModuleSyntax stays: without it pnpm check misses type-only re-exports, which then only fail at build time.
  • Cleanup and bumps. The emit-only options in tsconfig.lib.json are gone; oxlint, oxlint-tsgolint and oxfmt are bumped.

Verification

  • pnpm check, pnpm test (377 tests) and all 3 package builds pass without warnings.
  • Against the published 0.7.1 tarballs:
    • All 7 entry points export the same names and value types at runtime.
    • The public API is semantically unchanged, checked with the TypeScript compiler API.
    • JSDoc is preserved (704 blocks before and after).
  • The emitted declarations type-check for a nodenext consumer with skipLibCheck: false and exactOptionalPropertyTypes.

Behavior changes

  • @openapi-spec/downgrader no longer ships dist/shared.d.ts, which no public type refers to.

Generated by Claude Code

Build every package with tsdown from one shared root tsdown.config.ts.
Each package's prepack runs `tsdown`, which finds the root config; entries
come from the package's exports. unbundle keeps one output file per source
module, as tsc did, and packages stay external.

- Generate declarations with oxc.
- Fail the build when dist imports a package that isn't in the package's
  dependencies or peerDependencies (deps.onlyImport).
- Switch the root tsconfig to module preserve with bundler resolution and
  drop the .ts extension from relative imports.
- Remove the tsc-only tsconfig.lib.json options.
- Bump oxlint, oxlint-tsgolint and oxfmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7RpPRpasxuSAB8fbND
@coderabbitai

coderabbitai Bot commented Nov 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 113 files, which is 13 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 81f46f0f-8fb1-4b80-b0b9-c8da15a8628d
Commits

Reviewing files that changed from the base of the PR and between dcdf0c0 and de3a811.

Files ignored due to path filters: 1

  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 113

  • package.json
  • packages/downgrader/benches/chained.bench.ts
  • packages/downgrader/benches/v3.1-to-v3.0.bench.ts
  • packages/downgrader/benches/v3.2-to-v3.1.bench.ts
  • packages/downgrader/package.json
  • packages/downgrader/src/index.ts
  • packages/downgrader/src/v3.1-to-v3.0.ts
  • packages/downgrader/src/v3.2-to-v3.1.ts
  • packages/downgrader/tests/corpus.test.ts
  • packages/downgrader/tests/corpus.ts
  • packages/downgrader/tests/orpc.test.ts
  • packages/downgrader/tests/v3.1-to-v3.0.test.ts
  • packages/downgrader/tests/v3.2-to-v3.1.test.ts
  • packages/router/benches/router.bench.ts
  • packages/router/package.json
  • packages/router/tests/encoding.test.ts
  • packages/router/tests/late-adds.test.ts
  • packages/router/tests/orpc.test.ts
  • packages/router/tests/prefix.test.ts
  • packages/router/tests/priority.test.ts
  • packages/router/tests/router.test.ts
  • packages/types/package.json
  • packages/types/src/v3.0.test-d.ts
  • packages/types/src/v3.1.test-d.ts
  • packages/types/src/v3.1.ts
  • packages/types/src/v3.2.test-d.ts
  • packages/types/src/v3.2.ts
  • packages/types/tests/examples/3-2-query-example.ts
  • packages/types/tests/examples/3-2-tags-example.ts
  • packages/types/tests/examples/api-with-examples-3-0.ts
  • packages/types/tests/examples/callback-example-3-0.ts
  • packages/types/tests/examples/link-example-3-0.ts
  • packages/types/tests/examples/non-oauth-scopes-3-1.ts
  • packages/types/tests/examples/petstore-3-0.ts
  • packages/types/tests/examples/petstore-expanded-3-0.ts
  • packages/types/tests/examples/tictactoe-3-1.ts
  • packages/types/tests/examples/uspto-3-0.ts
  • packages/types/tests/examples/webhook-example-3-1.ts
  • packages/types/tests/schema-tests-3.1/callback-object-examples.ts
  • packages/types/tests/schema-tests-3.1/comp-pathitems.ts
  • packages/types/tests/schema-tests-3.1/components-object-example.ts
  • packages/types/tests/schema-tests-3.1/example-object-examples.ts
  • packages/types/tests/schema-tests-3.1/header-object-examples.ts
  • packages/types/tests/schema-tests-3.1/info-object-example.ts
  • packages/types/tests/schema-tests-3.1/info-summary.ts
  • packages/types/tests/schema-tests-3.1/json-schema-dialect.ts
  • packages/types/tests/schema-tests-3.1/license-identifier.ts
  • packages/types/tests/schema-tests-3.1/link-object-examples.ts
  • packages/types/tests/schema-tests-3.1/media-type-examples.ts
  • packages/types/tests/schema-tests-3.1/mega.ts
  • packages/types/tests/schema-tests-3.1/minimal-comp.ts
  • packages/types/tests/schema-tests-3.1/minimal-hooks.ts
  • packages/types/tests/schema-tests-3.1/minimal-paths.ts
  • packages/types/tests/schema-tests-3.1/non-oauth-scopes.ts
  • packages/types/tests/schema-tests-3.1/operation-object-example.ts
  • packages/types/tests/schema-tests-3.1/parameter-object-examples.ts
  • packages/types/tests/schema-tests-3.1/parameter-object-query-allow-reserved.ts
  • packages/types/tests/schema-tests-3.1/path-item-object-example.ts
  • packages/types/tests/schema-tests-3.1/path-item-servers-parameters.ts
  • packages/types/tests/schema-tests-3.1/path-no-response.ts
  • packages/types/tests/schema-tests-3.1/path-var-empty-pathitem.ts
  • packages/types/tests/schema-tests-3.1/paths-object-example.ts
  • packages/types/tests/schema-tests-3.1/request-body-examples.ts
  • packages/types/tests/schema-tests-3.1/response-object-examples.ts
  • packages/types/tests/schema-tests-3.1/schema-object-deprecated-example-keyword.ts
  • packages/types/tests/schema-tests-3.1/schema.ts
  • packages/types/tests/schema-tests-3.1/security-scheme-object-examples.ts
  • packages/types/tests/schema-tests-3.1/servers.ts
  • packages/types/tests/schema-tests-3.1/specification-extensions.ts
  • packages/types/tests/schema-tests-3.1/style-defaults.ts
  • packages/types/tests/schema-tests-3.1/tag-object-example.ts
  • packages/types/tests/schema-tests-3.1/valid-schema-types.ts
  • packages/types/tests/schema-tests-3.1/webhook-example.ts
  • packages/types/tests/schema-tests-3.2/callback-object-examples.ts
  • packages/types/tests/schema-tests-3.2/comp-pathitems.ts
  • packages/types/tests/schema-tests-3.2/components-object-example.ts
  • packages/types/tests/schema-tests-3.2/example-object-examples.ts
  • packages/types/tests/schema-tests-3.2/header-object-examples.ts
  • packages/types/tests/schema-tests-3.2/info-object-example.ts
  • packages/types/tests/schema-tests-3.2/info-summary.ts
  • packages/types/tests/schema-tests-3.2/json-schema-dialect.ts
  • packages/types/tests/schema-tests-3.2/license-identifier.ts
  • packages/types/tests/schema-tests-3.2/link-object-examples.ts
  • packages/types/tests/schema-tests-3.2/media-type-examples.ts
  • packages/types/tests/schema-tests-3.2/mega.ts
  • packages/types/tests/schema-tests-3.2/minimal-comp.ts
  • packages/types/tests/schema-tests-3.2/minimal-hooks.ts
  • packages/types/tests/schema-tests-3.2/minimal-paths.ts
  • packages/types/tests/schema-tests-3.2/non-oauth-scopes.ts
  • packages/types/tests/schema-tests-3.2/operation-object-example.ts
  • packages/types/tests/schema-tests-3.2/parameter-object-cookie-form-allow-reserved.ts
  • packages/types/tests/schema-tests-3.2/parameter-object-examples.ts
  • packages/types/tests/schema-tests-3.2/parameter-object-path-allow-reserved.ts
  • packages/types/tests/schema-tests-3.2/parameter-object-query-allow-reserved.ts
  • packages/types/tests/schema-tests-3.2/path-item-object-example.ts
  • packages/types/tests/schema-tests-3.2/path-item-servers-parameters.ts
  • packages/types/tests/schema-tests-3.2/path-no-response.ts
  • packages/types/tests/schema-tests-3.2/path-var-empty-pathitem.ts
  • packages/types/tests/schema-tests-3.2/paths-object-example.ts
  • packages/types/tests/schema-tests-3.2/request-body-examples.ts
  • packages/types/tests/schema-tests-3.2/response-object-examples.ts
  • packages/types/tests/schema-tests-3.2/schema-object-deprecated-example-keyword.ts
  • packages/types/tests/schema-tests-3.2/schema.ts
  • packages/types/tests/schema-tests-3.2/security-scheme-object-examples.ts
  • packages/types/tests/schema-tests-3.2/servers.ts
  • packages/types/tests/schema-tests-3.2/specification-extensions.ts
  • packages/types/tests/schema-tests-3.2/style-defaults.ts
  • packages/types/tests/schema-tests-3.2/tag-object-example.ts
  • packages/types/tests/schema-tests-3.2/valid-schema-types.ts
  • packages/types/tests/schema-tests-3.2/webhook-example.ts
  • tsconfig.json
  • tsconfig.lib.json
  • tsdown.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pkg-pr-new

pkg-pr-new Bot commented Nov 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@openapi-spec/downgrader@395
npm i https://pkg.pr.new/@openapi-spec/router@395
npm i https://pkg.pr.new/@openapi-spec/types@395

commit: de3a811

@codecov

codecov Bot commented Nov 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Nov 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 31 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/charming-lamport-1i70iz (de3a811) with main (dcdf0c0)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • tsdown replaces per-package tsc — every package's prepack is now tsdown, driven by one root config that derives entries from the package's exports, keeps one module per file (unbundle), externalizes dependencies (neverBundle + onlyImport), and emits declarations with the oxc generator.
  • Module resolution moves to the bundler — the root tsconfig switches to module: preserve + moduleResolution: bundler, and the .ts suffix is dropped from every relative import across src, tests, benches, and the generated OAI fixtures.
  • Emit-only tsconfig settings removed — tsconfig.lib.json keeps only types: [] now that tsc no longer emits, while tsdown and the oxlint/oxfmt bumps land in the root manifest and lockfile.

I verified the migration locally: all three prepack builds, pnpm check, the full 377-test suite, and sherif pass; the packed tarballs carry the expected publishConfig entries and runtime modules; a scratch consumer type-checks against them under both nodenext and bundler with skipLibCheck: false; and exported names plus public JSDoc blocks/tags match between source and emitted declarations.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit e5eb601 into main Nov 9, 2026
12 checks passed
dinwwwh added a commit to middleapi/orpc that referenced this pull request Nov 9, 2026
## Why

Builds move from per-package `tsc` to tsdown, driven by one shared
config at the root. Output keeps tsc's shape (one `.js` and `.d.ts` per
source module), so `publishConfig.exports` and `sideEffects` stay as
they are. Same migration as middleapi/standard-server#188 and
middleapi/openapi-spec#395, which use a simpler config.

## Changes

- **One root `tsdown.config.ts`.** tsdown finds it from each package
directory, so every `prepack` is just `tsdown`. Entries come from each
package's `exports`. `unbundle` keeps one file per module; relative
imports like `../package.json` are still bundled in
(`@orpc/opentelemetry` inlines only `name` and `version`), and packages
stay external.
- **Declarations come from oxc** (`dts: { generator: 'oxc' }`). The tsgo
generator drops the `DecoratedProcedure` import from `@orpc/effect`'s
`extensions/effect.d.ts`, because it is used only inside a `declare
module '@orpc/server'` augmentation. That is the bug reported in #2446
(comment 19474307). oxc needs explicit types on exports. Only
`@orpc/shared`'s regex constants lacked them; a missing annotation now
fails the build.
- **Re-exports stay tree-shakeable.** Rolldown writes re-exports as
`import { a } from 'x'; export { a }`, which Expo's tree shaking can't
see through (the problem behind #2362). A small `renderChunk` hook in
the config turns them back into `export { a } from 'x'`, in the import's
position so evaluation order is unchanged. tsdown and rolldown have no
option for this.
- **Undeclared imports fail the build.** `deps.onlyImport` lists each
package's `dependencies` and `peerDependencies` plus the runtime modules
(`bun`, `bun:*`, `node:*`, `cloudflare:*`). Any `.js` or `.d.ts` in
`dist` that imports something else fails the build, including
CommonJS-only packages and type-only imports.
- **Runtime modules are marked external** through rolldown's `external`
option, using the same list. `neverBundle: true` keeps Node built-ins
and npm-style names as written, but other `prefix:` modules (e.g.
`cloudflare:workers`) fall through to resolution and warn that they
can't be resolved.
- **Bundler module resolution.** The root tsconfig uses `module:
preserve` with `moduleResolution: bundler`, and the `.ts`/`.tsx`
extension is dropped from relative imports across packages, tests,
benches and the docs site. `dist` still imports with `.js` extensions.
`verbatimModuleSyntax` stays: without it `pnpm check` misses type-only
re-exports, which then only fail at build time.
- **Cleanup and bumps.** The emit-only options in `tsconfig.lib.json`
and the bun/cloudflare/nest `tsconfig.build.json` files are gone.
oxlint, oxlint-tsgolint, oxfmt and `@orpc/nest`'s `@oxc-project/runtime`
are bumped.
- **JSDoc kept under oxc.** `ORPCModule` is written as `export
@module({}) class`, since oxc drops a class's JSDoc that sits above its
decorators. `TmpFile.path` is now a documented field rather than a
documented constructor parameter.

## Verification

- `pnpm check`, `pnpm test` and `pnpm docs:validate` pass, and all 29
packages build without warnings.
- Every relative import in `dist` resolves, and every
`publishConfig.exports` and `sideEffects` path exists.
- The emitted declarations type-check for a `nodenext` consumer with
`skipLibCheck: false`.
- **#2446 reproduction**, built against `dist`: with tsgo declarations,
a missing required context and a wrong input type both compile. With oxc
both are rejected, matching the source types.
- **Declaration diff, tsgo vs oxc:** the same 312 files, 295 identical
apart from formatting. The rest differ only in kept `override`
modifiers, quoted member names, and three file-level comments in
`json-schema` that aren't attached to any declaration.
- **Re-export hook:** it rewrites 44 of 309 `.js` files and no `.d.ts`.
Loading each rewritten file in Node gives the same export names and
value types as a build without the hook.
- **`onlyImport`:** adding an undeclared `valibot` (runtime and
type-only) or `change-case` import to `@orpc/zod` fails the build with
`… is imported in index.js but is not included in deps.onlyImport
option`. A `bun:sqlite` import in `@orpc/bun` builds cleanly and stays
an import.
- **Expo SDK 58** (Android export, `EXPO_UNSTABLE_TREE_SHAKING=1`),
importing only `OpenAPILink` and the client:

| Build | Bundle | Server-only markers |
|---|---|---|
| tsc (beta.49) | 1,024,626 B | 0/12 |
| tsdown, no hook | 1,109,354 B | 12/12 |
| this PR | 1,027,558 B | 0/12 |

## Behavior changes

- `dist` no longer contains empty `.js` files for type-only modules, nor
`.d.ts` files that no public type refers to (for example
`router-hidden.d.ts`). Nothing imports them.
- Declarations no longer contain non-JSDoc comments.
- `@orpc/nest` bundles its decorator helpers under
`dist/_virtual/_@oxc-project_runtime@0.157.0/…`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VVaw7RpPRpasxuSAB8fbND

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants