Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/itchy-taxes-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@macalinao/coda": patch
---

Support generation of Rust clients
3 changes: 3 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down Expand Up @@ -312,6 +313,8 @@

"@codama/renderers-js": ["@codama/[email protected]", "", { "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/[email protected]", "", { "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/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@codama/errors": "1.3.3", "@codama/nodes": "1.3.3", "@codama/visitors-core": "1.3.3" } }, "sha512-ReZoo0kItffkhpvl9qRjy3HV1nZXv/k8p4wZ10NveUTDtRghk72YkY0kpK2lt/p+2SlWrhQ9IkO4Q+EQoqABrA=="],
Expand Down
1 change: 1 addition & 0 deletions packages/coda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
81 changes: 30 additions & 51 deletions packages/coda/src/bin/cli.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -17,25 +19,15 @@ program
.command("generate")
.alias("gen")
.description("Generate a client from an Anchor IDL")
.option(
"-i, --idl <path>",
"Path to the Anchor IDL file(s) or glob pattern",
"./idls/*.json",
)
.option(
"-o, --output <path>",
"Output directory for generated client",
"./src/generated",
)
.option(
"-c, --config <path>",
"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}...`);
Expand Down Expand Up @@ -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:");
Expand All @@ -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>",
"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")
Expand Down
39 changes: 39 additions & 0 deletions packages/coda/src/config-template.ts
Original file line number Diff line number Diff line change
@@ -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),
// ],
};
`;
6 changes: 6 additions & 0 deletions packages/coda/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export interface CodaConfig {
*/
outputDir?: string;

/**
* Output directory for the generated Rust client.
* @default "./rust"
*/
rustOutputDir?: string;

/**
* Documentation generation options.
*/
Expand Down
3 changes: 1 addition & 2 deletions packages/coda/src/utils/process-idls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down