Sfoglia il codice sorgente

cli: Add `idl type` command (#3017)

acheron 1 anno fa
parent
commit
8528092545
3 ha cambiato i file con 64 aggiunte e 46 eliminazioni
  1. 1 0
      CHANGELOG.md
  2. 62 7
      cli/src/lib.rs
  3. 1 39
      cli/src/rust_template.rs

+ 1 - 0
CHANGELOG.md

@@ -24,6 +24,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 - spl: Export `spl-associated-token-account` crate ([#2999](https://github.com/coral-xyz/anchor/pull/2999)).
 - lang: Support legacy IDLs with `declare_program!` ([#2997](https://github.com/coral-xyz/anchor/pull/2997)).
 - cli: Add `idl convert` command ([#3009](https://github.com/coral-xyz/anchor/pull/3009)).
+- cli: Add `idl type` command ([#3017](https://github.com/coral-xyz/anchor/pull/3017)).
 
 ### Fixes
 

+ 62 - 7
cli/src/lib.rs

@@ -17,7 +17,7 @@ use flate2::read::GzDecoder;
 use flate2::read::ZlibDecoder;
 use flate2::write::{GzEncoder, ZlibEncoder};
 use flate2::Compression;
-use heck::{ToKebabCase, ToSnakeCase};
+use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToSnakeCase};
 use regex::{Regex, RegexBuilder};
 use reqwest::blocking::multipart::{Form, Part};
 use reqwest::blocking::Client;
@@ -484,7 +484,7 @@ pub enum IdlCommand {
     /// The address can be a program, IDL account, or IDL buffer.
     Fetch {
         address: Pubkey,
-        /// Output file for the idl (stdout if not specified).
+        /// Output file for the IDL (stdout if not specified).
         #[clap(short, long)]
         out: Option<String>,
     },
@@ -492,7 +492,15 @@ pub enum IdlCommand {
     Convert {
         /// Path to the IDL file
         path: String,
-        /// Output file for the idl (stdout if not specified)
+        /// Output file for the IDL (stdout if not specified)
+        #[clap(short, long)]
+        out: Option<String>,
+    },
+    /// Generate TypeScript type for the IDL
+    Type {
+        /// Path to the IDL file
+        path: String,
+        /// Output file for the IDL (stdout if not specified)
         #[clap(short, long)]
         out: Option<String>,
     },
@@ -1534,7 +1542,7 @@ fn build_cwd_verifiable(
             // Write out the TypeScript type.
             println!("Writing the .ts file");
             let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.metadata.name));
-            fs::write(&ts_file, rust_template::idl_ts(&idl)?)?;
+            fs::write(&ts_file, idl_ts(&idl)?)?;
 
             // Copy out the TypeScript type.
             if !&cfg.workspace.types.is_empty() {
@@ -1842,7 +1850,7 @@ fn _build_rust_cwd(
         // Write out the JSON file.
         write_idl(&idl, OutFile::File(out))?;
         // Write out the TypeScript type.
-        fs::write(&ts_out, rust_template::idl_ts(&idl)?)?;
+        fs::write(&ts_out, idl_ts(&idl)?)?;
 
         // Copy out the TypeScript type.
         let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
@@ -1920,7 +1928,7 @@ fn _build_solidity_cwd(
     };
 
     // Write out the TypeScript type.
-    fs::write(&ts_out, rust_template::idl_ts(&idl)?)?;
+    fs::write(&ts_out, idl_ts(&idl)?)?;
     // Copy out the TypeScript type.
     let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
     if !&cfg.workspace.types.is_empty() {
@@ -2209,6 +2217,7 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
         } => idl_build(cfg_override, program_name, out, out_ts, no_docs, skip_lint),
         IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
         IdlCommand::Convert { path, out } => idl_convert(path, out),
+        IdlCommand::Type { path, out } => idl_type(path, out),
     }
 }
 
@@ -2673,7 +2682,7 @@ fn idl_build(
     write_idl(&idl, out)?;
 
     if let Some(path) = out_ts {
-        fs::write(path, rust_template::idl_ts(&idl)?)?;
+        fs::write(path, idl_ts(&idl)?)?;
     }
 
     Ok(())
@@ -2733,6 +2742,52 @@ fn idl_convert(path: String, out: Option<String>) -> Result<()> {
     write_idl(&idl, out)
 }
 
+fn idl_type(path: String, out: Option<String>) -> Result<()> {
+    let idl = fs::read(path)?;
+    let idl = Idl::from_slice_with_conversion(&idl)?;
+    let types = idl_ts(&idl)?;
+    match out {
+        Some(out) => fs::write(out, types)?,
+        _ => println!("{types}"),
+    };
+    Ok(())
+}
+
+fn idl_ts(idl: &Idl) -> Result<String> {
+    let idl_name = &idl.metadata.name;
+    let type_name = idl_name.to_pascal_case();
+    let idl = serde_json::to_string(idl)?;
+
+    // Convert every field of the IDL to camelCase
+    let camel_idl = Regex::new(r#""\w+":"([\w\d]+)""#)?
+        .captures_iter(&idl)
+        .fold(idl.clone(), |acc, cur| {
+            let name = cur.get(1).unwrap().as_str();
+
+            // Do not modify pubkeys
+            if Pubkey::from_str(name).is_ok() {
+                return acc;
+            }
+
+            let camel_name = name.to_lower_camel_case();
+            acc.replace(&format!(r#""{name}""#), &format!(r#""{camel_name}""#))
+        });
+
+    // Pretty format
+    let camel_idl = serde_json::to_string_pretty(&serde_json::from_str::<Idl>(&camel_idl)?)?;
+
+    Ok(format!(
+        r#"/**
+ * Program IDL in camelCase format in order to be used in JS/TS.
+ *
+ * Note that this is only a type helper and is not the actual IDL. The original
+ * IDL can be found at `target/idl/{idl_name}.json`.
+ */
+export type {type_name} = {camel_idl};
+"#
+    ))
+}
+
 fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
     let idl_json = serde_json::to_string_pretty(idl)?;
     match out {

+ 1 - 39
cli/src/rust_template.rs

@@ -2,11 +2,9 @@ use crate::{
     config::ProgramWorkspace, create_files, override_or_create_files, solidity_template, Files,
     VERSION,
 };
-use anchor_lang_idl::types::Idl;
 use anyhow::Result;
 use clap::{Parser, ValueEnum};
-use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
-use regex::Regex;
+use heck::{ToPascalCase, ToSnakeCase};
 use solana_sdk::{
     pubkey::Pubkey,
     signature::{read_keypair_file, write_keypair_file, Keypair},
@@ -18,7 +16,6 @@ use std::{
     io::Write as _,
     path::Path,
     process::Stdio,
-    str::FromStr,
 };
 
 /// Program initialization template
@@ -232,41 +229,6 @@ token = "{token}"
     )
 }
 
-pub fn idl_ts(idl: &Idl) -> Result<String> {
-    let idl_name = &idl.metadata.name;
-    let type_name = idl_name.to_pascal_case();
-    let idl = serde_json::to_string(idl)?;
-
-    // Convert every field of the IDL to camelCase
-    let camel_idl = Regex::new(r#""\w+":"([\w\d]+)""#)?
-        .captures_iter(&idl)
-        .fold(idl.clone(), |acc, cur| {
-            let name = cur.get(1).unwrap().as_str();
-
-            // Do not modify pubkeys
-            if Pubkey::from_str(name).is_ok() {
-                return acc;
-            }
-
-            let camel_name = name.to_lower_camel_case();
-            acc.replace(&format!(r#""{name}""#), &format!(r#""{camel_name}""#))
-        });
-
-    // Pretty format
-    let camel_idl = serde_json::to_string_pretty(&serde_json::from_str::<Idl>(&camel_idl)?)?;
-
-    Ok(format!(
-        r#"/**
- * Program IDL in camelCase format in order to be used in JS/TS.
- *
- * Note that this is only a type helper and is not the actual IDL. The original
- * IDL can be found at `target/idl/{idl_name}.json`.
- */
-export type {type_name} = {camel_idl};
-"#
-    ))
-}
-
 pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
     format!(
         r#"