diff --git a/.changeset/itchy-taxes-type.md b/.changeset/itchy-taxes-type.md new file mode 100644 index 00000000..86f2c867 --- /dev/null +++ b/.changeset/itchy-taxes-type.md @@ -0,0 +1,5 @@ +--- +"@macalinao/coda": patch +--- + +Support generation of Rust clients diff --git a/bun.lock b/bun.lock index 1fdfa424..ce14de05 100644 --- a/bun.lock +++ b/bun.lock @@ -104,6 +104,7 @@ }, "dependencies": { "@codama/nodes-from-anchor": "catalog:", + "@codama/renderers-rust": "^1.2.2", "@macalinao/codama-nodes-from-anchor-x": "workspace:*", "@macalinao/codama-rename-visitor": "workspace:*", "@macalinao/codama-renderers-js-esm": "workspace:*", @@ -312,6 +313,8 @@ "@codama/renderers-js": ["@codama/renderers-js@1.3.4", "", { "dependencies": { "@codama/errors": "1.3.3", "@codama/nodes": "1.3.3", "@codama/renderers-core": "1.0.19", "@codama/visitors-core": "1.3.3", "@solana/codecs-strings": "^2.3.0", "nunjucks": "^3.2.4", "prettier": "^3.6.2" } }, "sha512-srcHHCc7l2FXTlUEoPOolw0bmxBkdyqpzOGtBNc7eeF9sOCE3mlR84qXzxoqCKx4T26wl+QHihI0wby0bMlfrA=="], + "@codama/renderers-rust": ["@codama/renderers-rust@1.2.2", "", { "dependencies": { "@codama/errors": "1.3.3", "@codama/nodes": "1.3.3", "@codama/renderers-core": "1.0.19", "@codama/visitors-core": "1.3.3", "@solana/codecs-strings": "^2.3.0", "nunjucks": "^3.2.4" } }, "sha512-UnEVP7lN33w1VWBb5y6TJhxX52IhQAHz5J3JiB9qlDWBcqmrhTkeIn3Mp4StdDYGqoP9knBUXnvqhZEMdWh8Og=="], + "@codama/validators": ["@codama/validators@1.3.3", "", { "dependencies": { "@codama/errors": "1.3.3", "@codama/nodes": "1.3.3", "@codama/visitors-core": "1.3.3" } }, "sha512-dj3vlwMlxU57l6cRLxb76ZwlrEGv+dq7llDtH0aqc1z3OW5SOryREUuzeqL7T/2hY8FiY+pxjH4CQx+A08hULQ=="], "@codama/visitors": ["@codama/visitors@1.3.3", "", { "dependencies": { "@codama/errors": "1.3.3", "@codama/nodes": "1.3.3", "@codama/visitors-core": "1.3.3" } }, "sha512-ReZoo0kItffkhpvl9qRjy3HV1nZXv/k8p4wZ10NveUTDtRghk72YkY0kpK2lt/p+2SlWrhQ9IkO4Q+EQoqABrA=="], diff --git a/packages/coda/package.json b/packages/coda/package.json index f6a2383f..973333e8 100644 --- a/packages/coda/package.json +++ b/packages/coda/package.json @@ -49,6 +49,7 @@ }, "dependencies": { "@codama/nodes-from-anchor": "catalog:", + "@codama/renderers-rust": "^1.2.2", "@macalinao/codama-nodes-from-anchor-x": "workspace:*", "@macalinao/codama-renderers-js-esm": "workspace:*", "@macalinao/codama-renderers-markdown": "workspace:*", diff --git a/packages/coda/src/bin/cli.ts b/packages/coda/src/bin/cli.ts index 722bc957..4b544c06 100644 --- a/packages/coda/src/bin/cli.ts +++ b/packages/coda/src/bin/cli.ts @@ -1,9 +1,11 @@ #!/usr/bin/env node import { writeFile } from "node:fs/promises"; import { resolve } from "node:path"; +import { renderVisitor as renderRustVisitor } from "@codama/renderers-rust"; import { renderESMTypeScriptVisitor } from "@macalinao/codama-renderers-js-esm"; import { renderMarkdownVisitor } from "@macalinao/codama-renderers-markdown"; import { Command } from "commander"; +import { CONFIG_TEMPLATE } from "../config-template.js"; import { fileExists, processIdls } from "../utils/index.js"; const program = new Command(); @@ -17,25 +19,15 @@ program .command("generate") .alias("gen") .description("Generate a client from an Anchor IDL") - .option( - "-i, --idl ", - "Path to the Anchor IDL file(s) or glob pattern", - "./idls/*.json", - ) - .option( - "-o, --output ", - "Output directory for generated client", - "./src/generated", - ) .option( "-c, --config ", "Path to coda.config.mjs file", "./coda.config.mjs", ) - .action(async (options: { idl: string; output: string; config: string }) => { + .action(async (options: { config: string }) => { try { const { codama, config } = await processIdls(options); - const outputPath = resolve(config.outputDir ?? options.output); + const outputPath = resolve(config.outputDir ?? "./src/generated/"); // Apply the ESM TypeScript visitor console.log(`Generating client to ${outputPath}...`); @@ -66,46 +58,8 @@ program process.exit(1); } - // Create config template - const configTemplate = `/** - * @type {import('@macalinao/coda').CodaConfig} - */ -export default { - // Optional: Path to the Anchor IDL file(s) (overrides --idl option) - // Can be a single path, glob pattern, or an array of paths/patterns - // Default: Looks for "./idls/*.json" if available, otherwise "./target/idl/program.json" - // idlPath: "./target/idl/program.json", // Single file - // idlPath: "./idls/*.json", // Glob pattern (all JSON files in idls/) - // idlPath: "./idls/program_*.json", // Glob pattern (matching files) - // idlPath: ["./idls/program1.json", "./idls/program2.json"], // Array of files - // idlPath: ["./idls/*.json", "./extra/*.json"], // Array with glob patterns - - // Optional: Output directory for generated client (overrides --output option) - // outputDir: "./src/generated", - - // Optional: Documentation generation options - // docs: { - // // NPM package name for the TypeScript client - // // If provided, will add an NPM badge and link to the package - // npmPackageName: "@my-org/my-solana-client", - // }, - - // Optional: Add custom visitors to transform the Codama tree - // Can be an array of visitors or a function that returns visitors - // visitors: [ - // // Example: Add a custom visitor - // someVisitor(), - // ], - - // Example using a function to access the IDL: - // visitors: ({ idl }) => [ - // customVisitor(idl), - // ], -}; -`; - // Write config file - await writeFile(configPath, configTemplate, "utf-8"); + await writeFile(configPath, CONFIG_TEMPLATE, "utf-8"); console.log(`✅ Created coda.config.mjs at ${configPath}`); console.log("\nNext steps:"); @@ -119,6 +73,31 @@ export default { } }); +program + .command("generate-rust") + .alias("gen-rust") + .description("Generate a Rust client from an Anchor IDL") + .option( + "-c, --config ", + "Path to coda.config.mjs file", + "./coda.config.mjs", + ) + .action(async (options: { config: string }) => { + try { + const { codama, config } = await processIdls(options); + const outputPath = resolve(config.rustOutputDir ?? "./rust"); + + // Apply the Rust visitor + console.log(`Generating Rust client to ${outputPath}...`); + codama.accept(renderRustVisitor(outputPath)); + + console.log("✅ Rust client generated successfully!"); + } catch (error) { + console.error("Error generating Rust client:", error); + process.exit(1); + } + }); + program .command("docs") .description("Generate markdown documentation from an Anchor IDL") diff --git a/packages/coda/src/config-template.ts b/packages/coda/src/config-template.ts new file mode 100644 index 00000000..e43ca962 --- /dev/null +++ b/packages/coda/src/config-template.ts @@ -0,0 +1,39 @@ +export const CONFIG_TEMPLATE = `/** + * @type {import('@macalinao/coda').CodaConfig} + */ +export default { + // Optional: Path to the Anchor IDL file(s) (overrides --idl option) + // Can be a single path, glob pattern, or an array of paths/patterns + // Default: Looks for "./idls/*.json" if available, otherwise "./target/idl/program.json" + // idlPath: "./target/idl/program.json", // Single file + // idlPath: "./idls/*.json", // Glob pattern (all JSON files in idls/) + // idlPath: "./idls/program_*.json", // Glob pattern (matching files) + // idlPath: ["./idls/program1.json", "./idls/program2.json"], // Array of files + // idlPath: ["./idls/*.json", "./extra/*.json"], // Array with glob patterns + + // Optional: Output directory for generated client (overrides --output option) + // outputDir: "./src/generated", + + // Optional: Output directory for generated Rust client + // rustOutputDir: "./rust", + + // Optional: Documentation generation options + // docs: { + // // NPM package name for the TypeScript client + // // If provided, will add an NPM badge and link to the package + // npmPackageName: "@my-org/my-solana-client", + // }, + + // Optional: Add custom visitors to transform the Codama tree + // Can be an array of visitors or a function that returns visitors + // visitors: [ + // // Example: Add a custom visitor + // someVisitor(), + // ], + + // Example using a function to access the IDL: + // visitors: ({ idl }) => [ + // customVisitor(idl), + // ], +}; +`; diff --git a/packages/coda/src/config.ts b/packages/coda/src/config.ts index 716f5603..bc960734 100644 --- a/packages/coda/src/config.ts +++ b/packages/coda/src/config.ts @@ -34,6 +34,12 @@ export interface CodaConfig { */ outputDir?: string; + /** + * Output directory for the generated Rust client. + * @default "./rust" + */ + rustOutputDir?: string; + /** * Documentation generation options. */ diff --git a/packages/coda/src/utils/process-idls.ts b/packages/coda/src/utils/process-idls.ts index fa32dd2f..e35d42b5 100644 --- a/packages/coda/src/utils/process-idls.ts +++ b/packages/coda/src/utils/process-idls.ts @@ -17,14 +17,13 @@ import { resolveIdlPaths } from "./resolve-idl-paths.js"; * 5. Apply custom visitors */ export async function processIdls(options: { - idl: string; config: string; }): Promise<{ codama: Codama; config: CodaConfig; idls: AnchorIdl[] }> { const configPath = resolve(options.config); const config = await loadConfig(configPath); // Determine IDL paths - use config if provided, otherwise use command line option - const idlPathInput = config.idlPath ?? options.idl; + const idlPathInput = config.idlPath ?? "./idls/*.json"; const resolvedPaths = await resolveIdlPaths(idlPathInput); if (resolvedPaths.length === 0) {