diff --git a/Cargo.lock b/Cargo.lock index 4459065cf0d..809e50e80fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,6 +1590,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.3.0" @@ -1877,6 +1888,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -3399,6 +3416,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tldextract" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec03259a0567ad58eed30812bc3e5eda8030f154abc70317ab57b14f00699ca4" +dependencies = [ + "idna 0.2.3", + "log", + "regex", + "serde_json", + "thiserror", + "url", +] + [[package]] name = "tokio" version = "1.22.0" @@ -3648,7 +3679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", + "idna 0.3.0", "percent-encoding", "serde", ] @@ -3995,6 +4026,7 @@ dependencies = [ "target-lexicon 0.12.5", "tempdir", "tempfile", + "tldextract", "toml", "unix_mode", "url", diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 69f29f6b261..8909d2539d7 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -72,6 +72,7 @@ nuke-dir = { version = "0.1.0", optional = true } webc = { version = "3.0.1", optional = true } isatty = "0.1.9" dialoguer = "0.10.2" +tldextract = "0.6.0" [build-dependencies] chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] } diff --git a/lib/cli/src/commands/add.rs b/lib/cli/src/commands/add.rs index 76c4ffb2c3a..eb331c23b8c 100644 --- a/lib/cli/src/commands/add.rs +++ b/lib/cli/src/commands/add.rs @@ -40,7 +40,7 @@ impl Add { let bindings = self.lookup_bindings(®istry)?; - let mut cmd = self.target().command(&bindings); + let mut cmd = self.target()?.command(&bindings)?; #[cfg(feature = "debug")] log::debug!("Running {cmd:?}"); @@ -67,7 +67,7 @@ impl Add { log::debug!("Querying WAPM for the bindings packages"); let mut bindings_to_add = Vec::new(); - let language = self.target().language(); + let language = self.target()?.language(); for pkg in &self.packages { let bindings = lookup_bindings_for_package(registry, pkg, &language) @@ -90,14 +90,17 @@ impl Add { } } - fn target(&self) -> Target { + fn target(&self) -> Result { match (self.pip, self.npm, self.yarn) { - (true, false, false) => Target::Pip, - (false, true, false) => Target::Npm { dev: self.dev }, - (false, false, true) => Target::Yarn { dev: self.dev }, - _ => unreachable!( - "Clap should ensure at least one item in the \"bindings\" group is specified" - ), + (false, false, false) => Err(anyhow::anyhow!( + "at least one of --npm, --pip or --yarn has to be specified" + )), + (true, false, false) => Ok(Target::Pip), + (false, true, false) => Ok(Target::Npm { dev: self.dev }), + (false, false, true) => Ok(Target::Yarn { dev: self.dev }), + _ => Err(anyhow::anyhow!( + "only one of --npm, --pip or --yarn has to be specified" + )), } } } @@ -153,13 +156,43 @@ impl Target { /// `npm.cmd` or `yarn.ps1`). /// /// See for more. - fn command(self, packages: &[Bindings]) -> Command { + fn command(self, packages: &[Bindings]) -> Result { let command_line = match self { - Target::Pip => "pip install", - Target::Yarn { dev: true } => "yarn add --dev", - Target::Yarn { dev: false } => "yarn add", - Target::Npm { dev: true } => "npm install --dev", - Target::Npm { dev: false } => "npm install", + Target::Pip => { + if Command::new("pip").arg("--version").output().is_ok() { + "pip install" + } else if Command::new("pip3").arg("--version").output().is_ok() { + "pip3 install" + } else if Command::new("python").arg("--version").output().is_ok() { + "python -m pip install" + } else if Command::new("python3").arg("--version").output().is_ok() { + "python3 -m pip install" + } else { + return Err(anyhow::anyhow!( + "neither pip, pip3, python or python3 installed" + )); + } + } + Target::Yarn { dev } => { + if Command::new("yarn").arg("--version").output().is_err() { + return Err(anyhow::anyhow!("yarn not installed")); + } + if dev { + "yarn add --dev" + } else { + "yarn add" + } + } + Target::Npm { dev } => { + if Command::new("npm").arg("--version").output().is_err() { + return Err(anyhow::anyhow!("yarn not installed")); + } + if dev { + "npm install --dev" + } else { + "npm install" + } + } }; let mut command_line = command_line.to_string(); @@ -171,11 +204,11 @@ impl Target { if cfg!(windows) { let mut cmd = Command::new("cmd"); cmd.arg("/C").arg(command_line); - cmd + Ok(cmd) } else { let mut cmd = Command::new("sh"); cmd.arg("-c").arg(command_line); - cmd + Ok(cmd) } } } diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 290c1e73f9f..8e70b40ebfa 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -17,27 +17,32 @@ impl Login { match self.token.as_ref() { Some(s) => Ok(s.clone()), None => { - let registry_host = url::Url::parse(&wasmer_registry::format_graphql( - &self.registry, - )) - .map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Invalid registry for login {}: {e}", self.registry), - ) - })?; - let registry_host = registry_host.host_str().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Invalid registry for login {}: no host", self.registry), - ) - })?; - Input::new() - .with_prompt(&format!( - "Please paste the login token from https://{}/me:\"", - registry_host - )) - .interact_text() + 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 for https://{d}.{s}/me") + } + _ => "Please paste the login token".to_string(), + }; + #[cfg(test)] + { + Ok(login_prompt) + } + #[cfg(not(test))] + { + Input::new().with_prompt(&login_prompt).interact_text() + } } } } @@ -49,3 +54,23 @@ impl Login { .map_err(|e| anyhow::anyhow!("{e}")) } } + +#[test] +fn test_login_2() { + let login = Login { + registry: "wapm.dev".to_string(), + token: None, + }; + + assert_eq!( + login.get_token_or_ask_user().unwrap(), + "Please paste the login token for https://wapm.dev/me" + ); + + let login = Login { + registry: "wapm.dev".to_string(), + token: Some("abc".to_string()), + }; + + assert_eq!(login.get_token_or_ask_user().unwrap(), "abc"); +}