Skip to content

Fix @xnetjs/plugins/node losing its type declarations to a build race - #688

Open
crs48 wants to merge 3 commits into
mainfrom
claude/gallant-ptolemy-9d9513
Open

Fix @xnetjs/plugins/node losing its type declarations to a build race#688
crs48 wants to merge 3 commits into
mainfrom
claude/gallant-ptolemy-9d9513

Conversation

@crs48

@crs48 crs48 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

The symptom

pnpm --filter @xnetjs/cli typecheck fails with ~25 TS7006/TS7016 errors, pointing at callback parameters in packages/cli/src/utils/{agent-remote,agent-local,vector-tier}.ts. Only one of those errors names the real fault:

error TS7016: Could not find a declaration file for module '@xnetjs/plugins/node'.
  '.../packages/plugins/dist/services/node.js' implicitly has an 'any' type.

dist/services/node.js exists; dist/services/node.d.ts does not.

The cause

Not a missing dts: true — that was already set on both entries, and the exports map was already correct. It is a race between the two tsup configs.

packages/plugins/tsup.config.ts exported an array of two configs. tsup runs array configs concurrently, and config 1's clean covers all of dist/ — a strict superset of config 2's dist/services/ output.

tsup cleans twice per config:

  1. **/* under outDir before the ESM phase
  2. **/*.d.{ts,mts,cts} recursively under outDir before the DTS phase (cleanDtsFiles, tsup/dist/index.js:1364)

Whichever of those landed after the node bundle had written a file silently deleted it — and the build still exited 0. The window where clean #1 lands before config 2's JS write but clean #2 lands after its .d.ts write yields exactly node.js with no node.d.ts: a green, cacheable, truncated build that Turbo then stores as a success.

Concurrency is visible in the build log — config 2's build starts before config 1 cleans:

CLI Building entry: src/index.ts
CLI Building entry: src/services/node.ts
ESM Build start              <- config 2 has started
CLI Cleaning output folder   <- config 1 cleans dist/, which contains dist/services/

Proof that config 1's clean reaches config 2's output — running config 1 alone against a fully populated dist:

=== dist/services after config-1-only build ===
total 0        # node.js and node.d.ts both gone

Downstream, TypeScript does not report "module not found" — it degrades the subpath to any, so the failure surfaces as a pile of unrelated-looking errors in innocent files. turbo typecheck --affected then fails in the pre-commit hook for reasons that point nowhere near the actual problem, which pushes people toward --no-verify (forbidden by AGENTS.md). It is also the exact shape AGENTS.md names under Errors: a truncated run that is indistinguishable from a completed one.

The fix

Split the node bundle into packages/plugins/tsup.node.config.ts and run the two builds sequentially (tsup && tsup --config tsup.node.config.ts), so a clean and a write can never interleave. Each config's clean now only reaches its own output.

Bundle contents are unchanged — identical entry, format, externals, splitting, and output sizes (index.js 521.53 KB, index.d.ts 299.36 KB, node.js 327.31 KB, node.d.ts 85.94 KB).

packages/plugins was the only package with a nested-outDir tsup config (scanned all of packages/ and apps/).

Verification

  • 5/5 consecutive pnpm --filter @xnetjs/plugins build runs emit both .d.ts files
  • pnpm --filter @xnetjs/cli typecheck passes
  • pnpm turbo run build typecheck — 112/112 tasks pass
  • pnpm check:packaging passes; publint flags the missing declaration at --level error, confirming the poisoned state was reachable in CI too
  • 833 plugins tests pass; full pre-push suite 12,043 tests pass
  • 0 lint errors, prettier clean

Out of scope

pnpm check:api-report fails on packages/data/etc/data.api.md and packages/react/etc/react.api.md. Verified pre-existing on a clean main by stashing this branch's changes, rebuilding both packages, and re-running. Not touched here; tracked separately.

🤖 Generated with Claude Code

…dle's types

`packages/plugins/tsup.config.ts` exported an array of two configs. tsup runs
array configs CONCURRENTLY, and the first config's `clean` covers all of
`dist/` — a strict superset of the second config's `dist/services/` output.

tsup cleans twice per config: `**/*` before the ESM phase, then
`**/*.d.{ts,mts,cts}` RECURSIVELY before the DTS phase (`cleanDtsFiles`).
Whichever of those landed after the node bundle had already written a file
silently deleted it, and the build still exited 0. The narrow window where the
first clean lands before the node bundle's JS write but the DTS clean lands
after its `.d.ts` write leaves exactly `dist/services/node.js` with no
`node.d.ts` beside it — a green, cacheable, truncated build.

Consumers then resolved `@xnetjs/plugins/node` to bare JavaScript and degraded
it to `any` (TS7016), which surfaced downstream as ~25 unrelated-looking TS7006
"implicitly has an 'any' type" errors on callback parameters across
`packages/cli/src/utils/{agent-remote,agent-local,vector-tier}.ts`. That made
`turbo typecheck --affected` fail in the pre-commit hook for reasons pointing at
innocent files, which pushes people toward `--no-verify`.

Split the node bundle into `tsup.node.config.ts` and run the two builds
sequentially, so a clean and a write can never interleave. Bundle contents are
unchanged: identical entry, format, externals and output sizes.

Verified: 5/5 consecutive builds emit both `.d.ts` files;
`pnpm --filter @xnetjs/cli typecheck` passes; `pnpm check:packaging` passes
(publint flags the missing declaration, so the poisoned state was reachable in
CI too); 833 plugins tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
@crs48 crs48 added the skip-changelog Exclude this PR from the changelog label Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

✓ Changelog fragment found — thanks!

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@crs48, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fff84d45-b394-4798-86eb-da0169650d12

📥 Commits

Reviewing files that changed from the base of the PR and between b446dca and ef5b4ec.

📒 Files selected for processing (4)
  • .changeset/plugins-node-subpath-types.md
  • packages/plugins/package.json
  • packages/plugins/tsup.config.ts
  • packages/plugins/tsup.node.config.ts

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

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

github-actions Bot added a commit that referenced this pull request Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude this PR from the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant