Highly ambitious ATProtocol AppView service and sdks
at main 111 lines 3.3 kB view raw
1import { parseArgs } from "@std/cli/parse-args"; 2import { resolve, dirname } from "@std/path"; 3import { ensureDir } from "@std/fs"; 4import { generateTypeScript } from "@slices/codegen"; 5import { logger } from "../utils/logger.ts"; 6import { 7 findLexiconFiles, 8 validateLexiconFiles, 9 printValidationSummary, 10} from "../utils/lexicon.ts"; 11import { SlicesConfigLoader, mergeConfig } from "../utils/config.ts"; 12 13function showCodegenHelp() { 14 console.log(` 15slices codegen - Generate TypeScript client from lexicon files 16 17USAGE: 18 slices codegen [OPTIONS] 19 20OPTIONS: 21 --lexicons <PATH> Directory containing lexicon files (default: ./lexicons or from slices.json) 22 --output <PATH> Output file path (default: ./generated_client.ts or from slices.json) 23 --slice <SLICE_URI> Target slice URI (required, or from slices.json) 24 -h, --help Show this help message 25 26EXAMPLES: 27 slices codegen --slice at://did:plc:example/slice 28 slices codegen --lexicons ./my-lexicons --output ./src/client.ts --slice at://did:plc:example/slice 29 slices codegen # Uses config from slices.json 30`); 31} 32 33export async function codegenCommand( 34 commandArgs: unknown[], 35 _globalArgs: Record<string, unknown> 36): Promise<void> { 37 const args = parseArgs(commandArgs as string[], { 38 boolean: ["help", "include-slices"], 39 string: ["lexicons", "output", "slice"], 40 alias: { 41 h: "help", 42 }, 43 }); 44 45 if (args.help) { 46 showCodegenHelp(); 47 return; 48 } 49 50 // Load config file 51 const configLoader = new SlicesConfigLoader(); 52 const slicesConfig = await configLoader.load(); 53 const mergedConfig = mergeConfig(slicesConfig, args); 54 55 // Validate required arguments 56 if (!mergedConfig.slice) { 57 logger.error("--slice is required"); 58 if (!slicesConfig.slice) { 59 logger.info( 60 "Tip: Create a slices.json file with your slice URI to avoid passing --slice every time" 61 ); 62 } 63 console.log("\nRun 'slices codegen --help' for usage information."); 64 Deno.exit(1); 65 } 66 67 const lexiconsPath = resolve(mergedConfig.lexiconPath!); 68 const outputPath = resolve(mergedConfig.clientOutputPath!); 69 const sliceUri = mergedConfig.slice!; 70 71 try { 72 const lexiconFiles = await findLexiconFiles(lexiconsPath); 73 74 if (lexiconFiles.length === 0) { 75 logger.warn(`No .json files found in ${lexiconsPath}`); 76 return; 77 } 78 79 const validationResult = await validateLexiconFiles(lexiconFiles, false); 80 81 if (validationResult.invalidFiles > 0) { 82 printValidationSummary(validationResult); 83 logger.error("Cannot generate client with invalid lexicon files"); 84 Deno.exit(1); 85 } 86 87 if (validationResult.validFiles === 0) { 88 logger.error("No valid lexicon files found"); 89 Deno.exit(1); 90 } 91 92 const validLexicons = validationResult.files 93 .filter((f) => f.valid) 94 .map((f) => f.content); 95 96 const generatedCode = await generateTypeScript(validLexicons, { 97 sliceUri, 98 }); 99 100 const outputDir = dirname(outputPath); 101 await ensureDir(outputDir); 102 103 await Deno.writeTextFile(outputPath, generatedCode); 104 105 logger.success(`Generated client: ${outputPath}`); 106 } catch (error) { 107 const err = error as Error; 108 logger.error(`Code generation failed: ${err.message}`); 109 Deno.exit(1); 110 } 111}