Browse Source

cli: Global options to override Anchor.toml values (#313)

Armani Ferrante 4 years ago
parent
commit
1777ecaee4
3 changed files with 152 additions and 124 deletions
  1. 1 0
      CHANGELOG.md
  2. 19 1
      cli/src/config.rs
  3. 132 123
      cli/src/main.rs

+ 1 - 0
CHANGELOG.md

@@ -14,6 +14,7 @@ incremented for features.
 ## Features
 
 * ts: Address metadata is now optional for `anchor.workspace` clients ([#310](https://github.com/project-serum/anchor/pull/310)).
+* cli: Add global options for override Anchor.toml values ([#313](https://github.com/project-serum/anchor/pull/313)).
 
 ## [0.6.0] - 2021-05-23
 

+ 19 - 1
cli/src/config.rs

@@ -1,3 +1,4 @@
+use crate::ConfigOverride;
 use anchor_client::Cluster;
 use anchor_syn::idl::Idl;
 use anyhow::{anyhow, Error, Result};
@@ -27,8 +28,25 @@ pub struct ProviderConfig {
 pub type ClustersConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
 
 impl Config {
+    pub fn discover(
+        cfg_override: &ConfigOverride,
+    ) -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
+        Config::_discover().map(|opt| {
+            opt.map(|(mut cfg, cfg_path, cargo_toml)| {
+                if let Some(cluster) = cfg_override.cluster.clone() {
+                    cfg.provider.cluster = cluster;
+                }
+
+                if let Some(wallet) = cfg_override.wallet.clone() {
+                    cfg.provider.wallet = wallet;
+                }
+                (cfg, cfg_path, cargo_toml)
+            })
+        })
+    }
+
     // Searches all parent directories for an Anchor.toml file.
-    pub fn discover() -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
+    fn _discover() -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
         // Set to true if we ever see a Cargo.toml file when traversing the
         // parent directories.
         let mut cargo_toml = None;

+ 132 - 123
cli/src/main.rs

@@ -1,6 +1,6 @@
 //! CLI for workspace management of anchor programs.
 
-use crate::config::{read_all_programs, Config, Program, ProgramWorkspace};
+use crate::config::{read_all_programs, Config, Program, ProgramWorkspace, WalletPath};
 use anchor_client::Cluster;
 use anchor_lang::idl::{IdlAccount, IdlInstruction};
 use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
@@ -28,7 +28,6 @@ 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;
@@ -41,10 +40,22 @@ const DOCKER_BUILDER_VERSION: &str = VERSION;
 #[derive(Debug, Clap)]
 #[clap(version = VERSION)]
 pub struct Opts {
+    #[clap(flatten)]
+    pub cfg_override: ConfigOverride,
     #[clap(subcommand)]
     pub command: Command,
 }
 
+#[derive(Debug, Clap)]
+pub struct ConfigOverride {
+    /// Cluster override.
+    #[clap(global = true, long = "provider.cluster")]
+    cluster: Option<Cluster>,
+    /// Wallet override.
+    #[clap(global = true, long = "provider.wallet")]
+    wallet: Option<WalletPath>,
+}
+
 #[derive(Debug, Clap)]
 pub enum Command {
     /// Initializes a workspace.
@@ -97,24 +108,13 @@ pub enum Command {
     },
     /// Deploys each program in the workspace.
     Deploy {
-        #[clap(short, long)]
-        url: Option<String>,
-        #[clap(short, long)]
-        keypair: Option<String>,
         #[clap(short, long)]
         program_name: Option<String>,
     },
     /// Runs the deploy migration script.
-    Migrate {
-        #[clap(short, long)]
-        url: Option<String>,
-    },
+    Migrate,
     /// Deploys, initializes an IDL, and migrates all in one command.
     Launch {
-        #[clap(short, long)]
-        url: Option<String>,
-        #[clap(short, long)]
-        keypair: Option<String>,
         /// True if the build should be verifiable. If deploying to mainnet,
         /// this should almost always be set.
         #[clap(short, long)]
@@ -144,14 +144,7 @@ pub enum Command {
     },
     /// 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>,
-    },
+    Shell,
 }
 
 #[derive(Debug, Clap)]
@@ -235,43 +228,44 @@ pub enum ClusterCommand {
 fn main() -> Result<()> {
     let opts = Opts::parse();
     match opts.command {
-        Command::Init { name, typescript } => init(name, typescript),
-        Command::New { name } => new(name),
-        Command::Build { idl, verifiable } => build(idl, verifiable),
-        Command::Verify { program_id } => verify(program_id),
-        Command::Deploy {
-            url,
-            keypair,
-            program_name,
-        } => deploy(url, keypair, program_name),
+        Command::Init { name, typescript } => init(&opts.cfg_override, name, typescript),
+        Command::New { name } => new(&opts.cfg_override, name),
+        Command::Build { idl, verifiable } => build(&opts.cfg_override, idl, verifiable),
+        Command::Verify { program_id } => verify(&opts.cfg_override, program_id),
+        Command::Deploy { program_name } => deploy(&opts.cfg_override, program_name),
         Command::Upgrade {
             program_id,
             program_filepath,
-        } => upgrade(program_id, program_filepath),
-        Command::Idl { subcmd } => idl(subcmd),
-        Command::Migrate { url } => migrate(url),
+        } => upgrade(&opts.cfg_override, program_id, program_filepath),
+        Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd),
+        Command::Migrate => migrate(&opts.cfg_override),
         Command::Launch {
-            url,
-            keypair,
             verifiable,
             program_name,
-        } => launch(url, keypair, verifiable, program_name),
+        } => launch(&opts.cfg_override, verifiable, program_name),
         Command::Test {
             skip_deploy,
             skip_local_validator,
             skip_build,
             yarn,
             file,
-        } => test(skip_deploy, skip_local_validator, skip_build, yarn, file),
+        } => test(
+            &opts.cfg_override,
+            skip_deploy,
+            skip_local_validator,
+            skip_build,
+            yarn,
+            file,
+        ),
         #[cfg(feature = "dev")]
-        Command::Airdrop { url } => airdrop(url),
+        Command::Airdrop => airdrop(cfg_override),
         Command::Cluster { subcmd } => cluster(subcmd),
-        Command::Shell { cluster, wallet } => shell(cluster, wallet),
+        Command::Shell => shell(&opts.cfg_override),
     }
 }
 
-fn init(name: String, typescript: bool) -> Result<()> {
-    let cfg = Config::discover()?;
+fn init(cfg_override: &ConfigOverride, name: String, typescript: bool) -> Result<()> {
+    let cfg = Config::discover(cfg_override)?;
 
     if cfg.is_some() {
         println!("Anchor workspace already initialized");
@@ -328,8 +322,8 @@ fn init(name: String, typescript: bool) -> Result<()> {
 }
 
 // Creates a new program crate in the `programs/<name>` directory.
-fn new(name: String) -> Result<()> {
-    with_workspace(|_cfg, path, _cargo| {
+fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
+    with_workspace(cfg_override, |_cfg, path, _cargo| {
         match path.parent() {
             None => {
                 println!("Unable to make new program");
@@ -357,8 +351,8 @@ fn new_program(name: &str) -> Result<()> {
     Ok(())
 }
 
-fn build(idl: Option<String>, verifiable: bool) -> Result<()> {
-    let (cfg, path, cargo) = Config::discover()?.expect("Not in workspace.");
+fn build(cfg_override: &ConfigOverride, idl: Option<String>, verifiable: bool) -> Result<()> {
+    let (cfg, path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");
     let idl_out = match idl {
         Some(idl) => Some(PathBuf::from(idl)),
         None => {
@@ -524,14 +518,14 @@ fn _build_cwd(idl_out: Option<PathBuf>) -> Result<()> {
     write_idl(&idl, OutFile::File(out))
 }
 
-fn verify(program_id: Pubkey) -> Result<()> {
-    let (cfg, _path, cargo) = Config::discover()?.expect("Not in workspace.");
+fn verify(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
+    let (cfg, _path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");
     let cargo = cargo.ok_or(anyhow!("Must be inside program subdirectory."))?;
     let program_dir = cargo.parent().unwrap();
 
     // Build the program we want to verify.
     let cur_dir = std::env::current_dir()?;
-    build(None, true)?;
+    build(cfg_override, None, true)?;
     std::env::set_current_dir(&cur_dir)?;
 
     let local_idl = extract_idl("src/lib.rs")?;
@@ -545,7 +539,7 @@ fn verify(program_id: Pubkey) -> Result<()> {
     // Verify IDL (only if it's not a buffer account).
     if !is_buffer {
         std::env::set_current_dir(program_dir)?;
-        let deployed_idl = fetch_idl(program_id)?;
+        let deployed_idl = fetch_idl(cfg_override, program_id)?;
         if local_idl != deployed_idl {
             println!("Error: IDLs don't match");
             std::process::exit(1);
@@ -608,8 +602,10 @@ fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<bool
 }
 
 // Fetches an IDL for the given program_id.
-fn fetch_idl(idl_addr: Pubkey) -> Result<Idl> {
-    let cfg = Config::discover()?.expect("Inside a workspace").0;
+fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
+    let cfg = Config::discover(cfg_override)?
+        .expect("Inside a workspace")
+        .0;
     let client = RpcClient::new(cfg.provider.cluster.url().to_string());
 
     let mut account = client
@@ -640,35 +636,37 @@ fn extract_idl(file: &str) -> Result<Idl> {
     anchor_syn::parser::file::parse(&*file)
 }
 
-fn idl(subcmd: IdlCommand) -> Result<()> {
+fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
     match subcmd {
         IdlCommand::Init {
             program_id,
             filepath,
-        } => idl_init(program_id, filepath),
+        } => idl_init(cfg_override, program_id, filepath),
         IdlCommand::WriteBuffer {
             program_id,
             filepath,
-        } => idl_write_buffer(program_id, filepath).map(|_| ()),
-        IdlCommand::SetBuffer { program_id, buffer } => idl_set_buffer(program_id, buffer),
+        } => idl_write_buffer(cfg_override, program_id, filepath).map(|_| ()),
+        IdlCommand::SetBuffer { program_id, buffer } => {
+            idl_set_buffer(cfg_override, program_id, buffer)
+        }
         IdlCommand::Upgrade {
             program_id,
             filepath,
-        } => idl_upgrade(program_id, filepath),
+        } => idl_upgrade(cfg_override, program_id, filepath),
         IdlCommand::SetAuthority {
             program_id,
             address,
             new_authority,
-        } => idl_set_authority(program_id, address, new_authority),
-        IdlCommand::EraseAuthority { program_id } => idl_erase_authority(program_id),
-        IdlCommand::Authority { program_id } => idl_authority(program_id),
+        } => idl_set_authority(cfg_override, program_id, address, new_authority),
+        IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
+        IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
         IdlCommand::Parse { file, out } => idl_parse(file, out),
-        IdlCommand::Fetch { address, out } => idl_fetch(address, out),
+        IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
     }
 }
 
-fn idl_init(program_id: Pubkey, idl_filepath: String) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
+fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String) -> Result<()> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         let keypair = cfg.provider.wallet.to_string();
 
         let bytes = std::fs::read(idl_filepath)?;
@@ -681,8 +679,12 @@ fn idl_init(program_id: Pubkey, idl_filepath: String) -> Result<()> {
     })
 }
 
-fn idl_write_buffer(program_id: Pubkey, idl_filepath: String) -> Result<Pubkey> {
-    with_workspace(|cfg, _path, _cargo| {
+fn idl_write_buffer(
+    cfg_override: &ConfigOverride,
+    program_id: Pubkey,
+    idl_filepath: String,
+) -> Result<Pubkey> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         let keypair = cfg.provider.wallet.to_string();
 
         let bytes = std::fs::read(idl_filepath)?;
@@ -697,8 +699,8 @@ fn idl_write_buffer(program_id: Pubkey, idl_filepath: String) -> Result<Pubkey>
     })
 }
 
-fn idl_set_buffer(program_id: Pubkey, buffer: Pubkey) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
+fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pubkey) -> Result<()> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
             .map_err(|_| anyhow!("Unable to read keypair file"))?;
         let client = RpcClient::new(cfg.provider.cluster.url().to_string());
@@ -742,13 +744,17 @@ fn idl_set_buffer(program_id: Pubkey, buffer: Pubkey) -> Result<()> {
     })
 }
 
-fn idl_upgrade(program_id: Pubkey, idl_filepath: String) -> Result<()> {
-    let buffer = idl_write_buffer(program_id, idl_filepath)?;
-    idl_set_buffer(program_id, buffer)
+fn idl_upgrade(
+    cfg_override: &ConfigOverride,
+    program_id: Pubkey,
+    idl_filepath: String,
+) -> Result<()> {
+    let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath)?;
+    idl_set_buffer(cfg_override, program_id, buffer)
 }
 
-fn idl_authority(program_id: Pubkey) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
+fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         let client = RpcClient::new(cfg.provider.cluster.url().to_string());
         let idl_address = {
             let account = client
@@ -773,11 +779,12 @@ fn idl_authority(program_id: Pubkey) -> Result<()> {
 }
 
 fn idl_set_authority(
+    cfg_override: &ConfigOverride,
     program_id: Pubkey,
     address: Option<Pubkey>,
     new_authority: Pubkey,
 ) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         // Misc.
         let idl_address = match address {
             None => IdlAccount::address(&program_id),
@@ -826,7 +833,7 @@ fn idl_set_authority(
     })
 }
 
-fn idl_erase_authority(program_id: Pubkey) -> Result<()> {
+fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
     println!("Are you sure you want to erase the IDL authority: [y/n]");
 
     let stdin = std::io::stdin();
@@ -839,7 +846,7 @@ fn idl_erase_authority(program_id: Pubkey) -> Result<()> {
 
     // Program will treat the zero authority as erased.
     let new_authority = Pubkey::new_from_array([0u8; 32]);
-    idl_set_authority(program_id, None, new_authority)?;
+    idl_set_authority(cfg_override, program_id, None, new_authority)?;
 
     Ok(())
 }
@@ -917,8 +924,8 @@ fn idl_parse(file: String, out: Option<String>) -> Result<()> {
     write_idl(&idl, out)
 }
 
-fn idl_fetch(address: Pubkey, out: Option<String>) -> Result<()> {
-    let idl = fetch_idl(address)?;
+fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
+    let idl = fetch_idl(cfg_override, address)?;
     let out = match out {
         None => OutFile::Stdout,
         Some(out) => OutFile::File(PathBuf::from(out)),
@@ -942,18 +949,19 @@ enum OutFile {
 
 // Builds, deploys, and tests all workspace programs in a single command.
 fn test(
+    cfg_override: &ConfigOverride,
     skip_deploy: bool,
     skip_local_validator: bool,
     skip_build: bool,
     use_yarn: bool,
     file: Option<String>,
 ) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         // Bootup validator, if needed.
         let validator_handle = match cfg.provider.cluster.url() {
             "http://127.0.0.1:8899" => {
                 if !skip_build {
-                    build(None, false)?;
+                    build(cfg_override, None, false)?;
                 }
                 let flags = match skip_deploy {
                     true => None,
@@ -966,8 +974,8 @@ fn test(
             }
             _ => {
                 if !skip_deploy {
-                    build(None, false)?;
-                    deploy(None, None, None)?;
+                    build(cfg_override, None, false)?;
+                    deploy(cfg_override, None)?;
                 }
                 None
             }
@@ -1180,23 +1188,17 @@ fn start_test_validator(cfg: &Config, flags: Option<Vec<String>>) -> Result<Chil
     Ok(validator_handle)
 }
 
-fn deploy(
-    url: Option<String>,
-    keypair: Option<String>,
-    program_name: Option<String>,
-) -> Result<()> {
-    _deploy(url, keypair, program_name).map(|_| ())
+fn deploy(cfg_override: &ConfigOverride, program_name: Option<String>) -> Result<()> {
+    _deploy(cfg_override, program_name).map(|_| ())
 }
 
 fn _deploy(
-    url: Option<String>,
-    keypair: Option<String>,
+    cfg_override: &ConfigOverride,
     program_str: Option<String>,
 ) -> Result<Vec<(Pubkey, Program)>> {
-    with_workspace(|cfg, _path, _cargo| {
-        // Fallback to config vars if not provided via CLI.
-        let url = url.unwrap_or_else(|| cfg.provider.cluster.url().to_string());
-        let keypair = keypair.unwrap_or_else(|| cfg.provider.wallet.to_string());
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
+        let url = cfg.provider.cluster.url().to_string();
+        let keypair = cfg.provider.wallet.to_string();
 
         // Deploy the programs.
         println!("Deploying workspace: {}", url);
@@ -1265,11 +1267,15 @@ fn _deploy(
     })
 }
 
-fn upgrade(program_id: Pubkey, program_filepath: String) -> Result<()> {
+fn upgrade(
+    cfg_override: &ConfigOverride,
+    program_id: Pubkey,
+    program_filepath: String,
+) -> Result<()> {
     let path: PathBuf = program_filepath.parse().unwrap();
     let program_filepath = path.canonicalize()?.display().to_string();
 
-    with_workspace(|cfg, _path, _cargo| {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         let exit = std::process::Command::new("solana")
             .arg("program")
             .arg("deploy")
@@ -1293,18 +1299,16 @@ fn upgrade(program_id: Pubkey, program_filepath: String) -> Result<()> {
 }
 
 fn launch(
-    url: Option<String>,
-    keypair: Option<String>,
+    cfg_override: &ConfigOverride,
     verifiable: bool,
     program_name: Option<String>,
 ) -> Result<()> {
     // Build and deploy.
-    build(None, verifiable)?;
-    let programs = _deploy(url.clone(), keypair.clone(), program_name.clone())?;
+    build(cfg_override, None, verifiable)?;
+    let programs = _deploy(cfg_override, program_name.clone())?;
 
-    with_workspace(|cfg, _path, _cargo| {
-        let url = url.unwrap_or_else(|| cfg.provider.cluster.url().to_string());
-        let keypair = keypair.unwrap_or_else(|| cfg.provider.wallet.to_string());
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
+        let keypair = cfg.provider.wallet.to_string();
 
         // Add metadata to all IDLs.
         for (address, program) in programs {
@@ -1316,7 +1320,7 @@ fn launch(
         // Run migration script.
         if Path::new("migrations/deploy.js").exists() || Path::new("migrations/deploy.ts").exists()
         {
-            migrate(Some(url))?;
+            migrate(cfg_override)?;
         }
 
         Ok(())
@@ -1467,11 +1471,11 @@ fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8
     Ok(data)
 }
 
-fn migrate(url: Option<String>) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
+fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         println!("Running migration deploy script");
 
-        let url = url.unwrap_or_else(|| cfg.provider.cluster.url().to_string());
+        let url = cfg.provider.cluster.url().to_string();
         let cur_dir = std::env::current_dir()?;
         let module_path = cur_dir.join("migrations/deploy.js");
 
@@ -1521,7 +1525,10 @@ fn migrate(url: Option<String>) -> Result<()> {
 }
 
 fn set_workspace_dir_or_exit() {
-    let d = match Config::discover() {
+    let d = match Config::discover(&ConfigOverride {
+        cluster: None,
+        wallet: None,
+    }) {
         Err(_) => {
             println!("Not in anchor workspace.");
             std::process::exit(1);
@@ -1550,8 +1557,10 @@ fn set_workspace_dir_or_exit() {
 }
 
 #[cfg(feature = "dev")]
-fn airdrop(url: Option<String>) -> Result<()> {
-    let url = url.unwrap_or_else(|| "https://devnet.solana.com".to_string());
+fn airdrop(cfg_override: &ConfigOverride) -> Result<()> {
+    let url = cfg_override
+        .cluster
+        .unwrap_or_else(|| "https://devnet.solana.com".to_string());
     loop {
         let exit = std::process::Command::new("solana")
             .arg("airdrop")
@@ -1579,22 +1588,14 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> {
     Ok(())
 }
 
-fn shell(cluster: Option<String>, wallet: Option<String>) -> Result<()> {
-    with_workspace(|cfg, _path, _cargo| {
-        let cluster = match cluster {
-            None => cfg.provider.cluster.clone(),
-            Some(c) => Cluster::from_str(&c)?,
-        };
-        let wallet = match wallet {
-            None => cfg.provider.wallet.to_string(),
-            Some(c) => c,
-        };
+fn shell(cfg_override: &ConfigOverride) -> Result<()> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
         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) {
+            match cfg.clusters.get(&cfg.provider.cluster) {
                 None => Vec::new(),
                 Some(programs) => programs
                     .iter()
@@ -1612,7 +1613,11 @@ fn shell(cluster: Option<String>, wallet: Option<String>) -> Result<()> {
                     .collect::<Vec<ProgramWorkspace>>(),
             }
         };
-        let js_code = template::node_shell(cluster.url(), &wallet, programs)?;
+        let js_code = template::node_shell(
+            cfg.provider.cluster.url(),
+            &cfg.provider.wallet.to_string(),
+            programs,
+        )?;
         let mut child = std::process::Command::new("node")
             .args(&["-e", &js_code, "-i", "--experimental-repl-await"])
             .stdout(Stdio::inherit())
@@ -1634,14 +1639,18 @@ fn shell(cluster: Option<String>, wallet: Option<String>) -> Result<()> {
 //
 // The closure passed into this function must never change the working directory
 // to be outside the workspace. Doing so will have undefined behavior.
-fn with_workspace<R>(f: impl FnOnce(&Config, PathBuf, Option<PathBuf>) -> R) -> R {
+fn with_workspace<R>(
+    cfg_override: &ConfigOverride,
+    f: impl FnOnce(&Config, PathBuf, Option<PathBuf>) -> R,
+) -> R {
     set_workspace_dir_or_exit();
 
     clear_program_keys().unwrap();
 
-    let (cfg, cfg_path, cargo_toml) = Config::discover()
+    let (cfg, cfg_path, cargo_toml) = Config::discover(cfg_override)
         .expect("Previously set the workspace dir")
         .expect("Anchor.toml must always exist");
+
     let r = f(&cfg, cfg_path, cargo_toml);
 
     set_workspace_dir_or_exit();