Barazo lexicon schemas and TypeScript types barazo.forum
at main 123 lines 4.4 kB view raw
1/** 2 * Post-generation fixup script for lex-cli output. 3 * 4 * lex-cli generates TypeScript files with: 5 * 1. Missing .js extensions on relative imports (incompatible with NodeNext) 6 * 2. An XRPC server/client wrapper index.ts we don't need 7 * 8 * Additionally, some lexicons use types lex-cli can't process (e.g. permission-set). 9 * These are excluded from codegen but injected into lexicons.ts schemaDict and ids 10 * so runtime validation and ID lookup still work. 11 * 12 * This script: 13 * - Builds a clean index.ts from discovered type files (no hardcoded list) 14 * - Fixes missing .js import extensions 15 * - Injects excluded lexicons into schemaDict and ids 16 */ 17import { readdir, readFile, writeFile } from 'node:fs/promises' 18import { join, relative } from 'node:path' 19 20const GENERATED_DIR = new URL('../src/generated', import.meta.url).pathname 21const TYPES_DIR = join(GENERATED_DIR, 'types') 22const LEXICONS_DIR = new URL('../lexicons', import.meta.url).pathname 23 24// Lexicons excluded from lex-cli codegen but needing schemaDict/ids entries. 25// Each entry: { file: path relative to lexicons/, dictKey: schemaDict key } 26const EXCLUDED_LEXICONS = [ 27 { file: 'forum/barazo/authForumAccess.json', dictKey: 'ForumBarazoAuthForumAccess' }, 28] 29 30async function getTypeFiles(dir) { 31 const entries = await readdir(dir, { withFileTypes: true, recursive: true }) 32 return entries 33 .filter((e) => e.isFile() && e.name.endsWith('.ts')) 34 .map((e) => join(e.parentPath ?? e.path, e.name)) 35} 36 37function toExportName(filePath) { 38 const rel = relative(TYPES_DIR, filePath).replace(/\.ts$/, '') 39 return rel 40 .split('/') 41 .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) 42 .join('') 43} 44 45async function buildReplacementIndex(typeFiles) { 46 const exports = typeFiles 47 .map((file) => { 48 const name = toExportName(file) 49 const relPath = './' + relative(GENERATED_DIR, file).replace(/\.ts$/, '.js') 50 return `export * as ${name} from "${relPath}";` 51 }) 52 .sort() 53 54 return `/** 55 * GENERATED CODE - Re-exports only. 56 * The XRPC server/client wrappers generated by lex-cli are replaced with 57 * direct type re-exports since we use @atproto/api for PDS interactions. 58 */ 59${exports.join('\n')} 60export { schemas, validate } from "./lexicons.js"; 61` 62} 63 64async function fixImportExtensions(filePath) { 65 let content = await readFile(filePath, 'utf-8') 66 const original = content 67 content = content.replace(/from '(\.\.?\/[^']+?)(?<!\.js)'/g, "from '$1.js'") 68 if (content !== original) { 69 await writeFile(filePath, content) 70 } 71} 72 73async function injectExcludedLexicons(lexiconsFile) { 74 let content = await readFile(lexiconsFile, 'utf-8') 75 76 for (const { file, dictKey } of EXCLUDED_LEXICONS) { 77 // Skip if already present (idempotent) 78 if (content.includes(` ${dictKey}:`)) continue 79 80 const lexiconJson = JSON.parse(await readFile(join(LEXICONS_DIR, file), 'utf-8')) 81 82 // Insert into schemaDict (before the closing `} as const satisfies`) 83 const schemaDictEntry = ` ${dictKey}: ${JSON.stringify(lexiconJson, null, 4).replace(/\n/g, '\n ')},\n` 84 content = content.replace( 85 '} as const satisfies Record<string, LexiconDoc>', 86 `${schemaDictEntry}} as const satisfies Record<string, LexiconDoc>` 87 ) 88 89 // Insert into ids (before the closing `} as const`) 90 // Find the ids block and insert alphabetically 91 const idsEntry = ` ${dictKey}: '${lexiconJson.id}',\n` 92 const idsMatch = content.match(/export const ids = \{[\s\S]*?\} as const/) 93 if (idsMatch && !idsMatch[0].includes(dictKey)) { 94 content = content.replace(/(\} as const)$/m, `${idsEntry}$1`) 95 } 96 97 console.log(`Injected excluded lexicon: ${dictKey} (${lexiconJson.id})`) 98 } 99 100 await writeFile(lexiconsFile, content) 101} 102 103async function main() { 104 const typeFiles = await getTypeFiles(TYPES_DIR) 105 const indexContent = await buildReplacementIndex(typeFiles) 106 await writeFile(join(GENERATED_DIR, 'index.ts'), indexContent) 107 108 for (const file of typeFiles) { 109 await fixImportExtensions(file) 110 } 111 112 await fixImportExtensions(join(GENERATED_DIR, 'lexicons.ts')) 113 await fixImportExtensions(join(GENERATED_DIR, 'util.ts')) 114 115 // Inject lexicons that lex-cli can't process 116 await injectExcludedLexicons(join(GENERATED_DIR, 'lexicons.ts')) 117 118 console.log( 119 `Fixed ${typeFiles.length + 2} generated files (${typeFiles.length} types + lexicons.ts + util.ts)` 120 ) 121} 122 123main()