cli: Add shell command (#303)
This commit is contained in:
parent
74424fed7b
commit
ccf18557f9
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue