Skip to content

validateCompilerOptions ignores project tsconfig.json, causing cross-package z.infer<> types to resolve as empty objects #1864

Description

@joeferner

Search

Searched existing issues. Related prior discussion: #774 (type aliases from external packages not expanded). This issue describes the root cause and provides a fix.

Bug description

validateCompilerOptions in @tsoa/cli is currently a no-op cast:

// packages/cli/src/cli.ts (before fix)
return (config || {}) as CompilerOptions;

This means MetadataGenerator receives empty or near-empty compiler options and creates its TypeScript program with defaults — which resolves to Node10 / CommonJS module resolution. The project's tsconfig.json is never read.

Consequences:

  1. paths mappings are ignored. Any type imported from a package resolved via compilerOptions.paths in tsconfig.json (common in monorepos) cannot be found. TSOA throws Could not find declarations for type 'Foo'.

  2. moduleResolution, customConditions, and other settings are not applied. Projects using moduleResolution: bundler or node16/nodenext with customConditions: ["source"] (to resolve packages to their .ts source rather than compiled .d.ts) have those settings silently ignored.

  3. z.infer<> types from cross-package imports expand to empty objects {}. When TypeScript reads a compiled .d.ts file instead of the .ts source, z.infer<typeof SomeSchema> loses the Zod generic structure and collapses — TSOA generates a {} schema with no properties. This forces teams to either hand-copy DTOs or write a wrapper script that pre-parses the tsconfig themselves.

Reproduction

packages/external/src/types.ts (a separate package in the monorepo):

import { z } from 'zod';

const WidgetSchema = z.object({ id: z.number(), name: z.string() });
export type Widget = z.infer<typeof WidgetSchema>;

src/controllers/widget.ts (the TSOA controller):

import { Get, Route, Controller } from '@tsoa/runtime';
import type { Widget } from '@my-org/external';

@Route('widgets')
export class WidgetController extends Controller {
  @Get()
  public getWidget(): Widget {
    return { id: 1, name: 'test' };
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "customConditions": ["source"],
    "paths": { "@my-org/external": ["../external/src/types.ts"] }
  }
}

Running tsoa spec-and-routes generates a schema for Widget as {} — all properties missing. The paths option is never applied because validateCompilerOptions never reads tsconfig.json.

Expected behaviour

TSOA reads the project's tsconfig.json (via TypeScript's own findConfigFile / readConfigFile / parseJsonConfigFileContent APIs) and uses those compiler options when creating the TypeScript program. Any compilerOptions set in tsoa.json are merged on top as overrides.

Workaround

Pre-parse tsconfig.json manually and pass the result as compilerOptions in tsoa.json, or write a wrapper script that calls MetadataGenerator directly with the parsed options. Neither should be necessary.

Environment

  • tsoa: latest (master)
  • TypeScript: 5.x
  • Node: 18+
  • Affects all module resolution modes (node16, nodenext, bundler)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions