@@ -54,19 +54,21 @@ async function extractPropertiesOfTypeName(
5454 for ( const typeStatement of typeStatements ) {
5555 const properties : TypeProperties = { }
5656 const type = typeChecker . getTypeAtLocation ( typeStatement )
57+ const typeName = ( typeStatement as any ) . name . getText ( )
58+
5759 for ( const property of type . getProperties ( ) ) {
5860 const propertyName = property . getName ( )
5961 const type = typeChecker . getTypeOfSymbolAtLocation ( property , sourceFile )
6062 const nonNullableType = type . getNonNullableType ( )
61- const typeName = typeChecker . typeToString ( nonNullableType )
62- const isRequired = nonNullableType === type && typeName !== 'any'
63+ const typeNameStr = typeChecker . typeToString ( nonNullableType )
64+ const isRequired = nonNullableType === type && typeNameStr !== 'any'
6365
6466 const defaultValue = property . getJsDocTags ( ) . filter ( ( tag ) => tag . name === 'default' ) [ 0 ] ?. text ?. [ 0 ] . text
6567
6668 if ( shouldIgnoreProperty ( property ) ) {
6769 continue
6870 }
69- const prettyType = await tryPrettier ( typeName )
71+ const prettyType = await tryPrettier ( typeNameStr )
7072 properties [ propertyName ] = {
7173 type : prettyType ,
7274 defaultValue,
@@ -78,8 +80,9 @@ async function extractPropertiesOfTypeName(
7880 . join ( '\n' ) || undefined ,
7981 }
8082 }
83+
8184 if ( Object . keys ( properties ) . length ) {
82- results [ ( typeStatement as any ) . name . getText ( ) ] = Object . fromEntries (
85+ results [ typeName ] = Object . fromEntries (
8386 Object . entries ( properties )
8487 . sort ( ( [ aName ] , [ bName ] ) => aName . localeCompare ( bName ) )
8588 . sort ( ( [ , a ] , [ , b ] ) => ( a . isRequired === b . isRequired ? 0 : a . isRequired ? - 1 : 1 ) )
@@ -98,12 +101,17 @@ async function extractPropertiesOfTypeName(
98101 return Object . keys ( foo ) . length ? results : null
99102}
100103
101- async function createTypeSearch ( tsConfigPath : string , typeSearchOptions : TypeSearchOptions = { } ) {
104+ async function createTypeSearch (
105+ tsConfigPath : string ,
106+ componentPath : string ,
107+ typeSearchOptions : TypeSearchOptions = { } ,
108+ ) {
102109 const { shouldIgnoreProperty } = typeSearchOptions
103110 const configFile = ts . readConfigFile ( tsConfigPath , ts . sys . readFile )
104111 const { options } = ts . parseJsonConfigFileContent ( configFile . config , ts . sys , './dist' )
105112
106- const files = globbySync ( './dist/' )
113+ // Scope file search to the specific component directory to avoid type name collisions
114+ const files = globbySync ( `${ componentPath } /**/*.d.ts` )
107115 const program = ts . createProgram ( files , options )
108116 const sourceFiles = program . getSourceFiles ( )
109117
@@ -130,7 +138,13 @@ function getSourceFileName(symbol: ts.Symbol): string | undefined {
130138
131139function shouldIgnoreProperty ( property : ts . Symbol ) {
132140 const sourceFileName = getSourceFileName ( property )
133- const isExternal = sourceFileName ?. includes ( 'node_modules' ) && ! sourceFileName ?. includes ( '@zag-js' )
141+
142+ // Allow properties without source files (can happen with complex type resolution)
143+ if ( ! sourceFileName ) {
144+ return false
145+ }
146+
147+ const isExternal = sourceFileName . includes ( 'node_modules' ) && ! sourceFileName . includes ( '@zag-js' )
134148 const isExcludedByName = [ 'children' ] . includes ( property . getName ( ) )
135149 return isExternal || isExcludedByName
136150}
@@ -144,12 +158,15 @@ function extractTypeExports(fileContent?: string) {
144158 return sourceFile
145159 . forEachDescendantAsArray ( )
146160 . filter ( ( node ) : node is ExportDeclaration => Node . isExportDeclaration ( node ) )
147- . flatMap ( ( node ) =>
148- node
161+ . flatMap ( ( node ) => {
162+ // Check if the export declaration itself is type-only (export type {})
163+ const isTypeOnlyExport = node . isTypeOnly ( )
164+
165+ return node
149166 . getNamedExports ( )
150- . filter ( ( namedExport ) => namedExport . isTypeOnly ( ) )
151- . map ( ( namedExport ) => namedExport . getAliasNode ( ) ?. getText ( ) ?? namedExport . getName ( ) ) ,
152- )
167+ . filter ( ( namedExport ) => isTypeOnlyExport || namedExport . isTypeOnly ( ) )
168+ . map ( ( namedExport ) => namedExport . getAliasNode ( ) ?. getText ( ) ?? namedExport . getName ( ) )
169+ } )
153170 . sort ( )
154171}
155172
@@ -180,13 +197,19 @@ const extractTypesForFramework = async (framework: string) => {
180197 ) ,
181198 )
182199
183- const searchType = await createTypeSearch ( 'tsconfig.json' , {
184- shouldIgnoreProperty,
185- } )
186-
187200 const result = await Promise . all (
188201 Object . entries ( componentExportMap ) . flatMap ( async ( [ component , typeExports ] ) => {
202+ // Convert component path to dist path for type search
203+ // e.g., src/components/foo -> dist/components/foo
204+ const componentDistPath = component . replace ( / ^ s r c \/ / , 'dist/' )
205+
206+ // Create a type searcher scoped to this specific component to avoid type name collisions
207+ const searchType = await createTypeSearch ( 'tsconfig.json' , componentDistPath , {
208+ shouldIgnoreProperty,
209+ } )
210+
189211 const resolvedTypeExports = await Promise . all ( typeExports . map ( async ( type ) => await searchType ( type ) ) )
212+
190213 return {
191214 component,
192215 typeExports : resolvedTypeExports
@@ -272,8 +295,11 @@ const extractTypesForFramework = async (framework: string) => {
272295 return { ...acc , ...value }
273296 } , { } )
274297
298+ const filename = `${ path . basename ( component ) } .types.json`
299+ const filePath = path . join ( outDir , framework , filename )
300+
275301 fs . outputFileSync (
276- path . join ( outDir , framework , ` ${ path . basename ( component ) } .types.json` ) ,
302+ filePath ,
277303 await prettier . format ( JSON . stringify ( typeExportsWithElement ) , {
278304 ...prettierConfig ,
279305 parser : 'json' ,
0 commit comments