-
-
Notifications
You must be signed in to change notification settings - Fork 35
Migrating to tsdown
#2889
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrating to tsdown
#2889
Conversation
WalkthroughRepository-wide migration from tsup to tsdown: added tsdown configs, removed tsup configs and shared base/patch, updated package.json scripts/deps, renamed TSUP_* env vars to TSDOWN_* in code/tests, removed a runtime augmentation import, and added DTS compatibility tests; no runtime API signature changes. Changes
Sequence Diagram(s)(omitted — changes are configuration/tooling and small env/test edits; no control-flow feature to diagram) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (6)📓 Common learnings📚 Learning: 2025-06-02T21:11:20.768ZApplied to files:
📚 Learning: 2025-05-27T20:03:34.213ZApplied to files:
📚 Learning: 2025-08-05T14:43:24.702ZApplied to files:
📚 Learning: 2025-05-27T19:35:57.357ZApplied to files:
📚 Learning: 2025-05-27T20:27:17.015ZApplied to files:
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (4)
✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
express-zod-api/tsdown.config.ts (1)
4-4: Make the package.json read robust to working directory differences.Using "./package.json" relies on the process CWD. Resolve relative to the config file to avoid breakage when run from the repo root or via different tooling.
Apply:
-const { version } = JSON.parse(await readFile("./package.json", "utf8")); +const { version } = JSON.parse( + await readFile(new URL("./package.json", import.meta.url), "utf8"), +);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (20)
express-zod-api/package.json(1 hunks)express-zod-api/src/common-helpers.ts(1 hunks)express-zod-api/src/server.ts(1 hunks)express-zod-api/tests/builtin-logger.spec.ts(1 hunks)express-zod-api/tests/last-resort.spec.ts(1 hunks)express-zod-api/tests/result-helpers.spec.ts(1 hunks)express-zod-api/tests/system.spec.ts(1 hunks)express-zod-api/tsdown.config.ts(1 hunks)express-zod-api/tsup.config.ts(0 hunks)migration/package.json(1 hunks)migration/tsdown.config.ts(1 hunks)migration/tsup.config.ts(0 hunks)package.json(1 hunks)patches/tsup.patch(0 hunks)pnpm-workspace.yaml(0 hunks)tsup.base.ts(0 hunks)zod-plugin/index.ts(1 hunks)zod-plugin/package.json(1 hunks)zod-plugin/tsdown.config.ts(1 hunks)zod-plugin/tsup.config.ts(0 hunks)
💤 Files with no reviewable changes (6)
- pnpm-workspace.yaml
- migration/tsup.config.ts
- patches/tsup.patch
- express-zod-api/tsup.config.ts
- zod-plugin/tsup.config.ts
- tsup.base.ts
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-06-14T16:42:52.972Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2736
File: express-zod-api/tsup.config.ts:12-26
Timestamp: 2025-06-14T16:42:52.972Z
Learning: In express-zod-api tsup configurations, the direct mutation of `options.supported` in the `esbuildOptions` callback is intentional behavior and should not be flagged as a side effect issue.
Applied to files:
express-zod-api/tests/last-resort.spec.tsexpress-zod-api/tests/builtin-logger.spec.tsexpress-zod-api/src/server.tsexpress-zod-api/tests/system.spec.tsexpress-zod-api/tests/result-helpers.spec.tszod-plugin/package.jsonexpress-zod-api/tsdown.config.tsexpress-zod-api/package.jsonexpress-zod-api/src/common-helpers.ts
📚 Learning: 2025-05-27T19:35:57.357Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2546
File: express-zod-api/tests/buffer-schema.spec.ts:32-37
Timestamp: 2025-05-27T19:35:57.357Z
Learning: In the express-zod-api project, tests are run from the `express-zod-api` workspace directory, and the project uses an ESM-first environment without `__dirname`. Relative paths like `../logo.svg` in test files correctly resolve to the repository root due to this test execution context.
Applied to files:
express-zod-api/tests/last-resort.spec.tsexpress-zod-api/tests/system.spec.tsexpress-zod-api/tests/result-helpers.spec.tsexpress-zod-api/package.json
📚 Learning: 2025-06-02T21:08:56.475Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2697
File: CHANGELOG.md:5-5
Timestamp: 2025-06-02T21:08:56.475Z
Learning: The `cjs-test` directory in the express-zod-api repository is a test workspace and should be excluded when checking for main project version consistency with changelog entries.
Applied to files:
express-zod-api/tests/system.spec.ts
📚 Learning: 2025-08-01T09:48:13.742Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#0
File: :0-0
Timestamp: 2025-08-01T09:48:13.742Z
Learning: In express-zod-api, when migrating from Zod v3 to v4, the correct approach for internal type imports is to change from `import type { $ZodType } from "zod/v4/core"` to `import { z } from "zod"` and then use `z.core.$ZodType`. The zod/v4/core module is reexported as z.core by the main zod package, making this a valid and working approach.
Applied to files:
zod-plugin/index.ts
📚 Learning: 2025-05-28T18:58:10.064Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Applied to files:
zod-plugin/index.tsexpress-zod-api/tsdown.config.ts
📚 Learning: 2025-06-02T21:11:20.768Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2697
File: CHANGELOG.md:5-5
Timestamp: 2025-06-02T21:11:20.768Z
Learning: In the express-zod-api repository, RobinTail follows a release workflow where package.json version is only updated on the master branch after merging all planned release changes. Changelog entries may show future version numbers while package.json remains at the previous version during feature development, and this is intentional workflow, not a version inconsistency that needs to be flagged.
Applied to files:
express-zod-api/package.json
📚 Learning: 2025-05-27T20:27:17.015Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2546
File: express-zod-api/src/json-schema-helpers.ts:1-3
Timestamp: 2025-05-27T20:27:17.015Z
Learning: Ramda is correctly listed as a dependency in express-zod-api/package.json, so imports of ramda utilities are properly supported.
Applied to files:
express-zod-api/package.json
📚 Learning: 2025-08-08T11:59:04.814Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2878
File: zod-plugin/runtime.ts:39-42
Timestamp: 2025-08-08T11:59:04.814Z
Learning: express-zod-api/zod-plugin/runtime.ts (TypeScript): Do not memoize the ZodType.prototype.brand getter. The current design returning setBrand.bind(this) on each access is intentional/preferred; avoid redefining "brand" as a data property per instance.
Applied to files:
express-zod-api/src/common-helpers.ts
🔇 Additional comments (13)
package.json (1)
27-27: tsdown migration verified across all workspacesAll checks passed—no lingering tsup references and tsdown is configured in each workspace.
Findings:
- No
TSUP_*environment variables found- No
tsup.config.*files present- No
tsupinvocations in anypackage.jsonscriptstsdown.config.tsexists in:
express-zod-api/tsdown.config.tsmigration/tsdown.config.tszod-plugin/tsdown.config.tsProceed with approving these changes.
express-zod-api/tests/result-helpers.spec.ts (1)
119-121: LGTM: env stub migrated to TSDOWN_STATICThe rename from TSUP_STATIC to TSDOWN_STATIC, alongside NODE_ENV, aligns with the build tool migration and keeps the production-mode behavior consistent in tests.
express-zod-api/tests/system.spec.ts (1)
21-22: LGTM: production-mode stub updated to TSDOWN_STATICStubbing TSDOWN_STATIC to "production" here matches the new env strategy. The later
vi.unstubAllEnvs()in afterAll ensures isolation between suites.express-zod-api/tests/last-resort.spec.ts (1)
13-15: LGTM: TSDOWN_STATIC stub applied in Last Resort testsThe env variable rename is consistently applied and paired with NODE_ENV. Cleanup with
vi.unstubAllEnvs()is present.zod-plugin/package.json (1)
21-21: tsdown configuration validated – ready to approve
- Confirmed
zod-plugin/tsdown.config.tsexists.- Verifed it includes
matching the previous setup.attw: { profile: "esmOnly", level: "error" },- No leftover
tsupreferences detected.express-zod-api/src/server.ts (1)
35-35: Migration Verified – No leftover TSUP_ references*
Switch toTSDOWN_BUILDis consistent; default fallback unchanged. Confirmed viargthat noTSUP_*env vars remain.express-zod-api/tests/builtin-logger.spec.ts (1)
46-46: LGTM: Test env stub updated to TSDOWN_STATICMatches the new isProduction() source of truth. The afterEach vi.unstubAllEnvs() above keeps tests isolated.
migration/package.json (1)
20-20: ✅ Build Script Migration VerifiedThe
build: "tsdown"script is correctly wired:
tsdown.config.tsexists in all workspaces:
- migration/tsdown.config.ts
- express-zod-api/tsdown.config.ts
- zod-plugin/tsdown.config.ts
- Each config includes the required
attwintegration:
- migration/tsdown.config.ts:
attw: { profile: "esmOnly", level: "error" }- express-zod-api/tsdown.config.ts:
attw: { profile: "esmOnly", level: "error" }- zod-plugin/tsdown.config.ts:
attw: { profile: "esmOnly", level: "error" }No further changes needed—ready to ship!
express-zod-api/package.json (1)
19-19: LGTM:buildscript updated totsdownand no lingeringsemverimportsVerified that all
semverimports/requires have been removed. Approving these changes.express-zod-api/src/common-helpers.ts (1)
142-144: Env switch to TSDOWN_STATIC is correct and consistent with the new build config.The memoization key now aligns with express-zod-api/tsdown.config.ts where TSDOWN_STATIC is defined. No behavior change beyond the env rename.
zod-plugin/tsdown.config.ts (1)
3-7: Approved: tsdown config for zod-plugin is correct
The entry file at zod-plugin/index.ts exists, and the minimal tsdown config (entry/minify/attw) aligns with our migration goals.migration/tsdown.config.ts (1)
3-8: Approve migration package config
Verifiedmigration/index.tsexists.skipNodeModulesBundle: true, attw profile (esmOnly, levelerror), andminify: truealign with the other packages.express-zod-api/tsdown.config.ts (1)
11-13: Define mappings look correct for tsdown/esbuild.Quoting the values ensures string literal substitution; keys match the new consumers (isProduction and debug logging).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
zod-plugin/tsdown.config.ts (2)
8-10: Confirm whetherbanner.dtsmust live underdts.bannerin tsdown.Depending on tsdown’s config schema, the DTS banner may need to be nested under the
dtsoption (e.g.,dts.banner) rather thanbanner.dts. If your local type-checking or build warns, switch to the nested form.Apply this diff if required:
- banner: { - dts: "import './augmentation.js';", - }, + dts: { + banner: "import './augmentation.js';", + },
13-15: Rename the augmentation file to.d.tsfor reliable retentionTo ensure tsdown always preserves your module augmentations, rename and update your entry:
Rename on disk:
mv zod-plugin/augmentation.ts zod-plugin/augmentation.d.tsIn
zod-plugin/tsdown.config.ts(around lines 13–15), update the entry list:- entry: ["augmentation.ts"], + entry: ["augmentation.d.ts"],This makes the source a true declaration file, aligning with tsdown’s migration notes and hardening augmentation retention.
[optional_refactors_recommended]
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
zod-plugin/index.ts(0 hunks)zod-plugin/tsdown.config.ts(1 hunks)
💤 Files with no reviewable changes (1)
- zod-plugin/index.ts
🔇 Additional comments (1)
zod-plugin/tsdown.config.ts (1)
3-16: Config structure and two-entry strategy look solid.Good separation of the runtime bundle and the DTS-only augmentation; the NodeNext-friendly import in the DTS banner is a clean way to include augmentations without a runtime side effect. attw gating is also a nice touch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
compat-test/dts.spec.ts (4)
6-11: Avoid hard-coded node_modules paths; resolve package locations programmaticallyHard-coding "./node_modules/..." is brittle across package managers (pnpm/yarn/npm), hoisting, and workspace setups. Resolve the installed package paths using createRequire + require.resolve, then read the files from those resolved roots. Also, prefer a regex for the import assertion to allow either single or double quotes and optional semicolons.
Apply this diff:
- const fwDts = await readFile( - "./node_modules/express-zod-api/dist/index.d.ts", - "utf-8", - ); - expect(fwDts).toMatch(`import "@express-zod-api/zod-plugin";`); + const fwDts = await readFile(fwDtsPath, "utf-8"); + expect(fwDts).toMatch(/import\s+['"]@express-zod-api\/zod-plugin['"];?/);
14-19: Use resolver-based path and robust match ('.' is special in regex)The current test path assumes nested node_modules and may break with hoisting. Also, toMatch(string) treats the string as a regex; the '.' in './augmentation.js' is a wildcard. Use the resolved path and an escaped regex that allows both quote styles.
- const pluginDts = await readFile( - "./node_modules/express-zod-api/node_modules/@express-zod-api/zod-plugin/dist/index.d.ts", - "utf-8", - ); - expect(pluginDts).toMatch(`import './augmentation.js';`); + const pluginDts = await readFile(pluginDtsPath, "utf-8"); + expect(pluginDts).toMatch(/import\s+['"]\.\/augmentation\.js['"];?/);
22-27: Resolve augmentation.d.ts via the package root and relax quotingSame resilience/regex concerns as above. Use the resolved file path and allow either quote style.
- const augDts = await readFile( - "./node_modules/express-zod-api/node_modules/@express-zod-api/zod-plugin/dist/augmentation.d.ts", - "utf-8", - ); - expect(augDts).toMatch(`declare module "zod"`); + const augDts = await readFile(pluginAugDtsPath, "utf-8"); + expect(augDts).toMatch(/declare module ["']zod["']/);
1-3: Introduce resolver utilities once to keep tests portable across package managersAdd createRequire + path helpers and precompute the DTS file paths so all tests are independent of node_modules layout.
import { describe, test, expect } from "vitest"; import { readFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; + +const require = createRequire(import.meta.url); +const ezapiPkgJson = require.resolve("express-zod-api/package.json"); +const ezapiRoot = dirname(ezapiPkgJson); +const fwDtsPath = join(ezapiRoot, "dist/index.d.ts"); +// Resolve the plugin as if required from express-zod-api to avoid mismatches under hoisting +const pluginPkgJson = require.resolve("@express-zod-api/zod-plugin/package.json", { paths: [ezapiRoot] }); +const pluginRoot = dirname(pluginPkgJson); +const pluginDtsPath = join(pluginRoot, "dist/index.d.ts"); +const pluginAugDtsPath = join(pluginRoot, "dist/augmentation.d.ts");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
compat-test/dts.spec.ts(1 hunks)express-zod-api/tsdown.config.ts(1 hunks)migration/tsdown.config.ts(1 hunks)zod-plugin/tsdown.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- zod-plugin/tsdown.config.ts
- migration/tsdown.config.ts
- express-zod-api/tsdown.config.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
📚 Learning: 2025-05-28T18:58:10.064Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Applied to files:
compat-test/dts.spec.ts
📚 Learning: 2025-05-27T19:30:51.885Z
Learnt from: RobinTail
PR: RobinTail/express-zod-api#2546
File: compat-test/sample.ts:1-1
Timestamp: 2025-05-27T19:30:51.885Z
Learning: Files in compat-test/ directories, especially those named sample.ts or similar, are often test fixtures for migration scripts and may intentionally contain deprecated or "incorrect" code that the migration tooling is designed to fix. These should not be flagged as issues.
Applied to files:
compat-test/dts.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build (24.0.0)
🔇 Additional comments (1)
compat-test/dts.spec.ts (1)
4-28: Solid DTS wiring checks — nice coverage of the migration behaviorThese tests capture the critical DTS relationships (framework -> plugin -> augmentation) that tsdown can sometimes drop. This will guard against regressions in future tool updates.
|
✅ QA passed 🏁 Ready |
Due to egoist/tsup#1332
Migration guide: https://tsdown.dev/guide/migrate-from-tsup
Findings:
.d.tsfile —addressed by renamingaddressed using banner and extra entrypointreadFileattwis integrated when installedSummary by CodeRabbit