@@ -128,17 +128,19 @@ async function resolveRefs(obj: any, sourceDir: string): Promise<any> {
128
128
customTypePath = [ ...refTokens , title ] . join ( "." ) ;
129
129
}
130
130
131
- // Create a simplified reference object
132
- return {
133
- type : "object" ,
134
- title,
135
- properties : { } ,
136
- additionalProperties : false ,
137
- // Add metadata for TypeScript generator
138
- // These will be processed by json-schema-to-typescript
139
- description : `Reference to ${ customTypePath } ` ,
140
- tsType : customTypePath . split ( "." ) . pop ( ) ,
141
- } ;
131
+ // For external references, just return a simple type reference
132
+ // This prevents inlining the entire interface definition
133
+ if ( title ) {
134
+ return {
135
+ type : "object" ,
136
+ title : title ,
137
+ // Mark as external reference to prevent inlining
138
+ "x-external-ref" : true ,
139
+ } ;
140
+ }
141
+
142
+ // Fallback: return the resolved object if no title
143
+ return refObj || { } ;
142
144
} catch ( error ) {
143
145
console . error ( `Error resolving reference ${ ref } :` , error ) ;
144
146
return { } ;
@@ -198,8 +200,14 @@ async function convertSchemaToTypeScript(
198
200
const outputDir = path . dirname ( outputPath ) ;
199
201
await mkdir ( outputDir , { recursive : true } ) ;
200
202
203
+ // Post-process to fix external references
204
+ const processedTypeScript = await postProcessTypeScript (
205
+ typeScript ,
206
+ outputPath
207
+ ) ;
208
+
201
209
// Write the TypeScript interface to file
202
- await writeFile ( outputPath , typeScript ) ;
210
+ await writeFile ( outputPath , processedTypeScript ) ;
203
211
console . log ( `Generated: ${ outputPath } ` ) ;
204
212
} catch ( error ) {
205
213
console . error ( `Error converting schema ${ schemaPath } :` , error ) ;
@@ -274,6 +282,76 @@ function createBarrelFiles(dir: string): void {
274
282
}
275
283
}
276
284
285
+ /**
286
+ * Post-process generated TypeScript to fix external references
287
+ */
288
+ async function postProcessTypeScript (
289
+ typeScript : string ,
290
+ outputPath : string
291
+ ) : Promise < string > {
292
+ let processed = typeScript ;
293
+
294
+ // Calculate the correct import path for each interface based on current output path
295
+ const getImportPath = ( interfaceName : string ) : string => {
296
+ const outputDir = path . dirname ( outputPath ) ;
297
+ const modelsGenerated = path . resolve ( __dirname , "../src/models/generated" ) ;
298
+
299
+ // Define the actual locations of each interface
300
+ const interfaceLocations : Record < string , string > = {
301
+ BlockBase : path . join ( modelsGenerated , "block/base" ) ,
302
+ Block : path . join ( modelsGenerated , "block" ) ,
303
+ RichText : path . join ( modelsGenerated , "block/types/rich_text" ) ,
304
+ File : path . join ( modelsGenerated , "file" ) ,
305
+ ColumnBlock : path . join ( modelsGenerated , "block/types/column" ) ,
306
+ TableRowBlock : path . join ( modelsGenerated , "block/types/table_row" ) ,
307
+ FileExternal : path . join ( modelsGenerated , "file/external" ) ,
308
+ RichTextText : path . join ( modelsGenerated , "block/types/rich_text/text" ) ,
309
+ } ;
310
+
311
+ const targetPath = interfaceLocations [ interfaceName ] ;
312
+ if ( ! targetPath ) return "" ;
313
+
314
+ const relativePath = path . relative ( outputDir , targetPath ) ;
315
+ return relativePath . startsWith ( "." )
316
+ ? relativePath . replace ( / \\ / g, "/" )
317
+ : `./${ relativePath . replace ( / \\ / g, "/" ) } ` ;
318
+ } ;
319
+
320
+ // Find empty interface definitions and replace with imports
321
+ const emptyInterfaceRegex = / e x p o r t i n t e r f a c e ( \w + ) \{ \} \s * / g;
322
+ const imports : string [ ] = [ ] ;
323
+
324
+ processed = processed . replace ( emptyInterfaceRegex , ( match , interfaceName ) => {
325
+ const importPath = getImportPath ( interfaceName ) ;
326
+ if ( importPath ) {
327
+ imports . push ( `import type { ${ interfaceName } } from '${ importPath } ';` ) ;
328
+ return "" ; // Remove the empty interface
329
+ }
330
+ return match ; // Keep if not in our external interfaces map
331
+ } ) ;
332
+
333
+ // Add imports at the top if any were found
334
+ if ( imports . length > 0 ) {
335
+ processed = imports . join ( "\n" ) + "\n\n" + processed ;
336
+ }
337
+
338
+ // Fix Block type to extend BlockBase if this is the block.ts file
339
+ if ( outputPath . endsWith ( "block/block.ts" ) ) {
340
+ // Add import for BlockBase if not already present
341
+ if ( ! processed . includes ( "import type { BlockBase }" ) ) {
342
+ processed = `import type { BlockBase } from './base';\n\n` + processed ;
343
+ }
344
+
345
+ // Replace the Block type definition to extend BlockBase
346
+ processed = processed . replace (
347
+ / e x p o r t t y p e B l o c k = \{ [ ^ } ] * \} & \{ / ,
348
+ "export type Block = BlockBase & {"
349
+ ) ;
350
+ }
351
+
352
+ return processed ;
353
+ }
354
+
277
355
/**
278
356
* Extract enum values from a JSON schema file
279
357
*/
0 commit comments