cli: Add shell command (#303)

This commit is contained in:
Armani Ferrante 2021-05-22 16:06:08 -07:00 committed by GitHub
parent 74424fed7b
commit ccf18557f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 191 additions and 3 deletions

View File

@ -16,6 +16,7 @@ incremented for features.
* ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
* cli: Add yarn flag to test command ([#267](https://github.com/project-serum/anchor/pull/267)).
* cli: Add `--skip-build` flag to test command ([301](https://github.com/project-serum/anchor/pull/301)).
* cli: Add `anchor shell` command to spawn a node shell populated with an Anchor.toml based environment ([#303](https://github.com/project-serum/anchor/pull/303)).
## Breaking Changes

2
Cargo.lock generated
View File

@ -130,6 +130,7 @@ dependencies = [
name = "anchor-cli"
version = "0.5.0"
dependencies = [
"anchor-client",
"anchor-lang",
"anchor-syn",
"anyhow",
@ -158,6 +159,7 @@ dependencies = [
"anchor-lang",
"anyhow",
"regex",
"serde",
"solana-client",
"solana-sdk",
"thiserror",

View File

@ -17,6 +17,7 @@ clap = "3.0.0-beta.1"
anyhow = "1.0.32"
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
anchor-lang = { path = "../lang" }
anchor-client = { path = "../client" }
anchor-syn = { path = "../lang/syn", features = ["idl"] }
serde_json = "1.0"
shellexpand = "2.1.0"

View File

@ -1,8 +1,10 @@
use anchor_client::Cluster;
use anchor_syn::idl::Idl;
use anyhow::{anyhow, Error, Result};
use serde::{Deserialize, Serialize};
use serum_common::client::Cluster;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use std::collections::BTreeMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;
@ -12,6 +14,7 @@ use std::str::FromStr;
#[derive(Debug, Default)]
pub struct Config {
pub cluster: Cluster,
pub clusters: Clusters,
pub wallet: WalletPath,
pub test: Option<Test>,
}
@ -73,14 +76,24 @@ struct _Config {
cluster: String,
wallet: String,
test: Option<Test>,
clusters: Option<BTreeMap<String, BTreeMap<String, String>>>,
}
impl ToString for Config {
fn to_string(&self) -> String {
let clusters = {
let c = ser_clusters(&self.clusters);
if c.len() == 0 {
None
} else {
Some(c)
}
};
let cfg = _Config {
cluster: format!("{}", self.cluster),
wallet: self.wallet.to_string(),
test: self.test.clone(),
clusters,
};
toml::to_string(&cfg).expect("Must be well formed")
@ -97,10 +110,53 @@ impl FromStr for Config {
cluster: cfg.cluster.parse()?,
wallet: shellexpand::tilde(&cfg.wallet).parse()?,
test: cfg.test,
clusters: cfg
.clusters
.map_or(Ok(BTreeMap::new()), |c| deser_clusters(c))?,
})
}
}
fn ser_clusters(
clusters: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
) -> BTreeMap<String, BTreeMap<String, String>> {
clusters
.iter()
.map(|(cluster, programs)| {
let cluster = cluster.to_string();
let programs = programs
.iter()
.map(|(name, deployment)| (name.clone(), deployment.program_id.to_string()))
.collect::<BTreeMap<String, String>>();
(cluster, programs)
})
.collect::<BTreeMap<String, BTreeMap<String, String>>>()
}
fn deser_clusters(
clusters: BTreeMap<String, BTreeMap<String, String>>,
) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
clusters
.iter()
.map(|(cluster, programs)| {
let cluster: Cluster = cluster.parse()?;
let programs = programs
.iter()
.map(|(name, program_id)| {
Ok((
name.clone(),
ProgramDeployment {
name: name.clone(),
program_id: program_id.parse()?,
},
))
})
.collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
Ok((cluster, programs))
})
.collect::<Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>>>()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Test {
pub genesis: Vec<GenesisEntry>,
@ -177,4 +233,18 @@ impl Program {
}
}
pub type Clusters = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
#[derive(Debug, Default)]
pub struct ProgramDeployment {
pub name: String,
pub program_id: Pubkey,
}
pub struct ProgramWorkspace {
pub name: String,
pub program_id: Pubkey,
pub idl: Idl,
}
serum_common::home_path!(WalletPath, ".config/solana/id.json");

View File

@ -1,6 +1,7 @@
//! CLI for workspace management of anchor programs.
use crate::config::{read_all_programs, Config, Program};
use crate::config::{read_all_programs, Config, Program, ProgramWorkspace};
use anchor_client::Cluster;
use anchor_lang::idl::{IdlAccount, IdlInstruction};
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
use anchor_syn::idl::Idl;
@ -22,10 +23,12 @@ use solana_sdk::signature::Keypair;
use solana_sdk::signature::Signer;
use solana_sdk::sysvar;
use solana_sdk::transaction::Transaction;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::{Child, Stdio};
use std::str::FromStr;
use std::string::ToString;
mod config;
@ -139,6 +142,16 @@ pub enum Command {
#[clap(subcommand)]
subcmd: ClusterCommand,
},
/// Starts a node shell with an Anchor client setup according to the local
/// config.
Shell {
/// The cluster config to use.
#[clap(short, long)]
cluster: Option<String>,
/// Local path to the wallet keypair file.
#[clap(short, long)]
wallet: Option<String>,
},
}
#[derive(Debug, Clap)]
@ -253,6 +266,7 @@ fn main() -> Result<()> {
#[cfg(feature = "dev")]
Command::Airdrop { url } => airdrop(url),
Command::Cluster { subcmd } => cluster(subcmd),
Command::Shell { cluster, wallet } => shell(cluster, wallet),
}
}
@ -1589,3 +1603,52 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> {
println!("* Testnet - https://testnet.solana.com");
Ok(())
}
fn shell(cluster: Option<String>, wallet: Option<String>) -> Result<()> {
with_workspace(|cfg, _path, _cargo| {
let cluster = match cluster {
None => cfg.cluster.clone(),
Some(c) => Cluster::from_str(&c)?,
};
let wallet = match wallet {
None => cfg.wallet.to_string(),
Some(c) => c,
};
let programs = {
let idls: HashMap<String, Idl> = read_all_programs()?
.iter()
.map(|program| (program.idl.name.clone(), program.idl.clone()))
.collect();
match cfg.clusters.get(&cluster) {
None => Vec::new(),
Some(programs) => programs
.iter()
.map(|(name, program_deployment)| ProgramWorkspace {
name: name.to_string(),
program_id: program_deployment.program_id,
idl: match idls.get(name) {
None => {
println!("Unable to find IDL for {}", name);
std::process::exit(1);
}
Some(idl) => idl.clone(),
},
})
.collect::<Vec<ProgramWorkspace>>(),
}
};
let js_code = template::node_shell(cluster.url(), &wallet, programs)?;
let mut child = std::process::Command::new("node")
.args(&["-e", &js_code, "-i", "--experimental-repl-await"])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
if !child.wait()?.success() {
println!("Error running node shell");
return Ok(());
}
Ok(())
})
}

View File

@ -1,4 +1,6 @@
use crate::config::ProgramWorkspace;
use crate::VERSION;
use anyhow::Result;
use heck::{CamelCase, SnakeCase};
pub fn virtual_manifest() -> &'static str {
@ -190,3 +192,50 @@ target
**/*.rs.bk
"#
}
pub fn node_shell(
cluster_url: &str,
wallet_path: &str,
programs: Vec<ProgramWorkspace>,
) -> Result<String> {
let mut eval_string = format!(
r#"
const anchor = require('@project-serum/anchor');
const web3 = anchor.web3;
const PublicKey = anchor.web3.PublicKey;
const __wallet = new anchor.Wallet(
Buffer.from(
JSON.parse(
require('fs').readFileSync(
"{}",
{{
encoding: "utf-8",
}},
),
),
),
);
const __connection = new web3.Connection("{}", "processed");
const provider = new anchor.Provider(__connection, __wallet, {{
commitment: "processed",
preflightcommitment: "processed",
}});
anchor.setProvider(provider);
"#,
wallet_path, cluster_url,
);
for program in programs {
eval_string.push_str(&format!(
r#"
anchor.workspace.{} = new anchor.Program({}, new PublicKey("{}"), provider);
"#,
program.name,
serde_json::to_string(&program.idl)?,
program.program_id.to_string()
));
}
Ok(eval_string)
}

View File

@ -10,6 +10,7 @@ description = "Rust client for Anchor programs"
anchor-lang = { path = "../lang", version = "0.5.0" }
anyhow = "1.0.32"
regex = "1.4.5"
serde = { version = "1.0.122", features = ["derive"] }
solana-client = "1.6.6"
solana-sdk = "1.6.6"
thiserror = "1.0.20"

View File

@ -1,7 +1,8 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Cluster {
Testnet,
Mainnet,