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:
-
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'.
-
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.
-
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)
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
validateCompilerOptionsin@tsoa/cliis currently a no-op cast:This means
MetadataGeneratorreceives empty or near-empty compiler options and creates its TypeScript program with defaults — which resolves toNode10 / CommonJSmodule resolution. The project'stsconfig.jsonis never read.Consequences:
pathsmappings are ignored. Any type imported from a package resolved viacompilerOptions.pathsintsconfig.json(common in monorepos) cannot be found. TSOA throwsCould not find declarations for type 'Foo'.moduleResolution,customConditions, and other settings are not applied. Projects usingmoduleResolution: bundlerornode16/nodenextwithcustomConditions: ["source"](to resolve packages to their.tssource rather than compiled.d.ts) have those settings silently ignored.z.infer<>types from cross-package imports expand to empty objects{}. When TypeScript reads a compiled.d.tsfile instead of the.tssource,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):src/controllers/widget.ts(the TSOA controller):tsconfig.json:{ "compilerOptions": { "moduleResolution": "bundler", "customConditions": ["source"], "paths": { "@my-org/external": ["../external/src/types.ts"] } } }Running
tsoa spec-and-routesgenerates a schema forWidgetas{}— all properties missing. Thepathsoption is never applied becausevalidateCompilerOptionsnever readstsconfig.json.Expected behaviour
TSOA reads the project's
tsconfig.json(via TypeScript's ownfindConfigFile/readConfigFile/parseJsonConfigFileContentAPIs) and uses those compiler options when creating the TypeScript program. AnycompilerOptionsset intsoa.jsonare merged on top as overrides.Workaround
Pre-parse
tsconfig.jsonmanually and pass the result ascompilerOptionsintsoa.json, or write a wrapper script that callsMetadataGeneratordirectly with the parsed options. Neither should be necessary.Environment
master)node16,nodenext,bundler)