forked from
slices.network/slices
Highly ambitious ATProtocol AppView service and sdks
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}