Skip to content

DTS generation fails with TypeScript path mappings in monorepo #523

@ThePlenkov

Description

@ThePlenkov

DTS generation fails with TypeScript path mappings in monorepo

Describe the bug

When building a package in a monorepo that uses TypeScript path mappings to reference other workspace packages, tsdown's DTS generation fails with UNRESOLVED_IMPORT errors. The error messages show that tsdown is trying to resolve imports from a phantom .d.ts file that contains the wrong exports.

Reproduction

Monorepo Structure

packages/
├── types/           # Pure type definitions package
│   └── src/
│       └── index.ts  # Only exports types
├── config/          # Package that depends on @anygpt/types
│   └── src/
│       └── index.ts  # Imports from '@anygpt/types'

TypeScript Configuration

tsconfig.base.json:

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "baseUrl": ".",
    "paths": {
      "@anygpt/types": ["packages/types/src"],
      "@anygpt/config": ["packages/config/src"]
    }
  }
}

packages/types/src/index.ts:

// Only exports types
export type {
  ConnectorConfig,
  ProviderConfig,
  AnyGPTConfig,
  ConfigLoadOptions
} from './config.js';

packages/config/src/index.ts:

// Re-exports types from @anygpt/types
export type {
  ConnectorConfig,
  ProviderConfig,
  AnyGPTConfig,
  ConfigLoadOptions
} from '@anygpt/types';

// Also exports functions
export { setupRouter, loadConnectors } from './setup.js';

packages/config/tsdown.config.ts:

import { defineConfig } from 'tsdown';

export default defineConfig({
  sourcemap: true,
  tsconfig: 'tsconfig.lib.json',
  skipNodeModulesBundle: true,
  platform: 'node'
  // dts: true (auto-detected from package.json)
});

packages/config/package.json:

{
  "name": "@anygpt/config",
  "types": "./dist/index.d.ts",
  "dependencies": {
    "@anygpt/types": "0.1.0"
  }
}

Build Command

npx nx build config
# or
npx tsdown

Error Output

ERROR  Error: Build failed with 6 errors:

[UNRESOLVED_IMPORT] Error: Could not resolve './types.js' in ../types/src/index.d.ts
   ╭─[ ../types/src/index.d.ts:1:15 ]
   │
 1 │ export * from './types.js';
   │               ──────┬─────  
   │                     ╰─────── Module not found.
───╯

[UNRESOLVED_IMPORT] Error: Could not resolve './defaults.js' in ../types/src/index.d.ts
   ╭─[ ../types/src/index.d.ts:2:62 ]
   │
 2 │ export { getDefaultConfig, convertCodexToAnyGPTConfig } from './defaults.js';
   │                                                              ───────┬───────  
   │                                                                     ╰───────── Module not found.
───╯

[UNRESOLVED_IMPORT] Error: Could not resolve './connector-loader.js' in ../types/src/index.d.ts
   ╭─[ ../types/src/index.d.ts:3:73 ]
   │
 3 │ export { loadConnectors, getConnectorConfig, clearConnectorCache } from './connector-loader.js';
   │                                                                         ───────────┬───────────  
   │                                                                                    ╰───────────── Module not found.
───╯

What is Expected?

The build should succeed and generate proper DTS files with external references to @anygpt/types.

What is Actually Happening?

  1. Tsdown resolves @anygpt/types via the TypeScript path mapping to packages/types/src
  2. It tries to read/generate a .d.ts file at ../types/src/index.d.ts
  3. The phantom file contains WRONG exports - it shows exports from the @anygpt/config package (setupRouter, loadConnectors, etc.) instead of the actual @anygpt/types exports
  4. Tsdown then tries to resolve relative imports from this phantom file and fails

Key Observations

  • The error path ../types/src/index.d.ts doesn't exist in the source tree
  • The exports shown in the error (setupRouter, loadConnectors) are from @anygpt/config, NOT from @anygpt/types
  • This suggests tsdown is confusing package contents during DTS bundling
  • JavaScript bundling works perfectly - only DTS generation fails

Workaround

Setting dts: false and using tsc directly for DTS generation works:

export default defineConfig({
  sourcemap: true,
  tsconfig: 'tsconfig.lib.json',
  skipNodeModulesBundle: true,
  dts: false  // Disable tsdown DTS generation
});

Then generate DTS separately:

tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist

System Info

  • tsdown version: 0.15.6
  • rolldown version: 1.0.0-beta.41
  • Node version: v24.8.0
  • npm version: 11.6.0
  • OS: Linux (WSL Ubuntu)
  • Package manager: npm

Additional Context

This issue appears to be specific to monorepos using TypeScript path mappings. The DTS bundler seems to have a bug where it:

  1. Incorrectly resolves path-mapped imports to source directories
  2. Generates or reads phantom .d.ts files with wrong content
  3. Confuses exports between different packages

The JavaScript bundling works correctly, so this is isolated to the DTS generation phase.

Suggested Fix

The DTS bundler should either:

  1. Properly resolve TypeScript path mappings to built .d.ts files instead of source files
  2. Respect skipNodeModulesBundle: true for DTS generation (currently it only affects JS)
  3. Add an option to keep path-mapped dependencies as external references in DTS output

Related

This might be related to how rolldown-plugin-dts handles TypeScript project references and path mappings in monorepo setups.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions