diff --git a/.changes/colorful-cli.md b/.changes/colorful-cli.md new file mode 100644 index 000000000..911c737ba --- /dev/null +++ b/.changes/colorful-cli.md @@ -0,0 +1,6 @@ +--- +"cli.rs": "patch" +"cli.js": "patch" +--- + +Improve readability of the `info` subcommand output. diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs index a4e01f30f..47caff0b1 100644 --- a/tooling/bundler/src/bundle/updater_bundle.rs +++ b/tooling/bundler/src/bundle/updater_bundle.rs @@ -18,11 +18,7 @@ use std::{fs::File, io::prelude::*}; use zip::write::FileOptions; use crate::{bundle::Bundle, Settings}; -use std::{ - ffi::OsStr, - fs::{self}, - io::Write, -}; +use std::{fs, io::Write}; use anyhow::Context; use std::path::{Path, PathBuf}; @@ -43,6 +39,8 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result< // This is the Mac OS App packaged #[cfg(target_os = "macos")] fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result> { + use std::ffi::OsStr; + // find our .app or rebuild our bundle let bundle_path = match bundles .iter() @@ -83,6 +81,8 @@ fn bundle_update(settings: &Settings, bundles: &[Bundle]) -> crate::Result crate::Result> { + use std::ffi::OsStr; + // build our app actually we support only appimage on linux let bundle_path = match bundles .iter() diff --git a/tooling/cli/src/info.rs b/tooling/cli/src/info.rs index c0c63b50d..ee89488db 100644 --- a/tooling/cli/src/info.rs +++ b/tooling/cli/src/info.rs @@ -7,6 +7,7 @@ use crate::helpers::{ }; use crate::Result; use clap::Parser; +use colored::Colorize; use serde::Deserialize; use std::{ @@ -311,96 +312,6 @@ fn active_rust_toolchain() -> crate::Result> { Ok(toolchain) } -struct InfoBlock { - section: bool, - key: &'static str, - value: Option, - suffix: Option, -} - -impl InfoBlock { - fn new(key: &'static str) -> Self { - Self { - section: false, - key, - value: None, - suffix: None, - } - } - - fn section(mut self) -> Self { - self.section = true; - self - } - - fn value>>(mut self, value: V) -> Self { - self.value = value.into(); - self - } - - fn suffix>>(mut self, suffix: S) -> Self { - self.suffix = suffix.into(); - self - } - - fn display(&self) { - if self.section { - println!(); - } - print!("{}", self.key); - if let Some(value) = &self.value { - print!(" - {}", value); - } - if let Some(suffix) = &self.suffix { - print!("{}", suffix); - } - println!(); - } -} - -struct VersionBlock { - section: bool, - key: &'static str, - version: Option, - target_version: Option, -} - -impl VersionBlock { - fn new>>(key: &'static str, version: V) -> Self { - Self { - section: false, - key, - version: version.into(), - target_version: None, - } - } - - fn target_version>>(mut self, version: V) -> Self { - self.target_version = version.into(); - self - } - - fn display(&self) { - if self.section { - println!(); - } - print!("{}", self.key); - if let Some(version) = &self.version { - print!(" - {}", version); - } else { - print!(" - Not installed"); - } - if let (Some(version), Some(target_version)) = (&self.version, &self.target_version) { - let version = semver::Version::parse(version).unwrap(); - let target_version = semver::Version::parse(target_version).unwrap(); - if version < target_version { - print!(" (outdated, latest: {})", target_version); - } - } - println!(); - } -} - fn crate_version( tauri_dir: &Path, manifest: Option<&CargoManifest>, @@ -529,23 +440,120 @@ fn crate_version( (crate_version_string, suffix) } +fn indent(spaces: usize) { + print!( + "{}", + vec![0; spaces].iter().map(|_| " ").collect::() + ); +} + +struct Section(&'static str); +impl Section { + fn display(&self) { + println!(); + println!("{}", self.0.yellow().bold()); + } +} + +struct VersionBlock { + name: String, + version: String, + target_version: String, + indentation: usize, + skip_update: bool, +} + +impl VersionBlock { + fn new(name: impl Into, version: impl Into) -> Self { + Self { + name: name.into(), + version: version.into(), + target_version: "".into(), + indentation: 2, + skip_update: false, + } + } + + fn skip_update(mut self) -> Self { + self.skip_update = true; + self + } + + fn target_version(mut self, version: impl Into) -> Self { + self.target_version = version.into(); + self + } + + fn display(&self) { + indent(self.indentation); + print!("{} ", "›".cyan()); + print!("{}", self.name.bold()); + print!(": "); + print!( + "{}", + if self.version.is_empty() { + "Not installed!".red().to_string() + } else { + self.version.clone() + } + ); + if !self.target_version.is_empty() && !self.skip_update { + print!( + "({}, latest: {})", + "outdated".red(), + self.target_version.green() + ); + } + println!(); + } +} + +struct InfoBlock { + key: String, + value: String, + indentation: usize, +} + +impl InfoBlock { + fn new(key: impl Into, val: impl Into) -> Self { + Self { + key: key.into(), + value: val.into(), + indentation: 2, + } + } + + fn display(&self) { + indent(self.indentation); + print!("{} ", "›".cyan()); + print!("{}", self.key.bold()); + print!(": "); + print!("{}", self.value.clone()); + println!(); + } +} + pub fn command(_options: Options) -> Result<()> { + Section("Environment").display(); + let os_info = os_info::get(); - InfoBlock { - section: true, - key: "Operating System", - value: Some(format!( - "{}, version {} {:?}", + VersionBlock::new( + "OS", + format!( + "{} {} {:?}", os_info.os_type(), os_info.version(), os_info.bitness() - )), - suffix: None, - } + ), + ) .display(); #[cfg(windows)] - VersionBlock::new("Webview2", webview2_version().unwrap_or_default()).display(); + VersionBlock::new( + "Webview2", + webview2_version().unwrap_or_default().unwrap_or_default(), + ) + .display(); #[cfg(windows)] { @@ -554,24 +562,12 @@ pub fn command(_options: Options) -> Result<()> { .unwrap_or_default(); if build_tools.is_empty() { - InfoBlock { - section: false, - key: "Visual Studio Build Tools - Not installed", - value: None, - suffix: None, - } - .display(); + InfoBlock::new("MSVC", "").display(); } else { - InfoBlock { - section: false, - key: "Visual Studio Build Tools:", - value: None, - suffix: None, - } - .display(); - + InfoBlock::new("MSVC", "").display(); for i in build_tools { - VersionBlock::new(" ", i).display(); + indent(6); + println!("{}", format!("{} {}", "-".cyan(), i)); } } } @@ -585,6 +581,87 @@ pub fn command(_options: Options) -> Result<()> { .unwrap_or_default(); panic::set_hook(hook); + let metadata = serde_json::from_str::(include_str!("../metadata.json"))?; + VersionBlock::new( + "Node.js", + get_version("node", &[]) + .unwrap_or_default() + .unwrap_or_default() + .chars() + .skip(1) + .collect::(), + ) + .target_version(metadata.js_cli.node.replace(">= ", "")) + .skip_update() + .display(); + + VersionBlock::new( + "npm", + get_version("npm", &[]) + .unwrap_or_default() + .unwrap_or_default(), + ) + .display(); + VersionBlock::new( + "pnpm", + get_version("pnpm", &[]) + .unwrap_or_default() + .unwrap_or_default(), + ) + .display(); + VersionBlock::new( + "yarn", + get_version("yarn", &[]) + .unwrap_or_default() + .unwrap_or_default(), + ) + .display(); + VersionBlock::new( + "rustup", + get_version("rustup", &[]) + .unwrap_or_default() + .map(|v| { + let mut s = v.split(' '); + s.next(); + s.next().unwrap().to_string() + }) + .unwrap_or_default(), + ) + .display(); + VersionBlock::new( + "rustc", + get_version("rustc", &[]) + .unwrap_or_default() + .map(|v| { + let mut s = v.split(' '); + s.next(); + s.next().unwrap().to_string() + }) + .unwrap_or_default(), + ) + .display(); + VersionBlock::new( + "cargo", + get_version("cargo", &[]) + .unwrap_or_default() + .map(|v| { + let mut s = v.split(' '); + s.next(); + s.next().unwrap().to_string() + }) + .unwrap_or_default(), + ) + .display(); + InfoBlock::new( + "Rust toolchain", + active_rust_toolchain() + .unwrap_or_default() + .unwrap_or_default(), + ) + .display(); + + Section("Packages").display(); + let mut package_manager = PackageManager::Npm; if let Some(app_dir) = &app_dir { let file_names = read_dir(app_dir) @@ -601,76 +678,29 @@ pub fn command(_options: Options) -> Result<()> { .collect::>(); package_manager = get_package_manager(&file_names)?; } - - if let Some(node_version) = get_version("node", &[]).unwrap_or_default() { - InfoBlock::new("Node.js environment").section().display(); - let metadata = serde_json::from_str::(include_str!("../metadata.json"))?; + VersionBlock::new( + format!("{} {}", "@tauri-apps/cli", "[NPM]".dimmed()), + metadata.js_cli.version, + ) + .target_version( + npm_latest_version(&package_manager, "@tauri-apps/cli") + .unwrap_or_default() + .unwrap_or_default(), + ) + .display(); + if let Some(app_dir) = &app_dir { VersionBlock::new( - " Node.js", - node_version.chars().skip(1).collect::(), + format!("{} {}", "@tauri-apps/api", "[NPM]".dimmed()), + npm_package_version(&package_manager, "@tauri-apps/api", app_dir) + .unwrap_or_default() + .unwrap_or_default(), + ) + .target_version( + npm_latest_version(&package_manager, "@tauri-apps/api") + .unwrap_or_default() + .unwrap_or_default(), ) - .target_version(metadata.js_cli.node.replace(">= ", "")) .display(); - - VersionBlock::new(" @tauri-apps/cli", metadata.js_cli.version) - .target_version(npm_latest_version(&package_manager, "@tauri-apps/cli").unwrap_or_default()) - .display(); - if let Some(app_dir) = &app_dir { - VersionBlock::new( - " @tauri-apps/api", - npm_package_version(&package_manager, "@tauri-apps/api", app_dir).unwrap_or_default(), - ) - .target_version(npm_latest_version(&package_manager, "@tauri-apps/api").unwrap_or_default()) - .display(); - } - - InfoBlock::new("Global packages").section().display(); - - VersionBlock::new(" npm", get_version("npm", &[]).unwrap_or_default()).display(); - VersionBlock::new(" pnpm", get_version("pnpm", &[]).unwrap_or_default()).display(); - VersionBlock::new(" yarn", get_version("yarn", &[]).unwrap_or_default()).display(); - } - - InfoBlock::new("Rust environment").section().display(); - VersionBlock::new( - " rustup", - get_version("rustup", &[]).unwrap_or_default().map(|v| { - let mut s = v.split(' '); - s.next(); - s.next().unwrap().to_string() - }), - ) - .display(); - VersionBlock::new( - " rustc", - get_version("rustc", &[]).unwrap_or_default().map(|v| { - let mut s = v.split(' '); - s.next(); - s.next().unwrap().to_string() - }), - ) - .display(); - VersionBlock::new( - " cargo", - get_version("cargo", &[]).unwrap_or_default().map(|v| { - let mut s = v.split(' '); - s.next(); - s.next().unwrap().to_string() - }), - ) - .display(); - VersionBlock::new(" toolchain", active_rust_toolchain().unwrap_or_default()).display(); - - if let Some(app_dir) = app_dir { - InfoBlock::new("App directory structure") - .section() - .display(); - for entry in read_dir(app_dir)? { - let entry = entry?; - if entry.path().is_dir() { - println!("/{}", entry.path().file_name().unwrap().to_string_lossy()); - } - } } let hook = panic::take_hook(); @@ -683,9 +713,7 @@ pub fn command(_options: Options) -> Result<()> { panic::set_hook(hook); if tauri_dir.is_some() || app_dir.is_some() { - InfoBlock::new("App").section().display(); - - if let Some(tauri_dir) = tauri_dir { + if let Some(tauri_dir) = tauri_dir.clone() { let manifest: Option = if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) { toml::from_str(&manifest_contents).ok() @@ -700,46 +728,57 @@ pub fn command(_options: Options) -> Result<()> { }; for (dep, label) in [ - ("tauri", " tauri"), - ("tauri-build", " tauri-build"), - ("tao", " tao"), - ("wry", " wry"), + ("tauri", format!("{} {}", "tauri", "[RUST]".dimmed())), + ( + "tauri-build", + format!("{} {}", "tauri-build", "[RUST]".dimmed()), + ), + ("tao", format!("{} {}", "tao", "[RUST]".dimmed())), + ("wry", format!("{} {}", "wry", "[RUST]".dimmed())), ] { let (version_string, version_suffix) = crate_version(&tauri_dir, manifest.as_ref(), lock.as_ref(), dep); - InfoBlock::new(label) - .value(version_string) - .suffix(version_suffix) - .display(); + VersionBlock::new( + label, + format!( + "{},{}", + version_string, + version_suffix.unwrap_or_else(|| "".into()) + ), + ) + .display(); } + } + } + if tauri_dir.is_some() || app_dir.is_some() { + Section("App").display(); + if tauri_dir.is_some() { if let Ok(config) = get_config(None) { let config_guard = config.lock().unwrap(); let config = config_guard.as_ref().unwrap(); - InfoBlock::new(" build-type") - .value(if config.tauri.bundle.active { + InfoBlock::new( + "build-type", + if config.tauri.bundle.active { "bundle".to_string() } else { "build".to_string() - }) - .display(); - InfoBlock::new(" CSP") - .value( - config - .tauri - .security - .csp - .as_ref() - .map(|c| c.to_string()) - .unwrap_or_else(|| "unset".to_string()), - ) - .display(); - InfoBlock::new(" distDir") - .value(config.build.dist_dir.to_string()) - .display(); - InfoBlock::new(" devPath") - .value(config.build.dev_path.to_string()) - .display(); + }, + ) + .display(); + InfoBlock::new( + "CSP", + config + .tauri + .security + .csp + .clone() + .map(|c| c.to_string()) + .unwrap_or_else(|| "unset".to_string()), + ) + .display(); + InfoBlock::new("distDir", config.build.dist_dir.to_string()).display(); + InfoBlock::new("devPath", config.build.dev_path.to_string()).display(); } } @@ -747,14 +786,10 @@ pub fn command(_options: Options) -> Result<()> { if let Ok(package_json) = read_to_string(app_dir.join("package.json")) { let (framework, bundler) = infer_framework(&package_json); if let Some(framework) = framework { - InfoBlock::new(" framework") - .value(framework.to_string()) - .display(); + InfoBlock::new("framework", framework.to_string()).display(); } if let Some(bundler) = bundler { - InfoBlock::new(" bundler") - .value(bundler.to_string()) - .display(); + InfoBlock::new("bundler", bundler.to_string()).display(); } } else { println!("package.json not found"); @@ -762,6 +797,27 @@ pub fn command(_options: Options) -> Result<()> { } } + if let Some(app_dir) = app_dir { + Section("App directory structure").display(); + let dirs = read_dir(app_dir)? + .filter(|p| p.is_ok() && p.as_ref().unwrap().path().is_dir()) + .collect::>>(); + let dirs_len = dirs.len(); + for (i, entry) in dirs.into_iter().enumerate() { + let entry = entry?; + let prefix = if i + 1 == dirs_len { + "└─".cyan() + } else { + "├─".cyan() + }; + println!( + " {} {}", + prefix, + entry.path().file_name().unwrap().to_string_lossy() + ); + } + } + Ok(()) } diff --git a/tooling/cli/src/init.rs b/tooling/cli/src/init.rs index 1063c554e..231f2ae36 100644 --- a/tooling/cli/src/init.rs +++ b/tooling/cli/src/init.rs @@ -194,17 +194,19 @@ pub fn command(mut options: Options) -> Result<()> { fn request_input(prompt: &str, default: Option, skip: bool) -> Result> where - T: Clone + FromStr + Display, + T: Clone + FromStr + Display + ToString, T::Err: Display + std::fmt::Debug, { if skip { Ok(default) } else { - let mut builder = Input::new(); + let theme = dialoguer::theme::ColorfulTheme::default(); + let mut builder = Input::with_theme(&theme); builder.with_prompt(prompt); if let Some(v) = default { - builder.default(v); + builder.default(v.clone()); + builder.with_initial_text(v.to_string()); } builder.interact_text().map(Some).map_err(Into::into)