Skip to main content

quasar_cli/
idl.rs

1use {
2    crate::{error::CliResult, IdlCommand},
3    quasar_idl::{codegen, parser},
4    std::path::{Path, PathBuf},
5};
6
7/// Generate IDL, TypeScript client, and Rust client crate for a program.
8///
9/// Outputs:
10/// - `target/idl/<name>.idl.json`
11/// - `target/client/rust/<name>-client/` (standalone Rust crate)
12/// - `target/client/typescript/<name>/web3.ts` when `generate_typescript` is
13///   true
14pub fn generate(crate_path: &Path, generate_typescript: bool) -> CliResult {
15    // Parse the program
16    let parsed = parser::parse_program(crate_path);
17
18    // Generate client code before build_idl consumes parsed
19    let client_code = codegen::rust::generate_client(&parsed);
20    let client_cargo_toml = codegen::rust::generate_cargo_toml(&parsed.crate_name, &parsed.version);
21
22    // Build the IDL
23    let idl = parser::build_idl(parsed);
24
25    // Write IDL JSON to target/idl/
26    let idl_dir = PathBuf::from("target").join("idl");
27    std::fs::create_dir_all(&idl_dir)?;
28
29    let idl_path = idl_dir.join(format!("{}.idl.json", idl.metadata.name));
30    let json = serde_json::to_string_pretty(&idl)
31        .map_err(|e| anyhow::anyhow!("failed to serialize IDL: {e}"))?;
32    std::fs::write(&idl_path, &json)?;
33
34    if generate_typescript {
35        let ts_code = codegen::typescript::generate_ts_client(&idl);
36        let ts_kit_code = codegen::typescript::generate_ts_client_kit(&idl);
37
38        // Write TypeScript clients to target/client/typescript/<name>/
39        let ts_dir = PathBuf::from("target")
40            .join("client")
41            .join("typescript")
42            .join(&idl.metadata.name);
43        std::fs::create_dir_all(&ts_dir)?;
44        std::fs::write(ts_dir.join("web3.ts"), &ts_code)?;
45        std::fs::write(ts_dir.join("kit.ts"), &ts_kit_code)?;
46
47        // Write package.json for the TS client
48        let needs_codecs =
49            !idl.types.is_empty() || idl.instructions.iter().any(|ix| !ix.args.is_empty());
50        let codecs_dep = if needs_codecs {
51            "\n    \"@solana/codecs\": \"^6.2.0\","
52        } else {
53            ""
54        };
55        let ts_package_json = format!(
56            r#"{{
57  "name": "{crate_name}-client",
58  "version": "{version}",
59  "private": true,
60  "exports": {{
61    "./web3.js": "./web3.ts",
62    "./kit": "./kit.ts"
63  }},
64  "dependencies": {{{codecs_dep}
65    "@solana/kit": "^6.0.0",
66    "@solana/web3.js": "github:blueshift-gg/web3.js#v2"
67  }}
68}}
69"#,
70            crate_name = idl.metadata.crate_name,
71            version = idl.metadata.version,
72        );
73        std::fs::write(ts_dir.join("package.json"), &ts_package_json)?;
74    }
75
76    // Write Rust client as a standalone crate in target/client/rust/<name>-client/
77    let crate_name = &idl.metadata.crate_name;
78    let client_dir = PathBuf::from("target")
79        .join("client")
80        .join("rust")
81        .join(format!("{}-client", crate_name));
82    let client_src_dir = client_dir.join("src");
83    std::fs::create_dir_all(&client_src_dir)?;
84
85    std::fs::write(client_dir.join("Cargo.toml"), &client_cargo_toml)?;
86    std::fs::write(client_src_dir.join("lib.rs"), &client_code)?;
87
88    Ok(())
89}
90
91pub fn run(command: IdlCommand) -> CliResult {
92    let crate_path = &command.crate_path;
93
94    if !crate_path.exists() {
95        eprintln!("Error: path does not exist: {}", crate_path.display());
96        std::process::exit(1);
97    }
98
99    generate(crate_path, true)?;
100    println!("  {}", crate::style::success("IDL generated"));
101    Ok(())
102}