diff --git a/.changes/cli-info-plugins.md b/.changes/cli-info-plugins.md new file mode 100644 index 000000000000..df3dd042fd9e --- /dev/null +++ b/.changes/cli-info-plugins.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": "patch:feat" +"@tauri-apps/cli": "patch:feat" +--- + +Add plugins information in `tauri info` output diff --git a/tooling/cli/node/src/lib.rs b/tooling/cli/node/src/lib.rs index 6cb03ce18225..a6eece5235f6 100644 --- a/tooling/cli/node/src/lib.rs +++ b/tooling/cli/node/src/lib.rs @@ -19,7 +19,7 @@ pub fn run(args: Vec, bin_name: Option, callback: JsFunction) -> tauri_cli::try_run(args, bin_name) })) { Ok(t) => t, - Err(e) => { + Err(_) => { return function.call( Err(Error::new( Status::GenericFailure, diff --git a/tooling/cli/src/helpers/cargo_manifest.rs b/tooling/cli/src/helpers/cargo_manifest.rs index 4633dac49eb5..c8269a0d6894 100644 --- a/tooling/cli/src/helpers/cargo_manifest.rs +++ b/tooling/cli/src/helpers/cargo_manifest.rs @@ -6,8 +6,7 @@ use serde::Deserialize; use std::{ collections::HashMap, - fmt::Write, - fs::read_to_string, + fs, path::{Path, PathBuf}, }; @@ -50,9 +49,63 @@ pub struct CargoManifest { pub dependencies: HashMap, } +#[derive(Default)] pub struct CrateVersion { - pub version: String, - pub found_crate_versions: Vec, + pub version: Option, + pub git: Option, + pub git_branch: Option, + pub git_rev: Option, + pub path: Option, + pub lock_version: Option, +} + +impl CrateVersion { + pub fn has_version(&self) -> bool { + self.version.is_some() || self.git.is_some() || self.path.is_some() + } +} + +impl std::fmt::Display for CrateVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(g) = &self.git { + if let Some(version) = &self.version { + write!(f, "{g} ({version})")?; + } else { + write!(f, "git:{g}")?; + if let Some(branch) = &self.git_branch { + write!(f, "&branch={branch}")?; + } else if let Some(rev) = &self.git_rev { + write!(f, "#rev={rev}")?; + } + } + } else if let Some(p) = &self.path { + write!(f, "path:{}", p.display())?; + if let Some(version) = &self.version { + write!(f, " ({version})")?; + } + } else if let Some(version) = &self.version { + write!(f, "{version}")?; + } else { + return write!(f, "No version detected"); + } + + if let Some(lock_version) = &self.lock_version { + write!(f, " ({lock_version})")?; + } + + Ok(()) + } +} + +pub fn crate_latest_version(name: &str) -> Option { + let url = format!("https://docs.rs/crate/{name}/"); + match ureq::get(&url).call() { + Ok(response) => match (response.status(), response.header("location")) { + (302, Some(location)) => Some(location.replace(&url, "")), + _ => None, + }, + Err(_) => None, + } } pub fn crate_version( @@ -61,6 +114,8 @@ pub fn crate_version( lock: Option<&CargoLock>, name: &str, ) -> CrateVersion { + let mut version = CrateVersion::default(); + let crate_lock_packages: Vec = lock .as_ref() .map(|lock| { @@ -72,101 +127,54 @@ pub fn crate_version( .collect() }) .unwrap_or_default(); - let (crate_version_string, found_crate_versions) = - match (&manifest, &lock, crate_lock_packages.len()) { - (Some(_manifest), Some(_lock), 1) => { - let crate_lock_package = crate_lock_packages.first().unwrap(); - let version_string = if let Some(s) = &crate_lock_package.source { - if s.starts_with("git") { - format!("{} ({})", s, crate_lock_package.version) - } else { - crate_lock_package.version.clone() - } - } else { - crate_lock_package.version.clone() - }; - (version_string, vec![crate_lock_package.version.clone()]) - } - (None, Some(_lock), 1) => { - let crate_lock_package = crate_lock_packages.first().unwrap(); - let version_string = if let Some(s) = &crate_lock_package.source { - if s.starts_with("git") { - format!("{} ({})", s, crate_lock_package.version) - } else { - crate_lock_package.version.clone() + + if crate_lock_packages.len() == 1 { + let crate_lock_package = crate_lock_packages.first().unwrap(); + if let Some(s) = crate_lock_package + .source + .as_ref() + .filter(|s| s.starts_with("git")) + { + version.git = Some(s.clone()); + } + + version.version = Some(crate_lock_package.version.clone()); + } else { + if let Some(dep) = manifest.and_then(|m| m.dependencies.get(name).cloned()) { + match dep { + CargoManifestDependency::Version(v) => version.version = Some(v), + CargoManifestDependency::Package(p) => { + if let Some(v) = p.version { + version.version = Some(v); + } else if let Some(p) = p.path { + let manifest_path = tauri_dir.join(&p).join("Cargo.toml"); + let v = fs::read_to_string(manifest_path) + .ok() + .and_then(|m| toml::from_str::(&m).ok()) + .map(|m| m.package.version); + version.version = v; + version.path = Some(p); + } else if let Some(g) = p.git { + version.git = Some(g); + version.git_branch = p.branch; + version.git_rev = p.rev; } - } else { - crate_lock_package.version.clone() - }; - ( - format!("{version_string} (no manifest)"), - vec![crate_lock_package.version.clone()], - ) - } - _ => { - let mut found_crate_versions = Vec::new(); - let mut is_git = false; - let manifest_version = match manifest.and_then(|m| m.dependencies.get(name).cloned()) { - Some(tauri) => match tauri { - CargoManifestDependency::Version(v) => { - found_crate_versions.push(v.clone()); - v - } - CargoManifestDependency::Package(p) => { - if let Some(v) = p.version { - found_crate_versions.push(v.clone()); - v - } else if let Some(p) = p.path { - let manifest_path = tauri_dir.join(&p).join("Cargo.toml"); - let v = match read_to_string(manifest_path) - .map_err(|_| ()) - .and_then(|m| toml::from_str::(&m).map_err(|_| ())) - { - Ok(manifest) => manifest.package.version, - Err(_) => "unknown version".to_string(), - }; - format!("path:{p:?} [{v}]") - } else if let Some(g) = p.git { - is_git = true; - let mut v = format!("git:{g}"); - if let Some(branch) = p.branch { - let _ = write!(v, "&branch={branch}"); - } else if let Some(rev) = p.rev { - let _ = write!(v, "#{rev}"); - } - v - } else { - "unknown manifest".to_string() - } - } - }, - None => "no manifest".to_string(), - }; - - let lock_version = match (lock, crate_lock_packages.is_empty()) { - (Some(_lock), false) => crate_lock_packages - .iter() - .map(|p| p.version.clone()) - .collect::>() - .join(", "), - (Some(_lock), true) => "unknown lockfile".to_string(), - _ => "no lockfile".to_string(), - }; - - ( - format!( - "{} {}({})", - manifest_version, - if is_git { "(git manifest)" } else { "" }, - lock_version - ), - found_crate_versions, - ) + } } - }; + } + + if lock.is_some() && crate_lock_packages.is_empty() { + let lock_version = crate_lock_packages + .iter() + .map(|p| p.version.clone()) + .collect::>() + .join(", "); - CrateVersion { - found_crate_versions, - version: crate_version_string, + if !lock_version.is_empty() { + version.lock_version = Some(lock_version); + } + } } + + version } diff --git a/tooling/cli/src/info/mod.rs b/tooling/cli/src/info/mod.rs index 983fac9d8c6d..cfa5b46b71db 100644 --- a/tooling/cli/src/info/mod.rs +++ b/tooling/cli/src/info/mod.rs @@ -17,6 +17,7 @@ mod env_system; mod ios; mod packages_nodejs; mod packages_rust; +mod plugins; #[derive(Deserialize)] struct JsCliVersionMetadata { @@ -265,6 +266,11 @@ pub fn command(options: Options) -> Result<()> { crate::helpers::app_paths::resolve(); } + let package_manager = app_dir + .as_ref() + .map(packages_nodejs::package_manager) + .unwrap_or(crate::helpers::npm::PackageManager::Npm); + let metadata = version_metadata()?; let mut environment = Section { @@ -285,9 +291,17 @@ pub fn command(options: Options) -> Result<()> { packages .items .extend(packages_rust::items(app_dir.as_ref(), tauri_dir.as_deref())); - packages - .items - .extend(packages_nodejs::items(app_dir.as_ref(), &metadata)); + packages.items.extend(packages_nodejs::items( + app_dir.as_ref(), + package_manager, + &metadata, + )); + + let mut plugins = Section { + label: "Plugins", + interactive, + items: plugins::items(app_dir.as_ref(), tauri_dir.as_deref(), package_manager), + }; let mut app = Section { label: "App", @@ -300,6 +314,7 @@ pub fn command(options: Options) -> Result<()> { environment.display(); packages.display(); + plugins.display(); app.display(); // iOS diff --git a/tooling/cli/src/info/packages_nodejs.rs b/tooling/cli/src/info/packages_nodejs.rs index 67dd83cba87d..73d5cf61adc7 100644 --- a/tooling/cli/src/info/packages_nodejs.rs +++ b/tooling/cli/src/info/packages_nodejs.rs @@ -15,7 +15,7 @@ struct YarnVersionInfo { data: Vec, } -fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result> { +pub fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result> { match pm { PackageManager::Yarn => { let mut cmd = cross_command("yarn"); @@ -87,21 +87,22 @@ fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result>(app_dir_entries: &[T]) -> PackageManager { +pub fn package_manager(app_dir: &PathBuf) -> PackageManager { let mut use_npm = false; let mut use_pnpm = false; let mut use_yarn = false; let mut use_bun = false; - for name in app_dir_entries { - if name.as_ref() == "package-lock.json" { - use_npm = true; - } else if name.as_ref() == "pnpm-lock.yaml" { - use_pnpm = true; - } else if name.as_ref() == "yarn.lock" { - use_yarn = true; - } else if name.as_ref() == "bun.lockb" { - use_bun = true; + for entry in std::fs::read_dir(app_dir) + .unwrap() + .map(|e| e.unwrap().file_name().to_string_lossy().into_owned()) + { + match entry.as_str() { + "pnpm-lock.yaml" => use_pnpm = true, + "package-lock.json" => use_npm = true, + "yarn.lock" => use_yarn = true, + "bun.lockb" => use_bun = true, + _ => {} } } @@ -131,11 +132,11 @@ fn get_package_manager>(app_dir_entries: &[T]) -> PackageManager { if found.len() > 1 { let pkg_manger = found[0]; println!( - "{}: Only one package manager should be used, but found {}.\n Please remove unused package manager lock files, will use {} for now!", - "WARNING".yellow(), - found.iter().map(ToString::to_string).collect::>().join(" and "), - pkg_manger - ); + "{}: Only one package manager should be used, but found {}.\n Please remove unused package manager lock files, will use {} for now!", + "WARNING".yellow(), + found.iter().map(ToString::to_string).collect::>().join(" and "), + pkg_manger + ); return pkg_manger; } @@ -145,29 +146,21 @@ fn get_package_manager>(app_dir_entries: &[T]) -> PackageManager { PackageManager::Pnpm } else if use_bun { PackageManager::Bun + } else if manager_version("yarn") + .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default()) + .unwrap_or(false) + { + PackageManager::YarnBerry } else { PackageManager::Yarn } } -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) - .unwrap() - .map(|e| e.unwrap().file_name().to_string_lossy().into_owned()) - .collect::>(); - package_manager = get_package_manager(&app_dir_entries); - } - - if package_manager == PackageManager::Yarn - && manager_version("yarn") - .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default()) - .unwrap_or(false) - { - package_manager = PackageManager::YarnBerry; - } - +pub fn items( + app_dir: Option<&PathBuf>, + package_manager: PackageManager, + metadata: &VersionMetadata, +) -> Vec { let mut items = Vec::new(); if let Some(app_dir) = app_dir { for (package, version) in [ @@ -175,45 +168,54 @@ pub fn items(app_dir: Option<&PathBuf>, metadata: &VersionMetadata) -> Vec, + app_dir: PathBuf, + package_manager: PackageManager, +) -> SectionItem { + SectionItem::new().action(move || { + let version = version.clone().unwrap_or_else(|| { + package_manager + .current_package_version(&package, &app_dir) + .unwrap_or_default() + .unwrap_or_default() + }); + + let latest_ver = super::packages_nodejs::npm_latest_version(&package_manager, &package) + .unwrap_or_default() + .unwrap_or_default(); + + if version.is_empty() { + format!("{} {}: not installed!", package, "".green()) + } else { + format!( + "{} {}: {}{}", + package, + "".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() + } + ) + } + .into() + }) +} diff --git a/tooling/cli/src/info/packages_rust.rs b/tooling/cli/src/info/packages_rust.rs index 89c7592d4802..2778335babdc 100644 --- a/tooling/cli/src/info/packages_rust.rs +++ b/tooling/cli/src/info/packages_rust.rs @@ -4,24 +4,15 @@ use super::{ActionResult, SectionItem}; use crate::{ - helpers::cargo_manifest::{crate_version, CargoLock, CargoManifest}, + helpers::cargo_manifest::{ + crate_latest_version, crate_version, CargoLock, CargoManifest, CrateVersion, + }, interface::rust::get_workspace_dir, }; use colored::Colorize; use std::fs::read_to_string; use std::path::{Path, PathBuf}; -fn crate_latest_version(name: &str) -> Option { - let url = format!("https://docs.rs/crate/{name}/"); - match ureq::get(&url).call() { - Ok(response) => match (response.status(), response.header("location")) { - (302, Some(location)) => Some(location.replace(&url, "")), - _ => None, - }, - Err(_) => None, - } -} - pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec { let mut items = Vec::new(); @@ -39,39 +30,8 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec
{ - 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 - } - } - _ => None, - }; - - let item = SectionItem::new().description(format!( - "{} {}: {}{}", - dep, - "[RUST]".dimmed(), - version.version, - version_suffix - .clone() - .map(|s| format!(",{s}")) - .unwrap_or_else(|| "".into()) - )); + let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), dep); + let item = rust_section_item(dep, crate_version); items.push(item); } } @@ -91,7 +51,7 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec
, tauri_dir: Option<&Path>) -> Vec
SectionItem { + let version = crate_version + .version + .as_ref() + .and_then(|v| semver::Version::parse(v).ok()); + + let version_suffix = match (version, crate_latest_version(dep)) { + (Some(version), Some(target_version)) => { + 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 + } + } + _ => None, + }; + + SectionItem::new().description(format!( + "{} {}: {}{}", + dep, + "🦀", + crate_version, + version_suffix + .clone() + .map(|s| format!(",{s}")) + .unwrap_or_else(|| "".into()) + )) +} diff --git a/tooling/cli/src/info/plugins.rs b/tooling/cli/src/info/plugins.rs new file mode 100644 index 000000000000..d4c1d785c98d --- /dev/null +++ b/tooling/cli/src/info/plugins.rs @@ -0,0 +1,65 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use crate::{ + helpers::{ + self, + cargo_manifest::{crate_version, CargoLock, CargoManifest}, + npm::PackageManager, + }, + interface::rust::get_workspace_dir, +}; + +use super::{packages_nodejs, packages_rust, SectionItem}; + +pub fn items( + app_dir: Option<&PathBuf>, + tauri_dir: Option<&Path>, + package_manager: PackageManager, +) -> Vec { + let mut items = Vec::new(); + + if tauri_dir.is_some() || app_dir.is_some() { + if let Some(tauri_dir) = tauri_dir { + let manifest: Option = + if let Ok(manifest_contents) = fs::read_to_string(tauri_dir.join("Cargo.toml")) { + toml::from_str(&manifest_contents).ok() + } else { + None + }; + + let lock: Option = get_workspace_dir() + .ok() + .and_then(|p| fs::read_to_string(p.join("Cargo.lock")).ok()) + .and_then(|s| toml::from_str(&s).ok()); + + for p in helpers::plugins::known_plugins().keys() { + let dep = format!("tauri-plugin-{p}"); + let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &dep); + if !crate_version.has_version() { + continue; + } + let item = packages_rust::rust_section_item(&dep, crate_version); + items.push(item); + + let Some(app_dir) = app_dir else { + continue; + }; + + let package = format!("@tauri-apps/plugin-{p}"); + + let item = + packages_nodejs::nodejs_section_item(package, None, app_dir.clone(), package_manager); + items.push(item); + } + } + } + + items +} diff --git a/tooling/cli/src/migrate/mod.rs b/tooling/cli/src/migrate/mod.rs index 6c2c11075f81..b5e4ef0ec4d2 100644 --- a/tooling/cli/src/migrate/mod.rs +++ b/tooling/cli/src/migrate/mod.rs @@ -38,7 +38,9 @@ pub fn command() -> Result<()> { None }; - let tauri_version = crate_version(tauri_dir, Some(&manifest), lock.as_ref(), "tauri").version; + let tauri_version = crate_version(tauri_dir, Some(&manifest), lock.as_ref(), "tauri") + .version + .context("failed to get tauri version")?; let tauri_version = semver::Version::from_str(&tauri_version)?; if tauri_version.major == 1 {