@@ -36,18 +36,163 @@ function getKind(node: any): ExtractedDeclaration['kind'] {
3636 return 'const'
3737}
3838
39+ // --- Schema field extraction (static AST-based) ---
40+
41+ interface SchemaFieldMeta {
42+ name : string
43+ type : string
44+ required : boolean
45+ description ?: string
46+ defaultValue ?: string
47+ }
48+
49+ const JSDOC_START_RE = / ^ \s * \/ \* \* /
50+ const JSDOC_END_RE = / ^ \s * \* \/ /
51+ const DOC_LINE_RE = / ^ \s * \* \s ? ( .* ) /
52+ const DEFAULT_TAG_RE = / ^ @ d e f a u l t \s * /
53+ const FIELD_MATCH_RE = / ^ \s * ( \w + ) \s * : /
54+
55+ function resolveAstType ( node : any , source : string ) : string {
56+ if ( ! node )
57+ return 'unknown'
58+ if ( node . type === 'CallExpression' ) {
59+ const callee = node . callee ?. name
60+ if ( callee === 'string' )
61+ return 'string'
62+ if ( callee === 'number' )
63+ return 'number'
64+ if ( callee === 'boolean' )
65+ return 'boolean'
66+ if ( callee === 'any' )
67+ return 'any'
68+ if ( callee === 'object' )
69+ return 'object'
70+ if ( callee === 'optional' )
71+ return resolveAstType ( node . arguments ?. [ 0 ] , source )
72+ if ( callee === 'pipe' )
73+ return resolveAstType ( node . arguments ?. [ 0 ] , source )
74+ if ( callee === 'array' )
75+ return `${ resolveAstType ( node . arguments ?. [ 0 ] , source ) } []`
76+ if ( callee === 'record' )
77+ return `Record<${ resolveAstType ( node . arguments ?. [ 0 ] , source ) } , ${ resolveAstType ( node . arguments ?. [ 1 ] , source ) } >`
78+ if ( callee === 'literal' ) {
79+ const arg = node . arguments ?. [ 0 ]
80+ if ( arg ?. type === 'StringLiteral' )
81+ return `'${ arg . value } '`
82+ if ( arg ?. type === 'NumericLiteral' )
83+ return String ( arg . value )
84+ if ( arg ?. type === 'BooleanLiteral' )
85+ return String ( arg . value )
86+ return source . slice ( arg ?. start , arg ?. end )
87+ }
88+ if ( callee === 'union' ) {
89+ const arrArg = node . arguments ?. [ 0 ]
90+ if ( arrArg ?. type === 'ArrayExpression' ) {
91+ return arrArg . elements . map ( ( e : any ) => resolveAstType ( e , source ) ) . join ( ' | ' )
92+ }
93+ return 'unknown'
94+ }
95+ if ( callee === 'custom' )
96+ return 'Function'
97+ }
98+ return 'unknown'
99+ }
100+
101+ function isOptionalCall ( node : any ) : boolean {
102+ return node ?. type === 'CallExpression' && node . callee ?. name === 'optional'
103+ }
104+
105+ function parseSchemaComments ( code : string ) : Record < string , { description ?: string , defaultValue ?: string } > {
106+ const result : Record < string , { description ?: string , defaultValue ?: string } > = { }
107+ const lines = code . split ( '\n' )
108+ let desc = ''
109+ let def = ''
110+
111+ for ( const line of lines ) {
112+ if ( JSDOC_START_RE . test ( line ) ) {
113+ desc = ''
114+ def = ''
115+ continue
116+ }
117+ if ( JSDOC_END_RE . test ( line ) )
118+ continue
119+
120+ const docLine = line . match ( DOC_LINE_RE )
121+ if ( docLine ) {
122+ const content = docLine [ 1 ] ! . trim ( )
123+ if ( content . startsWith ( '@default' ) )
124+ def = content . replace ( DEFAULT_TAG_RE , '' )
125+ else if ( ! content . startsWith ( '@' ) && content )
126+ desc += ( desc ? ' ' : '' ) + content
127+ continue
128+ }
129+
130+ const fieldMatch = line . match ( FIELD_MATCH_RE )
131+ if ( fieldMatch ) {
132+ if ( desc || def )
133+ result [ fieldMatch [ 1 ] ! ] = { description : desc || undefined , defaultValue : def || undefined }
134+ desc = ''
135+ def = ''
136+ }
137+ }
138+
139+ return result
140+ }
141+
142+ function extractSchemaFields ( node : any , source : string , code : string ) : SchemaFieldMeta [ ] | null {
143+ // node is the init of the VariableDeclarator, should be CallExpression with callee "object"
144+ if ( node ?. type !== 'CallExpression' || node . callee ?. name !== 'object' )
145+ return null
146+ const objArg = node . arguments ?. [ 0 ]
147+ if ( objArg ?. type !== 'ObjectExpression' )
148+ return null
149+
150+ const comments = parseSchemaComments ( code )
151+ const fields : SchemaFieldMeta [ ] = [ ]
152+
153+ for ( const prop of objArg . properties || [ ] ) {
154+ if ( prop . type === 'SpreadElement' )
155+ continue
156+ const key = prop . key ?. name || prop . key ?. value
157+ if ( ! key )
158+ continue
159+
160+ const isOpt = isOptionalCall ( prop . value )
161+ const typeNode = isOpt ? prop . value . arguments ?. [ 0 ] : prop . value
162+
163+ fields . push ( {
164+ name : key ,
165+ type : resolveAstType ( typeNode , source ) ,
166+ required : ! isOpt ,
167+ description : comments [ key ] ?. description ,
168+ defaultValue : comments [ key ] ?. defaultValue ,
169+ } )
170+ }
171+
172+ return fields . length ? fields : null
173+ }
174+
39175// --- Registry type extraction ---
40176
41177// Pre-parse schemas.ts to look up re-exported schema declarations
42178const schemasSource = readFileSync ( join ( registryDir , 'schemas.ts' ) , 'utf-8' )
43179const schemaDeclarations = new Map < string , string > ( )
180+ const schemaFields : Record < string , SchemaFieldMeta [ ] > = { }
44181{
45182 const { program } = parseSync ( 'schemas.ts' , schemasSource )
46183 for ( const node of program . body ) {
47184 if ( node . type === 'ExportNamedDeclaration' && node . declaration ?. type === 'VariableDeclaration' ) {
48- const name = node . declaration . declarations [ 0 ] ?. id ?. name
49- if ( name )
185+ const declarator = node . declaration . declarations [ 0 ]
186+ const name = declarator ?. id ?. name
187+ if ( name ) {
50188 schemaDeclarations . set ( name , schemasSource . slice ( node . start , node . end ) )
189+ // Extract field metadata for pre-computed schema fields
190+ if ( ! name . endsWith ( 'Defaults' ) ) {
191+ const fields = extractSchemaFields ( declarator . init , schemasSource , schemasSource . slice ( node . start , node . end ) )
192+ if ( fields )
193+ schemaFields [ name ] = fields
194+ }
195+ }
51196 }
52197 }
53198}
@@ -233,6 +378,7 @@ const componentToSlug: Record<string, string> = {
233378 ScriptCrisp : 'crisp' ,
234379 ScriptIntercom : 'intercom' ,
235380 ScriptGoogleAdsense : 'google-adsense' ,
381+ ScriptBlueskyEmbed : 'bluesky-embed' ,
236382 ScriptInstagramEmbed : 'instagram-embed' ,
237383 ScriptXEmbed : 'x-embed' ,
238384 ScriptLemonSqueezy : 'lemon-squeezy' ,
@@ -268,5 +414,10 @@ for (const [componentName, props] of Object.entries(componentProps)) {
268414 }
269415}
270416
271- writeFileSync ( outputPath , JSON . stringify ( types , null , 2 ) )
272- console . log ( `Generated registry types for ${ Object . keys ( types ) . length } scripts (${ Object . keys ( componentProps ) . length } with component props)` )
417+ const output = {
418+ types,
419+ schemaFields,
420+ }
421+
422+ writeFileSync ( outputPath , `${ JSON . stringify ( output , null , 2 ) } \n` )
423+ console . log ( `Generated registry types for ${ Object . keys ( types ) . length } scripts (${ Object . keys ( componentProps ) . length } with component props, ${ Object . keys ( schemaFields ) . length } schema fields)` )
0 commit comments