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)).
|
* 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 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 `--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
|
## Breaking Changes
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ dependencies = [
|
||||||
name = "anchor-cli"
|
name = "anchor-cli"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anchor-client",
|
||||||
"anchor-lang",
|
"anchor-lang",
|
||||||
"anchor-syn",
|
"anchor-syn",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -158,6 +159,7 @@ dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
"solana-client",
|
"solana-client",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
@ -17,6 +17,7 @@ clap = "3.0.0-beta.1"
|
||||||
anyhow = "1.0.32"
|
anyhow = "1.0.32"
|
||||||
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
|
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
|
||||||
anchor-lang = { path = "../lang" }
|
anchor-lang = { path = "../lang" }
|
||||||
|
anchor-client = { path = "../client" }
|
||||||
anchor-syn = { path = "../lang/syn", features = ["idl"] }
|
anchor-syn = { path = "../lang/syn", features = ["idl"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
use anchor_client::Cluster;
|
||||||
use anchor_syn::idl::Idl;
|
use anchor_syn::idl::Idl;
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serum_common::client::Cluster;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::Keypair;
|
use solana_sdk::signature::Keypair;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -12,6 +14,7 @@ use std::str::FromStr;
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub cluster: Cluster,
|
pub cluster: Cluster,
|
||||||
|
pub clusters: Clusters,
|
||||||
pub wallet: WalletPath,
|
pub wallet: WalletPath,
|
||||||
pub test: Option<Test>,
|
pub test: Option<Test>,
|
||||||
}
|
}
|
||||||
|
@ -73,14 +76,24 @@ struct _Config {
|
||||||
cluster: String,
|
cluster: String,
|
||||||
wallet: String,
|
wallet: String,
|
||||||
test: Option<Test>,
|
test: Option<Test>,
|
||||||
|
clusters: Option<BTreeMap<String, BTreeMap<String, String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Config {
|
impl ToString for Config {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
|
let clusters = {
|
||||||
|
let c = ser_clusters(&self.clusters);
|
||||||
|
if c.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(c)
|
||||||
|
}
|
||||||
|
};
|
||||||
let cfg = _Config {
|
let cfg = _Config {
|
||||||
cluster: format!("{}", self.cluster),
|
cluster: format!("{}", self.cluster),
|
||||||
wallet: self.wallet.to_string(),
|
wallet: self.wallet.to_string(),
|
||||||
test: self.test.clone(),
|
test: self.test.clone(),
|
||||||
|
clusters,
|
||||||
};
|
};
|
||||||
|
|
||||||
toml::to_string(&cfg).expect("Must be well formed")
|
toml::to_string(&cfg).expect("Must be well formed")
|
||||||
|
@ -97,10 +110,53 @@ impl FromStr for Config {
|
||||||
cluster: cfg.cluster.parse()?,
|
cluster: cfg.cluster.parse()?,
|
||||||
wallet: shellexpand::tilde(&cfg.wallet).parse()?,
|
wallet: shellexpand::tilde(&cfg.wallet).parse()?,
|
||||||
test: cfg.test,
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Test {
|
pub struct Test {
|
||||||
pub genesis: Vec<GenesisEntry>,
|
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");
|
serum_common::home_path!(WalletPath, ".config/solana/id.json");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! CLI for workspace management of anchor programs.
|
//! 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::idl::{IdlAccount, IdlInstruction};
|
||||||
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
|
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
|
||||||
use anchor_syn::idl::Idl;
|
use anchor_syn::idl::Idl;
|
||||||
|
@ -22,10 +23,12 @@ use solana_sdk::signature::Keypair;
|
||||||
use solana_sdk::signature::Signer;
|
use solana_sdk::signature::Signer;
|
||||||
use solana_sdk::sysvar;
|
use solana_sdk::sysvar;
|
||||||
use solana_sdk::transaction::Transaction;
|
use solana_sdk::transaction::Transaction;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Child, Stdio};
|
use std::process::{Child, Stdio};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
@ -139,6 +142,16 @@ pub enum Command {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
subcmd: ClusterCommand,
|
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)]
|
#[derive(Debug, Clap)]
|
||||||
|
@ -253,6 +266,7 @@ fn main() -> Result<()> {
|
||||||
#[cfg(feature = "dev")]
|
#[cfg(feature = "dev")]
|
||||||
Command::Airdrop { url } => airdrop(url),
|
Command::Airdrop { url } => airdrop(url),
|
||||||
Command::Cluster { subcmd } => cluster(subcmd),
|
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");
|
println!("* Testnet - https://testnet.solana.com");
|
||||||
Ok(())
|
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 crate::VERSION;
|
||||||
|
use anyhow::Result;
|
||||||
use heck::{CamelCase, SnakeCase};
|
use heck::{CamelCase, SnakeCase};
|
||||||
|
|
||||||
pub fn virtual_manifest() -> &'static str {
|
pub fn virtual_manifest() -> &'static str {
|
||||||
|
@ -190,3 +192,50 @@ target
|
||||||
**/*.rs.bk
|
**/*.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" }
|
anchor-lang = { path = "../lang", version = "0.5.0" }
|
||||||
anyhow = "1.0.32"
|
anyhow = "1.0.32"
|
||||||
regex = "1.4.5"
|
regex = "1.4.5"
|
||||||
|
serde = { version = "1.0.122", features = ["derive"] }
|
||||||
solana-client = "1.6.6"
|
solana-client = "1.6.6"
|
||||||
solana-sdk = "1.6.6"
|
solana-sdk = "1.6.6"
|
||||||
thiserror = "1.0.20"
|
thiserror = "1.0.20"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum Cluster {
|
pub enum Cluster {
|
||||||
Testnet,
|
Testnet,
|
||||||
Mainnet,
|
Mainnet,
|
||||||
|
|
Loading…
Reference in New Issue