A personal website powered by Astro and ATProto
at main 4.8 kB view raw
1#!/usr/bin/env node 2 3import { readdir, readFile, writeFile } from 'fs/promises'; 4import { join } from 'path'; 5import { loadConfig } from '../src/lib/config/site'; 6 7interface LexiconSchema { 8 lexicon: number; 9 id: string; 10 description?: string; 11 defs: Record<string, any>; 12} 13 14function generateTypeScriptTypes(schema: LexiconSchema): string { 15 const nsid = schema.id; 16 const typeName = nsid.split('.').map(part => 17 part.charAt(0).toUpperCase() + part.slice(1) 18 ).join(''); 19 20 const mainDef = schema.defs.main; 21 if (!mainDef || mainDef.type !== 'record') { 22 throw new Error(`Schema ${nsid} must have a 'main' record definition`); 23 } 24 25 const recordSchema = mainDef.record; 26 const properties = recordSchema.properties || {}; 27 const required = recordSchema.required || []; 28 29 // Generate property types 30 const propertyTypes: string[] = []; 31 32 for (const [propName, propSchema] of Object.entries(properties)) { 33 const isRequired = required.includes(propName); 34 const optional = isRequired ? '' : '?'; 35 36 let type: string; 37 38 switch (propSchema.type) { 39 case 'string': 40 if (propSchema.enum) { 41 type = propSchema.enum.map((v: string) => `'${v}'`).join(' | '); 42 } else { 43 type = 'string'; 44 } 45 break; 46 case 'integer': 47 type = 'number'; 48 break; 49 case 'boolean': 50 type = 'boolean'; 51 break; 52 case 'array': 53 type = 'any[]'; // Could be more specific based on items schema 54 break; 55 case 'object': 56 type = 'Record<string, any>'; 57 break; 58 default: 59 type = 'any'; 60 } 61 62 propertyTypes.push(` ${propName}${optional}: ${type};`); 63 } 64 65 return `// Generated from lexicon schema: ${nsid} 66// Do not edit manually - regenerate with: npm run gen:types 67 68export interface ${typeName}Record { 69${propertyTypes.join('\n')} 70} 71 72export interface ${typeName} { 73 $type: '${nsid}'; 74 value: ${typeName}Record; 75} 76 77// Helper type for discriminated unions 78export type ${typeName}Union = ${typeName}; 79`; 80} 81 82async function generateTypes() { 83 const config = loadConfig(); 84 const lexiconsDir = join(process.cwd(), 'src/lexicons'); 85 const generatedDir = join(process.cwd(), 'src/lib/generated'); 86 87 console.log('🔍 Scanning for lexicon schemas...'); 88 89 try { 90 const files = await readdir(lexiconsDir); 91 const jsonFiles = files.filter(file => file.endsWith('.json')); 92 93 if (jsonFiles.length === 0) { 94 console.log('No lexicon schema files found in src/lexicons/'); 95 return; 96 } 97 98 console.log(`Found ${jsonFiles.length} lexicon schema(s):`); 99 100 const generatedTypes: string[] = []; 101 const unionTypes: string[] = []; 102 103 for (const file of jsonFiles) { 104 const schemaPath = join(lexiconsDir, file); 105 const schemaContent = await readFile(schemaPath, 'utf-8'); 106 const schema: LexiconSchema = JSON.parse(schemaContent); 107 108 console.log(` - ${schema.id} (${file})`); 109 110 try { 111 const typesCode = generateTypeScriptTypes(schema); 112 const outputPath = join(generatedDir, `${schema.id.replace(/\./g, '-')}.ts`); 113 114 await writeFile(outputPath, typesCode, 'utf-8'); 115 console.log(` ✅ Generated types: ${outputPath}`); 116 117 // Add to union types 118 const typeName = schema.id.split('.').map(part => 119 part.charAt(0).toUpperCase() + part.slice(1) 120 ).join(''); 121 unionTypes.push(typeName); 122 generatedTypes.push(`import type { ${typeName} } from './${schema.id.replace(/\./g, '-')}';`); 123 124 } catch (error) { 125 console.error(` ❌ Failed to generate types for ${schema.id}:`, error); 126 } 127 } 128 129 // Generate index file with union types 130 if (generatedTypes.length > 0) { 131 const indexContent = `// Generated index of all lexicon types 132// Do not edit manually - regenerate with: npm run gen:types 133 134${generatedTypes.join('\n')} 135 136// Union type for all generated lexicon records 137export type GeneratedLexiconUnion = ${unionTypes.join(' | ')}; 138 139// Type map for component registry 140export type GeneratedLexiconTypeMap = { 141${unionTypes.map(type => ` '${type}': ${type};`).join('\n')} 142}; 143`; 144 145 const indexPath = join(generatedDir, 'lexicon-types.ts'); 146 await writeFile(indexPath, indexContent, 'utf-8'); 147 console.log(` ✅ Generated index: ${indexPath}`); 148 } 149 150 console.log('\n🎉 Type generation complete!'); 151 console.log('\nNext steps:'); 152 console.log('1. Import the generated types in your components'); 153 console.log('2. Update the component registry with the new types'); 154 console.log('3. Create components that use the strongly typed records'); 155 156 } catch (error) { 157 console.error('Error generating types:', error); 158 process.exit(1); 159 } 160} 161 162generateTypes();