diff --git a/tooling/cli/src/info/app.rs b/tooling/cli/src/info/app.rs index ff37302a0..ef4977b4d 100644 --- a/tooling/cli/src/info/app.rs +++ b/tooling/cli/src/info/app.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{SectionItem, Status}; +use super::SectionItem; use crate::helpers::framework; use std::{fs::read_to_string, path::PathBuf}; @@ -14,15 +14,11 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option) -> Vec, tauri_dir: Option) -> Vec (Vec, Option) { - let yarn_version = cross_command("yarn") +pub fn manager_version(package_manager: &str) -> Option { + cross_command(package_manager) .arg("-v") .output() .map(|o| { @@ -19,129 +18,58 @@ pub fn items(metadata: &VersionMetadata) -> (Vec, Option) { } }) .ok() - .unwrap_or_default(); - let yarn_version_c = yarn_version.clone(); + .unwrap_or_default() +} + +pub fn items(metadata: &VersionMetadata) -> Vec { let node_target_ver = metadata.js_cli.node.replace(">= ", ""); - ( - vec![ - SectionItem::new( - move || { - cross_command("node") - .arg("-v") - .output() - .map(|o| { - if o.status.success() { - let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); - let v = v - .split('\n') - .next() - .unwrap() - .strip_prefix('v') - .unwrap_or_default() - .trim(); - Some(( - format!("node: {}{}", v, { - let version = semver::Version::parse(v).unwrap(); - let target_version = semver::Version::parse(node_target_ver.as_str()).unwrap(); - if version < target_version { - format!( - " ({}, latest: {})", - "outdated".red(), - target_version.to_string().green() - ) - } else { - "".into() - } - }), - Status::Neutral, - )) + vec![ + SectionItem::new().action(move || { + cross_command("node") + .arg("-v") + .output() + .map(|o| { + if o.status.success() { + let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); + let v = v + .split('\n') + .next() + .unwrap() + .strip_prefix('v') + .unwrap_or_default() + .trim(); + ActionResult::Description(format!("node: {}{}", v, { + let version = semver::Version::parse(v).unwrap(); + let target_version = semver::Version::parse(node_target_ver.as_str()).unwrap(); + if version < target_version { + format!( + " ({}, latest: {})", + "outdated".red(), + target_version.to_string().green() + ) } else { - None + "".into() } - }) - .ok() - .unwrap_or_default() - }, - || None, - false, - ), - SectionItem::new( - || { - cross_command("pnpm") - .arg("-v") - .output() - .map(|o| { - if o.status.success() { - let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); - Some(( - format!("pnpm: {}", v.split('\n').next().unwrap()), - Status::Neutral, - )) - } else { - None - } - }) - .ok() - .unwrap_or_default() - }, - || None, - false, - ), - SectionItem::new( - || { - cross_command("bun") - .arg("-v") - .output() - .map(|o| { - if o.status.success() { - let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); - Some(( - format!("bun: {}", v.split('\n').next().unwrap()), - Status::Neutral, - )) - } else { - None - } - }) - .ok() - .unwrap_or_default() - }, - || None, - false, - ), - SectionItem::new( - move || { - yarn_version_c - .as_ref() - .map(|v| (format!("yarn: {v}"), Status::Neutral)) - }, - || None, - false, - ), - SectionItem::new( - || { - cross_command("npm") - .arg("-v") - .output() - .map(|o| { - if o.status.success() { - let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); - Some(( - format!("npm: {}", v.split('\n').next().unwrap()), - Status::Neutral, - )) - } else { - None - } - }) - .ok() - .unwrap_or_default() - }, - || None, - false, - ), - ], - yarn_version, - ) + })) + } else { + ActionResult::None + } + }) + .ok() + .unwrap_or_default() + }), + SectionItem::new().action(|| { + manager_version("pnpm") + .map(|v| format!("pnpm: {}", v)) + .into() + }), + SectionItem::new().action(|| { + manager_version("yarn") + .map(|v| format!("yarn: {}", v)) + .into() + }), + SectionItem::new().action(|| manager_version("npm").map(|v| format!("npm: {}", v)).into()), + SectionItem::new().action(|| manager_version("bun").map(|v| format!("bun: {}", v)).into()), + ] } diff --git a/tooling/cli/src/info/env_rust.rs b/tooling/cli/src/info/env_rust.rs index 40f03ebd5..8fa786e10 100644 --- a/tooling/cli/src/info/env_rust.rs +++ b/tooling/cli/src/info/env_rust.rs @@ -7,95 +7,55 @@ use super::Status; use colored::Colorize; use std::process::Command; +fn component_version(component: &str) -> Option<(String, Status)> { + Command::new(component) + .arg("-V") + .output() + .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string()) + .map(|v| { + format!( + "{component}: {}", + v.split('\n') + .next() + .unwrap() + .strip_prefix(&format!("{component} ")) + .unwrap_or_default() + ) + }) + .map(|desc| (desc, Status::Success)) + .ok() +} + pub fn items() -> Vec { vec![ - SectionItem::new( - || { - Some( - Command::new("rustc") - .arg("-V") - .output() - .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string()) - .map(|v| { + SectionItem::new().action(|| { + component_version("rustc") + .unwrap_or_else(|| { + ( format!( - "rustc: {}", - v.split('\n') - .next() - .unwrap() - .strip_prefix("rustc ") - .unwrap_or_default() - ) - }) - .map(|desc| (desc, Status::Success)) - .ok() - .unwrap_or_else(|| { - ( - format!( - "rustc: {}\nMaybe you don't have rust installed! Visit {}", - "not installed!".red(), - "https://rustup.rs/".cyan() - ), - Status::Error, - ) - }), - ) - }, - || None, - false, - ), - SectionItem::new( - || { - Some( - Command::new("cargo") - .arg("-V") - .output() - .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string()) - .map(|v| { + "rustc: {}\nMaybe you don't have rust installed! Visit {}", + "not installed!".red(), + "https://rustup.rs/".cyan() + ), + Status::Error, + ) + }).into() + }), + SectionItem::new().action(|| { + component_version("cargo") + .unwrap_or_else(|| { + ( format!( - "Cargo: {}", - v.split('\n') - .next() - .unwrap() - .strip_prefix("cargo ") - .unwrap_or_default() - ) - }) - .map(|desc| (desc, Status::Success)) - .ok() - .unwrap_or_else(|| { - ( - format!( - "Cargo: {}\nMaybe you don't have rust installed! Visit {}", - "not installed!".red(), - "https://rustup.rs/".cyan() - ), - Status::Error, - ) - }), - ) - }, - || None, - false, - ), - SectionItem::new( - || { - Some( - Command::new("rustup") - .arg("-V") - .output() - .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string()) - .map(|v| { - format!( - "rustup: {}", - v.split('\n') - .next() - .unwrap() - .strip_prefix("rustup ") - .unwrap_or_default() - ) - }) - .map(|desc| (desc, Status::Success)) - .ok() + "Cargo: {}\nMaybe you don't have rust installed! Visit {}", + "not installed!".red(), + "https://rustup.rs/".cyan() + ), + Status::Error, + ) + }).into() + }), + SectionItem::new().action(|| { + component_version("rustup") .unwrap_or_else(|| { ( format!( @@ -105,15 +65,9 @@ pub fn items() -> Vec { ), Status::Warning, ) - }), - ) - }, - || None, - false, - ), - SectionItem::new( - || { - Some( + }).into() + }), + SectionItem::new().action(|| { Command::new("rustup") .args(["show", "active-toolchain"]) .output() @@ -135,11 +89,7 @@ pub fn items() -> Vec { ), Status::Warning, ) - }), - ) - }, - || None, - false, - ), + }).into() + }), ] } diff --git a/tooling/cli/src/info/env_system.rs b/tooling/cli/src/info/env_system.rs index a4bc6a24b..bc7ea3cc5 100644 --- a/tooling/cli/src/info/env_system.rs +++ b/tooling/cli/src/info/env_system.rs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::SectionItem; -use super::Status; +use super::{SectionItem, Status}; use colored::Colorize; #[cfg(windows)] use serde::Deserialize; @@ -177,74 +176,55 @@ fn is_xcode_command_line_tools_installed() -> bool { pub fn items() -> Vec { vec![ - SectionItem::new( - || { - let os_info = os_info::get(); - Some(( - format!( - "OS: {} {} {:?}", - os_info.os_type(), - os_info.version(), - os_info.bitness() - ), - Status::Neutral, - )) - }, - || None, - false, - ), + SectionItem::new().action(|| { + let os_info = os_info::get(); + format!( + "OS: {} {} {:?}", + os_info.os_type(), + os_info.version(), + os_info.bitness() + ).into() + }), #[cfg(windows)] - SectionItem::new( - || { - let error = || { - format!( - "Webview2: {}\nVisit {}", - "not installed!".red(), - "https://developer.microsoft.com/en-us/microsoft-edge/webview2/".cyan() - ) - }; - Some( - webview2_version() - .map(|v| { - v.map(|v| (format!("WebView2: {}", v), Status::Success)) - .unwrap_or_else(|| (error(), Status::Error)) - }) - .unwrap_or_else(|_| (error(), Status::Error)), - ) - }, - || None, - false, - ), + SectionItem::new().action(|| { + let error = format!( + "Webview2: {}\nVisit {}", + "not installed!".red(), + "https://developer.microsoft.com/en-us/microsoft-edge/webview2/".cyan() + ); + webview2_version() + .map(|v| { + v.map(|v| (format!("WebView2: {}", v), Status::Success)) + .unwrap_or_else(|| (error.clone(), Status::Error)) + }) + .unwrap_or_else(|_| (error, Status::Error)).into() + }), #[cfg(windows)] - SectionItem::new( - || { - let build_tools = build_tools_version().unwrap_or_default(); - if build_tools.is_empty() { - Some(( + SectionItem::new().action(|| { + let build_tools = build_tools_version().unwrap_or_default(); + if build_tools.is_empty() { + ( format!( "Couldn't detect any Visual Studio or VS Build Tools instance with MSVC and SDK components. Download from {}", "https://aka.ms/vs/17/release/vs_BuildTools.exe".cyan() ), Status::Error, - )) - } else { - Some(( - format!( - "MSVC: {}{}", - if build_tools.len() > 1 { - format!("\n {} ", "-".cyan()) - } else { - "".into() - }, - build_tools.join(format!("\n {} ", "-".cyan()).as_str()), - ), - Status::Success, - )) - } - }, - || None, - false, - ), + ).into() + } else { + ( + format!( + "MSVC: {}{}", + if build_tools.len() > 1 { + format!("\n {} ", "-".cyan()) + } else { + "".into() + }, + build_tools.join(format!("\n {} ", "-".cyan()).as_str()), + ), + Status::Success, + ).into() + } + }), #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -252,9 +232,7 @@ pub fn items() -> Vec { target_os = "openbsd", target_os = "netbsd" ))] - SectionItem::new( - || { - Some( + SectionItem::new().action(|| { webkit2gtk_ver() .map(|v| (format!("webkit2gtk-4.0: {v}"), Status::Success)) .unwrap_or_else(|| { @@ -266,11 +244,8 @@ pub fn items() -> Vec { ), Status::Error, ) - }), - ) + }).into() }, - || None, - false, ), #[cfg(any( target_os = "linux", @@ -279,9 +254,7 @@ pub fn items() -> Vec { target_os = "openbsd", target_os = "netbsd" ))] - SectionItem::new( - || { - Some( + SectionItem::new().action(|| { rsvg2_ver() .map(|v| (format!("rsvg2: {v}"), Status::Success)) .unwrap_or_else(|| { @@ -293,16 +266,12 @@ pub fn items() -> Vec { ), Status::Error, ) - }), - ) + }).into() }, - || None, - false, ), #[cfg(target_os = "macos")] - SectionItem::new( - || { - Some(if is_xcode_command_line_tools_installed() { + SectionItem::new().action(|| { + if is_xcode_command_line_tools_installed() { ( "Xcode Command Line Tools: installed".into(), Status::Success, @@ -316,10 +285,8 @@ pub fn items() -> Vec { ), Status::Error, ) - }) + }.into() }, - || None, - false, ), ] } diff --git a/tooling/cli/src/info/mod.rs b/tooling/cli/src/info/mod.rs index 1abc22bbf..868dce287 100644 --- a/tooling/cli/src/info/mod.rs +++ b/tooling/cli/src/info/mod.rs @@ -4,7 +4,7 @@ use crate::Result; use clap::Parser; -use colored::Colorize; +use colored::{ColoredString, Colorize}; use dialoguer::{theme::ColorfulTheme, Confirm}; use serde::Deserialize; use std::{ @@ -92,6 +92,18 @@ pub enum Status { Error, } +impl Status { + fn color>(&self, s: S) -> ColoredString { + let s = s.as_ref(); + match self { + Status::Neutral => s.normal(), + Status::Success => s.green(), + Status::Warning => s.yellow(), + Status::Error => s.red(), + } + } +} + impl Display for Status { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( @@ -107,15 +119,55 @@ impl Display for Status { } } +#[derive(Default)] +pub enum ActionResult { + Full { + description: String, + status: Status, + }, + Description(String), + #[default] + None, +} + +impl From for ActionResult { + fn from(value: String) -> Self { + ActionResult::Description(value) + } +} + +impl From<(String, Status)> for ActionResult { + fn from(value: (String, Status)) -> Self { + ActionResult::Full { + description: value.0, + status: value.1, + } + } +} + +impl From> for ActionResult { + fn from(value: Option) -> Self { + value.map(ActionResult::Description).unwrap_or_default() + } +} + +impl From> for ActionResult { + fn from(value: Option<(String, Status)>) -> Self { + value + .map(|v| ActionResult::Full { + description: v.0, + status: v.1, + }) + .unwrap_or_default() + } +} + pub struct SectionItem { /// If description is none, the item is skipped description: Option, status: Status, - /// This closure return will be assigned to status and description - action: Box Option<(String, Status)>>, - /// This closure return will be assigned to status and description - action_if_err: Box Option<(String, Status)>>, - has_action_if_err: bool, + action: Option ActionResult>>, + action_if_err: Option ActionResult>>, } impl Display for SectionItem { @@ -131,29 +183,66 @@ impl Display for SectionItem { } impl SectionItem { - fn new< - F1: FnMut() -> Option<(String, Status)> + 'static, - F2: FnMut() -> Option<(String, Status)> + 'static, - >( - action: F1, - action_if_err: F2, - has_action_if_err: bool, - ) -> Self { + fn new() -> Self { Self { - action: Box::new(action), - action_if_err: Box::new(action_if_err), - has_action_if_err, + action: None, + action_if_err: None, description: None, status: Status::Neutral, } } - fn run(&mut self, interactive: bool) -> Status { - if let Some(ret) = (self.action)() { - self.description = Some(ret.0); - self.status = ret.1; - } - if self.status == Status::Error && interactive && self.has_action_if_err { + fn action ActionResult + 'static>(mut self, action: F) -> Self { + self.action = Some(Box::new(action)); + self + } + + // fn action_if_err ActionResult + 'static>(mut self, action: F) -> Self { + // self.action_if_err = Some(Box::new(action)); + // self + // } + + fn description>(mut self, description: S) -> Self { + self.description = Some(description.as_ref().to_string()); + self + } + + fn run_action(&mut self) { + let mut res = ActionResult::None; + if let Some(action) = &mut self.action { + res = action(); + } + self.apply_action_result(res); + } + + fn run_action_if_err(&mut self) { + let mut res = ActionResult::None; + if let Some(action) = &mut self.action_if_err { + res = action(); + } + self.apply_action_result(res); + } + + fn apply_action_result(&mut self, result: ActionResult) { + match result { + ActionResult::Full { + description, + status, + } => { + self.description = Some(description); + self.status = status; + } + ActionResult::Description(description) => { + self.description = Some(description); + } + ActionResult::None => {} + } + } + + fn run(&mut self, interactive: bool) -> Status { + self.run_action(); + + if self.status == Status::Error && interactive && self.action_if_err.is_some() { if let Some(description) = &self.description { let confirmed = Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(format!( @@ -163,13 +252,11 @@ impl SectionItem { .interact() .unwrap_or(false); if confirmed { - if let Some(ret) = (self.action_if_err)() { - self.description = Some(ret.0); - self.status = ret.1; - } + self.run_action_if_err() } } } + self.status } } @@ -192,12 +279,7 @@ impl Section<'_> { } let status_str = format!("[{status}]"); - let status = match status { - Status::Neutral => status_str.normal(), - Status::Success => status_str.green(), - Status::Warning => status_str.yellow(), - Status::Error => status_str.red(), - }; + let status = status.color(status_str); println!(); println!("{} {}", status, self.label.bold().yellow()); @@ -239,7 +321,7 @@ pub fn command(options: Options) -> Result<()> { }; environment.items.extend(env_system::items()); environment.items.extend(env_rust::items()); - let (items, yarn_version) = env_nodejs::items(&metadata); + let items = env_nodejs::items(&metadata); environment.items.extend(items); let mut packages = Section { @@ -252,7 +334,7 @@ pub fn command(options: Options) -> Result<()> { .extend(packages_rust::items(app_dir, tauri_dir.clone())); packages .items - .extend(packages_nodejs::items(app_dir, &metadata, yarn_version)); + .extend(packages_nodejs::items(app_dir, &metadata)); let mut app = Section { label: "App", diff --git a/tooling/cli/src/info/packages_nodejs.rs b/tooling/cli/src/info/packages_nodejs.rs index 217ce34e2..13cbb7e80 100644 --- a/tooling/cli/src/info/packages_nodejs.rs +++ b/tooling/cli/src/info/packages_nodejs.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{cross_command, VersionMetadata}; -use super::{SectionItem, Status}; +use super::SectionItem; +use super::{cross_command, env_nodejs::manager_version, VersionMetadata}; use colored::Colorize; use serde::Deserialize; use std::fmt::Display; @@ -241,11 +241,7 @@ fn get_package_manager>(app_dir_entries: &[T]) -> PackageManager { } } -pub fn items( - app_dir: Option<&PathBuf>, - metadata: &VersionMetadata, - yarn_version: Option, -) -> Vec { +pub fn items(app_dir: Option<&PathBuf>, metadata: &VersionMetadata) -> Vec { let mut package_manager = PackageManager::Npm; if let Some(app_dir) = &app_dir { let app_dir_entries = std::fs::read_dir(app_dir) @@ -256,7 +252,7 @@ pub fn items( } if package_manager == PackageManager::Yarn - && yarn_version + && manager_version("yarn") .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default()) .unwrap_or(false) { @@ -270,46 +266,40 @@ pub fn items( ("@tauri-apps/cli", Some(metadata.js_cli.version.clone())), ] { let app_dir = app_dir.clone(); - let item = SectionItem::new( - move || { - let version = version.clone().unwrap_or_else(|| { - npm_package_version(&package_manager, package, &app_dir) - .unwrap_or_default() - .unwrap_or_default() - }); - let latest_ver = npm_latest_version(&package_manager, package) + let item = SectionItem::new().action(move || { + let version = version.clone().unwrap_or_else(|| { + npm_package_version(&package_manager, package, &app_dir) .unwrap_or_default() - .unwrap_or_default(); + .unwrap_or_default() + }); + let latest_ver = npm_latest_version(&package_manager, package) + .unwrap_or_default() + .unwrap_or_default(); - Some(( - if version.is_empty() { - format!("{} {}: not installed!", package, "[NPM]".dimmed()) + if version.is_empty() { + format!("{} {}: not installed!", package, "".green()) + } else { + format!( + "{} {}: {}{}", + package, + "[NPM]".dimmed(), + version, + if !(version.is_empty() || latest_ver.is_empty()) { + let version = semver::Version::parse(version.as_str()).unwrap(); + let target_version = semver::Version::parse(latest_ver.as_str()).unwrap(); + + if version < target_version { + format!(" ({}, latest: {})", "outdated".yellow(), latest_ver.green()) + } else { + "".into() + } } else { - format!( - "{} {}: {}{}", - package, - "[NPM]".dimmed(), - version, - if !(version.is_empty() || latest_ver.is_empty()) { - let version = semver::Version::parse(version.as_str()).unwrap(); - let target_version = semver::Version::parse(latest_ver.as_str()).unwrap(); - - if version < target_version { - format!(" ({}, latest: {})", "outdated".yellow(), latest_ver.green()) - } else { - "".into() - } - } else { - "".into() - } - ) - }, - Status::Neutral, - )) - }, - || None, - false, - ); + "".into() + } + ) + } + .into() + }); items.push(item); } diff --git a/tooling/cli/src/info/packages_rust.rs b/tooling/cli/src/info/packages_rust.rs index 30359c043..0386ca4b2 100644 --- a/tooling/cli/src/info/packages_rust.rs +++ b/tooling/cli/src/info/packages_rust.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{SectionItem, Status}; +use super::{ActionResult, SectionItem}; use crate::interface::rust::get_workspace_dir; use colored::Colorize; use serde::Deserialize; @@ -212,96 +212,58 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option) -> Vec { - let version = semver::Version::parse(&version_string).unwrap(); - let target_version = semver::Version::parse(&target_version).unwrap(); - if version < target_version { - Some(format!( - " ({}, latest: {})", - "outdated".yellow(), - target_version.to_string().green() - )) - } else { - None - } + if version < target_version { + format!(" ({}, latest: {})", "outdated".yellow(), latest_ver.green()) + } else { + "".into() + } + } else { + "".into() + } + ) + .into() + } else { + ActionResult::None } - None => None, - }; - - items.push(SectionItem::new( - move || { - Some(( - format!( - "{} {}: {}{}", - dep, - "[RUST]".dimmed(), - version_string, - version_suffix - .clone() - .map(|s| format!(", {s}")) - .unwrap_or_else(|| "".into()) - ), - Status::Neutral, - )) - }, - || None, - false, - )); - } else { - items.push(SectionItem::new( - move || { - Some(( - format!("tauri-cli {}: not installed!", "[RUST]".dimmed()), - Status::Neutral, - )) - }, - || None, - false, - )); - } - } + }) + .unwrap_or_default() + }); + items.push(tauri_cli_rust_item); items }