Skip to content

Commit 803b1e5

Browse files
authored
Merge pull request #20 from macalinao/igm/rust
Support Rust code generation
2 parents 76d4742 + e46f6e3 commit 803b1e5

File tree

7 files changed

+85
-53
lines changed

7 files changed

+85
-53
lines changed

.changeset/itchy-taxes-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@macalinao/coda": patch
3+
---
4+
5+
Support generation of Rust clients

bun.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
},
105105
"dependencies": {
106106
"@codama/nodes-from-anchor": "catalog:",
107+
"@codama/renderers-rust": "^1.2.2",
107108
"@macalinao/codama-nodes-from-anchor-x": "workspace:*",
108109
"@macalinao/codama-rename-visitor": "workspace:*",
109110
"@macalinao/codama-renderers-js-esm": "workspace:*",
@@ -312,6 +313,8 @@
312313

313314
"@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=="],
314315

316+
"@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=="],
317+
315318
"@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=="],
316319

317320
"@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=="],

packages/coda/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
},
5050
"dependencies": {
5151
"@codama/nodes-from-anchor": "catalog:",
52+
"@codama/renderers-rust": "^1.2.2",
5253
"@macalinao/codama-nodes-from-anchor-x": "workspace:*",
5354
"@macalinao/codama-renderers-js-esm": "workspace:*",
5455
"@macalinao/codama-renderers-markdown": "workspace:*",

packages/coda/src/bin/cli.ts

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#!/usr/bin/env node
22
import { writeFile } from "node:fs/promises";
33
import { resolve } from "node:path";
4+
import { renderVisitor as renderRustVisitor } from "@codama/renderers-rust";
45
import { renderESMTypeScriptVisitor } from "@macalinao/codama-renderers-js-esm";
56
import { renderMarkdownVisitor } from "@macalinao/codama-renderers-markdown";
67
import { Command } from "commander";
8+
import { CONFIG_TEMPLATE } from "../config-template.js";
79
import { fileExists, processIdls } from "../utils/index.js";
810

911
const program = new Command();
@@ -17,25 +19,15 @@ program
1719
.command("generate")
1820
.alias("gen")
1921
.description("Generate a client from an Anchor IDL")
20-
.option(
21-
"-i, --idl <path>",
22-
"Path to the Anchor IDL file(s) or glob pattern",
23-
"./idls/*.json",
24-
)
25-
.option(
26-
"-o, --output <path>",
27-
"Output directory for generated client",
28-
"./src/generated",
29-
)
3022
.option(
3123
"-c, --config <path>",
3224
"Path to coda.config.mjs file",
3325
"./coda.config.mjs",
3426
)
35-
.action(async (options: { idl: string; output: string; config: string }) => {
27+
.action(async (options: { config: string }) => {
3628
try {
3729
const { codama, config } = await processIdls(options);
38-
const outputPath = resolve(config.outputDir ?? options.output);
30+
const outputPath = resolve(config.outputDir ?? "./src/generated/");
3931

4032
// Apply the ESM TypeScript visitor
4133
console.log(`Generating client to ${outputPath}...`);
@@ -66,46 +58,8 @@ program
6658
process.exit(1);
6759
}
6860

69-
// Create config template
70-
const configTemplate = `/**
71-
* @type {import('@macalinao/coda').CodaConfig}
72-
*/
73-
export default {
74-
// Optional: Path to the Anchor IDL file(s) (overrides --idl option)
75-
// Can be a single path, glob pattern, or an array of paths/patterns
76-
// Default: Looks for "./idls/*.json" if available, otherwise "./target/idl/program.json"
77-
// idlPath: "./target/idl/program.json", // Single file
78-
// idlPath: "./idls/*.json", // Glob pattern (all JSON files in idls/)
79-
// idlPath: "./idls/program_*.json", // Glob pattern (matching files)
80-
// idlPath: ["./idls/program1.json", "./idls/program2.json"], // Array of files
81-
// idlPath: ["./idls/*.json", "./extra/*.json"], // Array with glob patterns
82-
83-
// Optional: Output directory for generated client (overrides --output option)
84-
// outputDir: "./src/generated",
85-
86-
// Optional: Documentation generation options
87-
// docs: {
88-
// // NPM package name for the TypeScript client
89-
// // If provided, will add an NPM badge and link to the package
90-
// npmPackageName: "@my-org/my-solana-client",
91-
// },
92-
93-
// Optional: Add custom visitors to transform the Codama tree
94-
// Can be an array of visitors or a function that returns visitors
95-
// visitors: [
96-
// // Example: Add a custom visitor
97-
// someVisitor(),
98-
// ],
99-
100-
// Example using a function to access the IDL:
101-
// visitors: ({ idl }) => [
102-
// customVisitor(idl),
103-
// ],
104-
};
105-
`;
106-
10761
// Write config file
108-
await writeFile(configPath, configTemplate, "utf-8");
62+
await writeFile(configPath, CONFIG_TEMPLATE, "utf-8");
10963

11064
console.log(`✅ Created coda.config.mjs at ${configPath}`);
11165
console.log("\nNext steps:");
@@ -119,6 +73,31 @@ export default {
11973
}
12074
});
12175

76+
program
77+
.command("generate-rust")
78+
.alias("gen-rust")
79+
.description("Generate a Rust client from an Anchor IDL")
80+
.option(
81+
"-c, --config <path>",
82+
"Path to coda.config.mjs file",
83+
"./coda.config.mjs",
84+
)
85+
.action(async (options: { config: string }) => {
86+
try {
87+
const { codama, config } = await processIdls(options);
88+
const outputPath = resolve(config.rustOutputDir ?? "./rust");
89+
90+
// Apply the Rust visitor
91+
console.log(`Generating Rust client to ${outputPath}...`);
92+
codama.accept(renderRustVisitor(outputPath));
93+
94+
console.log("✅ Rust client generated successfully!");
95+
} catch (error) {
96+
console.error("Error generating Rust client:", error);
97+
process.exit(1);
98+
}
99+
});
100+
122101
program
123102
.command("docs")
124103
.description("Generate markdown documentation from an Anchor IDL")
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export const CONFIG_TEMPLATE = `/**
2+
* @type {import('@macalinao/coda').CodaConfig}
3+
*/
4+
export default {
5+
// Optional: Path to the Anchor IDL file(s) (overrides --idl option)
6+
// Can be a single path, glob pattern, or an array of paths/patterns
7+
// Default: Looks for "./idls/*.json" if available, otherwise "./target/idl/program.json"
8+
// idlPath: "./target/idl/program.json", // Single file
9+
// idlPath: "./idls/*.json", // Glob pattern (all JSON files in idls/)
10+
// idlPath: "./idls/program_*.json", // Glob pattern (matching files)
11+
// idlPath: ["./idls/program1.json", "./idls/program2.json"], // Array of files
12+
// idlPath: ["./idls/*.json", "./extra/*.json"], // Array with glob patterns
13+
14+
// Optional: Output directory for generated client (overrides --output option)
15+
// outputDir: "./src/generated",
16+
17+
// Optional: Output directory for generated Rust client
18+
// rustOutputDir: "./rust",
19+
20+
// Optional: Documentation generation options
21+
// docs: {
22+
// // NPM package name for the TypeScript client
23+
// // If provided, will add an NPM badge and link to the package
24+
// npmPackageName: "@my-org/my-solana-client",
25+
// },
26+
27+
// Optional: Add custom visitors to transform the Codama tree
28+
// Can be an array of visitors or a function that returns visitors
29+
// visitors: [
30+
// // Example: Add a custom visitor
31+
// someVisitor(),
32+
// ],
33+
34+
// Example using a function to access the IDL:
35+
// visitors: ({ idl }) => [
36+
// customVisitor(idl),
37+
// ],
38+
};
39+
`;

packages/coda/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ export interface CodaConfig {
3434
*/
3535
outputDir?: string;
3636

37+
/**
38+
* Output directory for the generated Rust client.
39+
* @default "./rust"
40+
*/
41+
rustOutputDir?: string;
42+
3743
/**
3844
* Documentation generation options.
3945
*/

packages/coda/src/utils/process-idls.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ import { resolveIdlPaths } from "./resolve-idl-paths.js";
1717
* 5. Apply custom visitors
1818
*/
1919
export async function processIdls(options: {
20-
idl: string;
2120
config: string;
2221
}): Promise<{ codama: Codama; config: CodaConfig; idls: AnchorIdl[] }> {
2322
const configPath = resolve(options.config);
2423
const config = await loadConfig(configPath);
2524

2625
// Determine IDL paths - use config if provided, otherwise use command line option
27-
const idlPathInput = config.idlPath ?? options.idl;
26+
const idlPathInput = config.idlPath ?? "./idls/*.json";
2827
const resolvedPaths = await resolveIdlPaths(idlPathInput);
2928

3029
if (resolvedPaths.length === 0) {

0 commit comments

Comments
 (0)