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

This commit is contained in:
acheron 2024-06-10 23:44:56 +02:00 committed by GitHub
parent 29a6558d08
commit 8528092545
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 46 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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#"