diff --git a/Cargo.lock b/Cargo.lock index d334035ea1e..c99406c8aaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,9 +560,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc" dependencies = [ "clap_builder", "clap_derive", @@ -575,15 +575,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eef05769009513df2eb1c3b4613e7fad873a14c600ff025b08f250f59fee7de" dependencies = [ - "clap 4.3.4", + "clap 4.3.5", "log", ] [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae" dependencies = [ "anstream", "anstyle", @@ -5643,7 +5643,7 @@ dependencies = [ "cargo_metadata", "cfg-if 1.0.0", "chrono", - "clap 4.3.4", + "clap 4.3.5", "colored 2.0.0", "dialoguer", "dirs", @@ -5735,7 +5735,7 @@ dependencies = [ "atty", "bytesize", "cfg-if 1.0.0", - "clap 4.3.4", + "clap 4.3.5", "colored 2.0.0", "distance", "fern", @@ -5815,7 +5815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "401220ca92419731ffb670b6d6f8442fc32b110f6e9bb5658c4cfa658c48ab32" dependencies = [ "anyhow", - "clap 4.3.4", + "clap 4.3.5", "clap-verbosity-flag", "comfy-table", "dialoguer", @@ -6025,6 +6025,7 @@ name = "wasmer-registry" version = "5.1.0" dependencies = [ "anyhow", + "clap 4.3.5", "console", "dirs", "filetime", diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index b0a04270fa7..7000c9bd9ff 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -39,7 +39,7 @@ wasmer-wasix-experimental-io-devices = { version = "0.8.0", path = "../wasi-expe wasmer-wast = { version = "=4.0.0-beta.3", path = "../../tests/lib/wast", optional = true } wasmer-cache = { version = "=4.0.0-beta.3", path = "../cache", features = ["blake3-pure"] } wasmer-types = { version = "=4.0.0-beta.3", path = "../types", features = ["enable-serde"] } -wasmer-registry = { version = "5.1.0", path = "../registry", features = ["build-package"] } +wasmer-registry = { version = "5.1.0", path = "../registry", features = ["build-package", "clap"] } wasmer-object = { version = "=4.0.0-beta.3", path = "../object", optional = true } virtual-fs = { version = "0.6.0", path = "../virtual-fs", default-features = false, features = ["host-fs"] } virtual-net = { version = "0.3.0", path = "../virtual-net" } diff --git a/lib/cli/src/commands/add.rs b/lib/cli/src/commands/add.rs index 04f6038e143..ddf33ba476d 100644 --- a/lib/cli/src/commands/add.rs +++ b/lib/cli/src/commands/add.rs @@ -2,14 +2,13 @@ use std::process::{Command, Stdio}; use anyhow::{Context, Error}; use clap::Parser; -use wasmer_registry::{Bindings, ProgrammingLanguage, WasmerConfig}; +use wasmer_registry::{wasmer_env::WasmerEnv, Bindings, ProgrammingLanguage}; /// Add a Wasmer package's bindings to your application. #[derive(Debug, Parser)] pub struct Add { - /// The registry to fetch bindings from. - #[clap(long, env = "WASMER_REGISTRY")] - registry: Option, + #[clap(flatten)] + env: WasmerEnv, /// Add the JavaScript bindings using "npm install". #[clap(long, groups = &["bindings", "js"])] npm: bool, @@ -32,10 +31,11 @@ impl Add { anyhow::ensure!(!self.packages.is_empty(), "No packages specified"); let registry = self - .registry() + .env + .registry_endpoint() .context("Unable to determine which registry to use")?; - let bindings = self.lookup_bindings(®istry)?; + let bindings = self.lookup_bindings(registry.as_str())?; let mut cmd = self.target()?.command(&bindings)?; cmd.stdin(Stdio::null()) @@ -71,20 +71,6 @@ impl Add { Ok(bindings_to_add) } - fn registry(&self) -> Result { - match &self.registry { - Some(r) => Ok(r.clone()), - None => { - let wasmer_dir = - WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?; - let cfg = WasmerConfig::from_file(&wasmer_dir) - .map_err(Error::msg) - .context("Unable to load Wasmer config file")?; - Ok(cfg.registry.get_current_registry()) - } - } - } - fn target(&self) -> Result { match (self.pip, self.npm, self.yarn) { (false, false, false) => Err(anyhow::anyhow!( diff --git a/lib/cli/src/commands/config.rs b/lib/cli/src/commands/config.rs index 0de850a6839..119d8b67c07 100644 --- a/lib/cli/src/commands/config.rs +++ b/lib/cli/src/commands/config.rs @@ -1,14 +1,15 @@ use crate::VERSION; use anyhow::{Context, Result}; use clap::Parser; -use std::env; -use std::path::PathBuf; use std::str::ParseBoolError; -use wasmer_registry::WasmerConfig; +use wasmer_registry::{wasmer_env::WasmerEnv, WasmerConfig}; #[derive(Debug, Parser)] /// The options for the `wasmer config` subcommand: `wasmer config get --OPTION` or `wasmer config set [FLAG]` pub struct Config { + #[clap(flatten)] + env: WasmerEnv, + #[clap(flatten)] flags: Flags, /// Subcommand for `wasmer config get | set` @@ -168,29 +169,12 @@ impl Config { fn inner_execute(&self) -> Result<()> { if let Some(s) = self.set.as_ref() { - return s.execute(); + return s.execute(&self.env); } let flags = &self.flags; - let key = "WASMER_DIR"; - let wasmer_dir = env::var(key) - .ok() - .or_else(|| option_env!("WASMER_INSTALL_PREFIX").map(str::to_string)) - .or_else(|| { - // Allowing deprecated function home_dir since it works fine, - // and will never be removed from std. - #[allow(deprecated)] - let dir = std::env::home_dir()?.join(".wasmer").to_str()?.to_string(); - - Some(dir) - }) - .context(format!( - "failed to retrieve the {} environment variables", - key - ))?; - - let prefix = PathBuf::from(wasmer_dir); + let prefix = self.env.dir(); let prefixdir = prefix.display().to_string(); let bindir = prefix.join("bin").display().to_string(); @@ -233,9 +217,7 @@ impl Config { } if flags.config_path { - let wasmer_dir = WasmerConfig::get_wasmer_dir() - .map_err(|e| anyhow::anyhow!("could not find wasmer dir: {e}"))?; - let path = WasmerConfig::get_file_location(&wasmer_dir); + let path = WasmerConfig::get_file_location(self.env.dir()); println!("{}", path.display()); } @@ -244,16 +226,10 @@ impl Config { } impl GetOrSet { - fn execute(&self) -> Result<()> { - let wasmer_dir = WasmerConfig::get_wasmer_dir() - .map_err(|e| anyhow::anyhow!("could not find wasmer dir: {e}"))?; - let config_file = WasmerConfig::get_file_location(&wasmer_dir); - let mut config = WasmerConfig::from_file(&wasmer_dir).map_err(|e| { - anyhow::anyhow!( - "could not find config file {e} at {}", - config_file.display() - ) - })?; + fn execute(&self, env: &WasmerEnv) -> Result<()> { + let config_file = WasmerConfig::get_file_location(env.dir()); + let mut config = env.config()?; + match self { GetOrSet::Get(g) => match g { RetrievableConfigField::RegistryUrl => { diff --git a/lib/cli/src/commands/init.rs b/lib/cli/src/commands/init.rs index 5b7586f9495..393554833b4 100644 --- a/lib/cli/src/commands/init.rs +++ b/lib/cli/src/commands/init.rs @@ -1,11 +1,13 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + use anyhow::Context; use cargo_metadata::{CargoOpt, MetadataCommand}; use clap::Parser; use indexmap::IndexMap; -use std::collections::HashMap; -use std::path::Path; -use std::path::PathBuf; -use wasmer_registry::WasmerConfig; +use wasmer_registry::wasmer_env::WasmerEnv; static NOTE: &str = "# See more keys and definitions at https://docs.wasmer.io/registry/manifest"; @@ -14,6 +16,9 @@ const NEWLINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; /// CLI args for the `wasmer init` command #[derive(Debug, Parser)] pub struct Init { + #[clap(flatten)] + env: WasmerEnv, + /// Initialize wasmer.toml for a library package #[clap(long, group = "crate-type")] pub lib: bool, @@ -136,6 +141,7 @@ impl Init { self.template.as_ref(), self.include.as_slice(), self.quiet, + self.env.dir(), )?; if let Some(parent) = target_file.parent() { @@ -347,6 +353,7 @@ fn construct_manifest( template: Option<&Template>, include_fs: &[String], quiet: bool, + wasmer_dir: &Path, ) -> Result { if let Some(ct) = cargo_toml.as_ref() { let msg = format!( @@ -365,9 +372,8 @@ fn construct_manifest( .map(|p| &p.name) .unwrap_or(fallback_package_name) }); - let wasmer_dir = WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?; let namespace = namespace.or_else(|| { - wasmer_registry::whoami(&wasmer_dir, None, None) + wasmer_registry::whoami(wasmer_dir, None, None) .ok() .map(|o| o.1) }); diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 06a90d9b315..773fd97a7d1 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -1,86 +1,122 @@ +use std::path::PathBuf; + use clap::Parser; #[cfg(not(test))] use dialoguer::Input; -use wasmer_registry::WasmerConfig; + +use wasmer_registry::wasmer_env::{Registry, WasmerEnv, WASMER_DIR}; /// Subcommand for listing packages #[derive(Debug, Clone, Parser)] pub struct Login { - /// Registry to log into (default: wasmer.io) - #[clap(long, default_value = "wasmer.io")] - pub registry: String, - /// Login token - #[clap(name = "TOKEN")] + /// Set Wasmer's home directory + #[clap(long, env = "WASMER_DIR", default_value = WASMER_DIR.as_os_str())] + pub wasmer_dir: PathBuf, + /// The registry to fetch packages from (inferred from the environment by + /// default) + #[clap(long, env = "WASMER_REGISTRY")] + pub registry: Option, + /// The API token to use when communicating with the registry (inferred from + /// the environment by default) pub token: Option, } impl Login { - fn get_token_or_ask_user(&self) -> Result { - match self.token.as_ref() { - Some(s) => Ok(s.clone()), - None => { - let registry_host = wasmer_registry::format_graphql(&self.registry); - let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default()) - .extract(®istry_host) - .map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Invalid registry for login {}: {e}", self.registry), - ) - })?; - let login_prompt = match ( - registry_tld.domain.as_deref(), - registry_tld.suffix.as_deref(), - ) { - (Some(d), Some(s)) => { - format!("Please paste the login token from https://{d}.{s}/settings/access-tokens") - } - _ => "Please paste the login token".to_string(), - }; - #[cfg(test)] - { - Ok(login_prompt) - } - #[cfg(not(test))] - { - Input::new().with_prompt(&login_prompt).interact_text() - } + fn get_token_or_ask_user(&self, env: &WasmerEnv) -> Result { + if let Some(token) = &self.token { + return Ok(token.clone()); + } + + let registry_host = env.registry_endpoint()?; + let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default()) + .extract(registry_host.as_str()) + .map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid registry for login {}: {e}", registry_host), + ) + })?; + let login_prompt = match ( + registry_tld.domain.as_deref(), + registry_tld.suffix.as_deref(), + ) { + (Some(d), Some(s)) => { + format!("Please paste the login token from https://{d}.{s}/settings/access-tokens") } + _ => "Please paste the login token".to_string(), + }; + #[cfg(test)] + { + Ok(login_prompt) } + #[cfg(not(test))] + { + let token = Input::new().with_prompt(&login_prompt).interact_text()?; + Ok(token) + } + } + + fn wasmer_env(&self) -> WasmerEnv { + WasmerEnv::new( + self.wasmer_dir.clone(), + self.registry.clone(), + self.token.clone(), + ) } /// execute [List] pub fn execute(&self) -> Result<(), anyhow::Error> { - let token = self.get_token_or_ask_user()?; - let wasmer_dir = - WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?; - match wasmer_registry::login::login_and_save_token(&wasmer_dir, &self.registry, &token)? { + let env = self.wasmer_env(); + let token = self.get_token_or_ask_user(&env)?; + + let registry = env.registry_endpoint()?; + match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { Some(s) => println!("Login for Wasmer user {:?} saved", s), None => println!( "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", - self.registry, token + registry, token ), } Ok(()) } } -#[test] -fn test_login_2() { - let login = Login { - registry: "wasmer.wtf".to_string(), - token: None, - }; +#[cfg(test)] +mod tests { + use tempfile::TempDir; + + use super::*; - assert_eq!( - login.get_token_or_ask_user().unwrap(), - "Please paste the login token from https://wasmer.wtf/settings/access-tokens" - ); + #[test] + fn interactive_login() { + let temp = TempDir::new().unwrap(); + let login = Login { + registry: Some("wasmer.wtf".into()), + wasmer_dir: temp.path().to_path_buf(), + token: None, + }; + let env = login.wasmer_env(); - let login = Login { - registry: "wasmer.wtf".to_string(), - token: Some("abc".to_string()), - }; + let token = login.get_token_or_ask_user(&env).unwrap(); + + assert_eq!( + token, + "Please paste the login token from https://wasmer.wtf/settings/access-tokens" + ); + } - assert_eq!(login.get_token_or_ask_user().unwrap(), "abc"); + #[test] + fn login_with_token() { + let temp = TempDir::new().unwrap(); + let login = Login { + registry: Some("wasmer.wtf".into()), + wasmer_dir: temp.path().to_path_buf(), + token: Some("abc".to_string()), + }; + let env = login.wasmer_env(); + + let token = login.get_token_or_ask_user(&env).unwrap(); + + assert_eq!(token, "abc"); + } } diff --git a/lib/cli/src/commands/publish.rs b/lib/cli/src/commands/publish.rs index bd332d6276c..0de1868b689 100644 --- a/lib/cli/src/commands/publish.rs +++ b/lib/cli/src/commands/publish.rs @@ -1,14 +1,14 @@ -use anyhow::Context; +use std::path::Path; + use clap::Parser; -use wasmer_registry::WasmerConfig; +use wasmer_registry::wasmer_env::WasmerEnv; use wasmer_wasix::runtime::resolver::WapmSource; /// Publish a package to the package registry. #[derive(Debug, Parser)] pub struct Publish { - /// Registry to publish to - #[clap(long)] - pub registry: Option, + #[clap(flatten)] + env: WasmerEnv, /// Run the publish logic without sending anything to the registry server #[clap(long, name = "dry-run")] pub dry_run: bool, @@ -21,9 +21,6 @@ pub struct Publish { /// Override the package version of the uploaded package in the wasmer.toml #[clap(long)] pub version: Option, - /// Override the token (by default, it will use the current logged in user) - #[clap(long)] - pub token: Option, /// Skip validation of the uploaded package #[clap(long)] pub no_validate: bool, @@ -38,18 +35,18 @@ impl Publish { /// Executes `wasmer publish` pub fn execute(&self) -> Result<(), anyhow::Error> { let publish = wasmer_registry::package::builder::Publish { - registry: self.registry.clone(), + registry: self.env.registry_endpoint().map(|u| u.to_string()).ok(), dry_run: self.dry_run, quiet: self.quiet, package_name: self.package_name.clone(), version: self.version.clone(), - token: self.token.clone(), + token: self.env.token(), no_validate: self.no_validate, package_path: self.package_path.clone(), }; publish.execute().map_err(on_error)?; - if let Err(e) = invalidate_graphql_query_cache() { + if let Err(e) = invalidate_graphql_query_cache(self.env.dir()) { tracing::warn!( error = &*e, "Unable to invalidate the cache used for package version queries", @@ -72,11 +69,7 @@ fn on_error(e: anyhow::Error) -> anyhow::Error { // are cleaner ways to achieve this, but for now we're just going to // clear out the whole GraphQL query cache. // See https://github.com/wasmerio/wasmer/pull/3983 for more -fn invalidate_graphql_query_cache() -> Result<(), anyhow::Error> { - let wasmer_dir = WasmerConfig::get_wasmer_dir() - .map_err(anyhow::Error::msg) - .context("Unable to determine the wasmer dir")?; - +fn invalidate_graphql_query_cache(wasmer_dir: &Path) -> Result<(), anyhow::Error> { WapmSource::invalidate_local_cache(wasmer_dir)?; Ok(()) diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index 82d0b17ff65..b5184b262bb 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -29,7 +29,7 @@ use wasmer::{ }; #[cfg(feature = "compiler")] use wasmer_compiler::ArtifactBuild; -use wasmer_registry::Package; +use wasmer_registry::{wasmer_env::WasmerEnv, Package}; use wasmer_wasix::{ bin_factory::BinaryPackage, runners::{MappedDirectory, Runner}, @@ -53,19 +53,11 @@ use crate::{commands::run::wasi::Wasi, error::PrettyError, logging::Output, stor const TICK: Duration = Duration::from_millis(250); -static WASMER_HOME: Lazy = Lazy::new(|| { - wasmer_registry::WasmerConfig::get_wasmer_dir() - .ok() - .or_else(|| dirs::home_dir().map(|home| home.join(".wasmer"))) - .unwrap_or_else(|| PathBuf::from(".wasmer")) -}); - /// The unstable `wasmer run` subcommand. #[derive(Debug, Parser)] pub struct Run { - /// The Wasmer home directory. - #[clap(long = "wasmer-dir", env = "WASMER_DIR", default_value = WASMER_HOME.as_os_str())] - wasmer_dir: PathBuf, + #[clap(flatten)] + env: WasmerEnv, #[clap(flatten)] store: StoreOptions, #[clap(flatten)] @@ -112,9 +104,9 @@ impl Run { } let (store, _) = self.store.get_store()?; - let runtime = - self.wasi - .prepare_runtime(store.engine().clone(), &self.wasmer_dir, handle)?; + let runtime = self + .wasi + .prepare_runtime(store.engine().clone(), &self.env, handle)?; // This is a slow operation, so let's temporarily wrap the runtime with // something that displays progress @@ -358,7 +350,7 @@ impl Run { }; let store = StoreOptions::default(); Ok(Run { - wasmer_dir: WASMER_HOME.clone(), + env: WasmerEnv::default(), store, wasi: Wasi::for_binfmt_interpreter()?, wcgi: WcgiOptions::default(), diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index 273329926b1..7bcb81e9de7 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -12,6 +12,7 @@ use tokio::runtime::Handle; use url::Url; use virtual_fs::{DeviceFile, FileSystem, PassthruFileSystem, RootFileSystemBuilder}; use wasmer::{Engine, Function, Instance, Memory32, Memory64, Module, RuntimeError, Store, Value}; +use wasmer_registry::wasmer_env::WasmerEnv; use wasmer_wasix::{ bin_factory::BinaryPackage, capabilities::Capabilities, @@ -110,10 +111,6 @@ pub struct Wasi { /// Require WASI modules to only import 1 version of WASI. #[clap(long = "deny-multiple-wasi-versions")] pub deny_multiple_wasi_versions: bool, - - /// The registry to use. - #[clap(long, env = "WASMER_REGISTRY", value_parser = parse_registry)] - pub registry: Option, } pub struct RunProperties { @@ -249,7 +246,7 @@ impl Wasi { pub fn prepare_runtime( &self, engine: Engine, - wasmer_dir: &Path, + env: &WasmerEnv, handle: Handle, ) -> Result { let mut rt = PluggableRuntime::new(Arc::new(TokioTaskManager::new(handle))); @@ -271,12 +268,12 @@ impl Wasi { let client = Arc::new(client); let package_loader = self - .prepare_package_loader(wasmer_dir, client.clone()) + .prepare_package_loader(env.dir(), client.clone()) .context("Unable to prepare the package loader")?; - let registry = self.prepare_source(wasmer_dir, client)?; + let registry = self.prepare_source(env, client)?; - let cache_dir = FileSystemCache::default_cache_dir(wasmer_dir); + let cache_dir = FileSystemCache::default_cache_dir(env.dir()); let module_cache = wasmer_wasix::runtime::module_cache::in_memory() .with_fallback(FileSystemCache::new(cache_dir)); @@ -328,7 +325,7 @@ impl Wasi { fn prepare_source( &self, - wasmer_dir: &Path, + env: &WasmerEnv, client: Arc, ) -> Result { let mut source = MultiSource::new(); @@ -343,13 +340,13 @@ impl Wasi { } source.add_source(preloaded); - let graphql_endpoint = self.graphql_endpoint(wasmer_dir)?; - let cache_dir = WapmSource::default_cache_dir(wasmer_dir); + let graphql_endpoint = self.graphql_endpoint(env)?; + let cache_dir = WapmSource::default_cache_dir(env.dir()); let wapm_source = WapmSource::new(graphql_endpoint, Arc::clone(&client)) .with_local_cache(cache_dir, WAPM_SOURCE_CACHE_TIMEOUT); source.add_source(wapm_source); - let cache_dir = WebSource::default_cache_dir(wasmer_dir); + let cache_dir = WebSource::default_cache_dir(env.dir()); source.add_source(WebSource::new(cache_dir, client)); source.add_source(FileSystemSource::default()); @@ -357,13 +354,12 @@ impl Wasi { Ok(source) } - fn graphql_endpoint(&self, wasmer_dir: &Path) -> Result { - if let Some(endpoint) = &self.registry { - return Ok(endpoint.clone()); + fn graphql_endpoint(&self, env: &WasmerEnv) -> Result { + if let Ok(endpoint) = env.registry_endpoint() { + return Ok(endpoint); } - let config = - wasmer_registry::WasmerConfig::from_file(wasmer_dir).map_err(anyhow::Error::msg)?; + let config = env.config()?; let graphql_endpoint = config.registry.get_graphql_url(); let graphql_endpoint = graphql_endpoint .parse() diff --git a/lib/cli/src/commands/whoami.rs b/lib/cli/src/commands/whoami.rs index 8d5b27fedee..e6ec1dc2a1c 100644 --- a/lib/cli/src/commands/whoami.rs +++ b/lib/cli/src/commands/whoami.rs @@ -1,21 +1,19 @@ use clap::Parser; -use wasmer_registry::WasmerConfig; +use wasmer_registry::wasmer_env::WasmerEnv; #[derive(Debug, Parser)] /// The options for the `wasmer whoami` subcommand pub struct Whoami { - /// Which registry to check the logged in username for - #[clap(long, name = "registry")] - pub registry: Option, + #[clap(flatten)] + env: WasmerEnv, } impl Whoami { /// Execute `wasmer whoami` pub fn execute(&self) -> Result<(), anyhow::Error> { - let wasmer_dir = - WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?; + let registry = self.env.registry_endpoint()?; let (registry, username) = - wasmer_registry::whoami(&wasmer_dir, self.registry.as_deref(), None)?; + wasmer_registry::whoami(self.env.dir(), Some(registry.as_str()), None)?; println!("logged into registry {registry:?} as user {username:?}"); Ok(()) } diff --git a/lib/registry/Cargo.toml b/lib/registry/Cargo.toml index 33deb99227f..6964f2d5214 100644 --- a/lib/registry/Cargo.toml +++ b/lib/registry/Cargo.toml @@ -13,39 +13,40 @@ rust-version.workspace = true build-package = ["rusqlite", "indexmap", "wasmer-wasm-interface", "wasmparser", "rpassword", "minisign", "time"] [dependencies] -dirs = "4.0.0" -graphql_client = "0.11.0" -serde = { version = "1.0.145", features = ["derive"] } anyhow = "1.0.65" -futures-util = "0.3.25" -whoami = "1.2.3" -serde_json = "1.0.85" -url = "2.3.1" -thiserror = "1.0.37" -toml = "0.5.9" -wasmer-toml = "0.6.0" -tar = "0.4.38" +clap = { version = "4.3.5", default-features = false, features = ["derive", "env"], optional = true } +console = "0.15.2" +dirs = "4.0.0" +filetime = "0.2.19" flate2 = "1.0.24" -semver = "1.0.14" -lzma-rs = "0.2.0" +futures-util = "0.3.25" +graphql_client = "0.11.0" hex = "0.4.3" -tokio = "1.24.0" -log = "0.4.17" -regex = "1.7.0" -filetime = "0.2.19" -tldextract = "0.6.0" -console = "0.15.2" +indexmap = { version = "1.9.3", optional = true } indicatif = "0.17.2" lazy_static = "1.4.0" -tempfile = "3.4.0" +log = "0.4.17" +lzma-rs = "0.2.0" +minisign = { version = "0.7.2", optional = true } +regex = "1.7.0" +reqwest = { version = "0.11.12", default-features = false, features = ["blocking", "multipart", "json", "stream"] } +rpassword = { version = "7.2.0", optional = true } rusqlite = { version = "0.28.0", optional = true, features = ["bundled"] } +semver = "1.0.14" +serde = { version = "1.0.145", features = ["derive"] } +serde_json = "1.0.85" +tar = "0.4.38" +tempfile = "3.4.0" +thiserror = "1.0.37" time = { version = "0.3.17", default-features = false, features = ["parsing", "std", "formatting"], optional = true } -indexmap = { version = "1.9.3", optional = true } +tldextract = "0.6.0" +tokio = "1.24.0" +toml = "0.5.9" +url = "2.3.1" +wasmer-toml = "0.6.0" wasmer-wasm-interface = { version = "4.0.0-beta.3", path = "../wasm-interface", optional = true } wasmparser = { version = "0.51.4", optional = true } -rpassword = { version = "7.2.0", optional = true } -minisign = { version = "0.7.2", optional = true } -reqwest = { version = "0.11.12", default-features = false, features = ["blocking", "multipart", "json", "stream"] } +whoami = "1.2.3" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs index 522f174871a..88ce6b5948f 100644 --- a/lib/registry/src/lib.rs +++ b/lib/registry/src/lib.rs @@ -18,8 +18,9 @@ pub mod package; pub mod publish; pub mod types; pub mod utils; +pub mod wasmer_env; -pub use client::RegistryClient; +pub use crate::client::RegistryClient; use std::{ fmt, diff --git a/lib/registry/src/wasmer_env.rs b/lib/registry/src/wasmer_env.rs new file mode 100644 index 00000000000..b7a2c214dc1 --- /dev/null +++ b/lib/registry/src/wasmer_env.rs @@ -0,0 +1,124 @@ +use std::path::{Path, PathBuf}; + +use crate::WasmerConfig; +use anyhow::{Context, Error}; +use url::Url; + +/// Command-line flags for determining the local "Wasmer Environment". +/// +/// This is where you access `$WASMER_DIR`, the `$WASMER_DIR/wasmer.toml` config +/// file, and specify the current registry. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "clap", derive(clap::Parser))] +pub struct WasmerEnv { + /// Set Wasmer's home directory + #[cfg_attr(feature = "clap", clap(long, env = "WASMER_DIR", default_value = WASMER_DIR.as_os_str()))] + wasmer_dir: PathBuf, + /// The registry to fetch packages from (inferred from the environment by + /// default) + #[cfg_attr(feature = "clap", clap(long, env = "WASMER_REGISTRY"))] + registry: Option, + /// The API token to use when communicating with the registry (inferred from + /// the environment by default) + #[cfg_attr(feature = "clap", clap(long, env = "WASMER_TOKEN"))] + token: Option, +} + +impl WasmerEnv { + pub fn new(wasmer_dir: PathBuf, registry: Option, token: Option) -> Self { + WasmerEnv { + wasmer_dir, + registry, + token, + } + } + + /// Get the GraphQL endpoint used to query the registry. + pub fn registry_endpoint(&self) -> Result { + if let Some(registry) = &self.registry { + return registry.graphql_endpoint(); + } + + let config = self.config()?; + let url = config.registry.get_current_registry().parse()?; + + Ok(url) + } + + /// Load the current Wasmer config. + pub fn config(&self) -> Result { + WasmerConfig::from_file(self.dir()) + .map_err(Error::msg) + .with_context(|| { + format!( + "Unable to load the config from the \"{}\" directory", + self.dir().display() + ) + }) + } + + /// The directory all Wasmer artifacts are stored in. + pub fn dir(&self) -> &Path { + &self.wasmer_dir + } + + /// The API token for the active registry. + pub fn token(&self) -> Option { + let config = self.config().ok()?; + let login = config.registry.current_login()?; + Some(login.token.clone()) + } +} + +impl Default for WasmerEnv { + fn default() -> Self { + Self { + wasmer_dir: WASMER_DIR.clone(), + registry: None, + token: None, + } + } +} + +lazy_static::lazy_static! { + /// The default value for `$WASMER_DIR`. + pub static ref WASMER_DIR: PathBuf = match crate::WasmerConfig::get_wasmer_dir() { + Ok(path) => path, + Err(e) => { + if let Some(install_prefix) = option_env!("WASMER_INSTALL_PREFIX") { + return PathBuf::from(install_prefix); + } + + panic!("Unable to determine the wasmer dir: {e}"); + } + }; +} + +/// A registry as specified by the user. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Registry(String); + +impl Registry { + /// Get the [`Registry`]'s string representation. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Get the GraphQL endpoint for this [`Registry`]. + pub fn graphql_endpoint(&self) -> Result { + let url = crate::format_graphql(self.as_str()).parse()?; + Ok(url) + } +} + +impl From for Registry { + fn from(value: String) -> Self { + Registry(value) + } +} + +impl From<&str> for Registry { + fn from(value: &str) -> Self { + Registry(value.to_string()) + } +} diff --git a/tests/integration/cli/tests/config.rs b/tests/integration/cli/tests/config.rs index 9bbfcb122ce..994382fef23 100644 --- a/tests/integration/cli/tests/config.rs +++ b/tests/integration/cli/tests/config.rs @@ -1,5 +1,5 @@ +use assert_cmd::Command; use std::path::{Path, PathBuf}; -use std::process::Command; use wasmer_integration_tests_cli::{get_repo_root_path, get_wasmer_path}; fn get_wasmer_dir() -> Result { @@ -26,61 +26,50 @@ fn get_wasmer_dir() -> Result { } #[test] -fn wasmer_config_multiget() -> anyhow::Result<()> { - let bin_path = get_wasmer_dir()?.join("bin"); - let include_path = get_wasmer_dir()?.join("include"); - +fn wasmer_config_multiget() { + let wasmer_dir = get_wasmer_dir().unwrap(); + let bin_path = wasmer_dir.join("bin"); + let include_path = wasmer_dir.join("include"); let bin = format!("{}", bin_path.display()); let include = format!("-I{}", include_path.display()); - let output = Command::new(get_wasmer_path()) + let assert = Command::new(get_wasmer_path()) .arg("config") .arg("--bindir") .arg("--cflags") - .output()?; - - let lines = String::from_utf8_lossy(&output.stdout) - .lines() - .map(|s| s.trim().to_string()) - .collect::>(); + .assert(); - let expected = vec![bin, include]; - - assert_eq!(lines, expected); - - Ok(()) + assert + .success() + .stdout(predicates::str::contains(&bin)) + .stdout(predicates::str::contains(&include)); } #[test] -fn wasmer_config_error() -> anyhow::Result<()> { - let output = Command::new(get_wasmer_path()) +fn wasmer_config_error() { + let assert = Command::new(get_wasmer_path()) .arg("config") .arg("--bindir") .arg("--cflags") .arg("--pkg-config") - .output()?; + .assert(); - let lines = String::from_utf8_lossy(&output.stderr) - .lines() - .map(|s| s.trim().to_string()) - .collect::>(); - #[cfg(not(windows))] - let expected_1 = "Usage: wasmer config --bindir --cflags"; - #[cfg(windows)] - let expected_1 = "Usage: wasmer.exe config --bindir --cflags"; - - let expected = vec![ - "error: the argument '--bindir' cannot be used with '--pkg-config'", - "", - expected_1, - "", - "For more information, try '--help'.", - ]; - - assert_eq!(lines, expected); - - Ok(()) + let expected_1 = if cfg!(windows) { + "Usage: wasmer.exe config --bindir --cflags" + } else { + "Usage: wasmer config --bindir --cflags" + }; + + assert + .stderr(predicates::str::contains( + "error: the argument '--bindir' cannot be used with '--pkg-config'", + )) + .stderr(predicates::str::contains(expected_1)) + .stderr(predicates::str::contains( + "For more information, try '--help'.", + )); } + #[test] fn config_works() -> anyhow::Result<()> { let bindir = Command::new(get_wasmer_path())