diff --git a/README.md b/README.md index 1ba187b..0817c16 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ - [x] Autocomplete - [x] Colored updatable packages based on semver diff - [x] CLI utility flags +- [x] Check global packages ## Roadmap - [ ] Monorepo support ⚠️ -- [ ] Check global packages ⚠️ - [ ] Single packages update with filters ⚠️ - [ ] Non-interactive mode with different display formatting and infos (publish time, semver grouping ) ⚠️ @@ -43,6 +43,7 @@ pushapp | Option | Description | |-------------------------------------|--------------------------------------| +| `-g`, `--global` | Check global packages | | `-d`, `--development` | Check only `devDependencies` | | `-p`, `--production` | Check only `dependencies` | | `-o`, `--optional` | Check only `optionalDependencies` | diff --git a/src/cli/args.rs b/src/cli/args.rs index 77634dc..1ecbf80 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -1,7 +1,8 @@ use clap::Parser; -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Default)] #[command(version, about, long_about = None)] +#[allow(clippy::struct_excessive_bools)] pub struct Args { /// Check only "devDependencies" #[clap(short, long)] @@ -12,4 +13,7 @@ pub struct Args { /// Check only "optionalDependencies" #[clap(short, long)] pub optional: bool, + /// Check global packages + #[clap(short, long)] + pub global: bool, } diff --git a/src/cli/package_json.rs b/src/cli/package_json.rs index dac1958..5696afd 100644 --- a/src/cli/package_json.rs +++ b/src/cli/package_json.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::env; use std::path::{Path, PathBuf}; -use tokio::process::Command; +use std::process::Command; use super::{ args::Args, @@ -25,6 +25,16 @@ pub struct PackageJson { pub package_manager: Option, } +#[derive(Deserialize, Debug)] +pub struct GlobalPackage { + pub version: String, +} + +#[derive(Deserialize, Debug)] +pub struct GlobalList { + pub dependencies: HashMap, +} + #[derive(Debug, Default)] #[allow(clippy::module_name_repetitions)] pub struct PackageJsonManager { @@ -63,69 +73,89 @@ impl PackageJsonManager { } } - pub fn all_deps_iter(&self, args: &Args) -> impl Iterator { - // Build a vector of the selected dependencies based on CLI arguments - let mut selected_deps: Vec<&Option> = Vec::new(); + pub fn get_local_deps(&self, args: &Args) -> PackageDependencies { + let mut combined_deps = PackageDependencies::new(); + // Apply logic based on the provided flags if args.production || (!args.development && !args.optional) { - selected_deps.push(&self.json.dependencies); + if let Some(dependencies) = &self.json.dependencies { + combined_deps.extend(dependencies.clone()); + } } if args.development || (!args.production && !args.optional) { - selected_deps.push(&self.json.dev_dependencies); + if let Some(dev_dependencies) = &self.json.dev_dependencies { + combined_deps.extend(dev_dependencies.clone()); + } } if args.optional || (!args.production && !args.development) { - selected_deps.push(&self.json.optional_dependencies); + if let Some(optional_dependencies) = &self.json.optional_dependencies { + combined_deps.extend(optional_dependencies.clone()); + } } - selected_deps - .into_iter() - .flat_map(|deps_option| deps_option.iter().flat_map(|deps| deps.iter())) + combined_deps + } + + pub fn get_global_deps() -> Result { + // Run the `npm list -g --depth=0` command to get the global packages and their versions + let output = Command::new("npm") + .arg("ls") + .arg("--json") + .arg("-g") + .arg("--depth=0") + .output()?; + + let output_str = String::from_utf8(output.stdout)?; + + let global_list: GlobalList = serde_json::from_str(&output_str)?; + + let packages = global_list + .dependencies + .iter() + .map(|(name, package)| (name.clone(), package.version.clone())) + .collect(); + + Ok(packages) } /// Detect the package manager used in the project and return it with the install command. - fn detect_package_manager(&self) -> (String, String) { - let package_manager = self.json.package_manager.as_deref().unwrap_or("npm"); + fn detect_package_manager(&self, args: &Args) -> String { + if args.global { + return "npm".to_string(); + } - // Split at '@' and get the package manager name - let package_manager_name = package_manager.split('@').next().unwrap_or("npm"); + let package_manager_field = self.json.package_manager.as_deref().unwrap_or("npm"); - // Determine the command based on the package manager - let command = match package_manager_name { - "npm" => "install", - _ => "add", - }; + // Split at '@' and get the package manager name + let package_manager = package_manager_field.split('@').next().unwrap_or("npm"); - (package_manager_name.to_string(), command.to_string()) + package_manager.to_string() } - pub async fn install_deps(&self, updates: Vec) -> Result<()> { - let (package_manager, command) = self.detect_package_manager(); + pub fn install_deps(&self, updates: &[PackageInfo], args: &Args) -> Result<()> { + let package_manager = self.detect_package_manager(args); let install_args = updates .iter() .map(|package| format!("{}@{}", package.pkg_name, package.latest_version)) .collect::>(); - #[cfg(not(debug_assertions))] - let status = Command::new(package_manager) - .arg(command) - .args(install_args) - .status() - .await?; - - #[cfg(debug_assertions)] - let status = Command::new("echo") - .arg(format!( - "Would have run: {} {} {}", - package_manager, - command, - install_args.join(" ") - )) - .status() - .await?; + // Determine the command based on the package manager + let command = match package_manager.as_str() { + "npm" => "install", + _ => "add", + }; + + let mut cmd = Command::new(package_manager); + cmd.arg(command).args(install_args); + + if args.global { + cmd.arg("-g"); + } + let status = cmd.status()?; if status.success() { println!("{}", "Packages successfully updated!".bright_green()); } else { @@ -172,9 +202,8 @@ mod tests { ..Default::default() }; - assert_eq!( - manager.detect_package_manager(), - ("pnpm".to_owned(), "add".to_owned()) - ); + let args = Args::default(); + + assert_eq!(manager.detect_package_manager(&args), "pnpm"); } } diff --git a/src/cli/updater.rs b/src/cli/updater.rs index 29f1065..43c10a8 100644 --- a/src/cli/updater.rs +++ b/src/cli/updater.rs @@ -9,7 +9,7 @@ use tokio::task::{self, JoinHandle}; use super::{ args::Args, package_info::{normalize_version, PackageInfo}, - package_json::PackageJsonManager, + package_json::{PackageDependencies, PackageJsonManager}, prompt::display_update, registry::RegistryClient, }; @@ -33,7 +33,13 @@ impl UpdateChecker { pub async fn run(&self) -> Result<()> { println!("🔍 {}", "Checking updates...".bright_yellow()); - let tasks = self.fetch_updates(); + let deps = if self.args.global { + self::PackageJsonManager::get_global_deps()? + } else { + self.pkg_manager.get_local_deps(&self.args) + }; + + let tasks = self.fetch_updates(&deps); if tasks.is_empty() { println!("{}", "📦 No dependencies found.".bright_red()); return Ok(()); @@ -45,13 +51,15 @@ impl UpdateChecker { ); let updatable_packages = self.process_update_stream(tasks).await; - self.handle_updatable_packages(updatable_packages).await + self.handle_updatable_packages(updatable_packages) } - fn fetch_updates(&self) -> FuturesUnordered>> { - self - .pkg_manager - .all_deps_iter(&self.args) + fn fetch_updates( + &self, + deps: &PackageDependencies, + ) -> FuturesUnordered>> { + deps + .iter() .map(|(name, version)| { let client = Arc::clone(&self.client); let name = name.to_string(); @@ -93,10 +101,7 @@ impl UpdateChecker { pkg_infos } - async fn handle_updatable_packages( - &self, - mut updatable_packages: Vec, - ) -> Result<()> { + fn handle_updatable_packages(&self, mut updatable_packages: Vec) -> Result<()> { if updatable_packages.is_empty() { println!("{}", "There are no updates available.".bright_blue()); return Ok(()); @@ -106,7 +111,7 @@ impl UpdateChecker { match display_update(updatable_packages) { Some(selected) => { - self.pkg_manager.install_deps(selected).await?; + self.pkg_manager.install_deps(&selected, &self.args)?; } None => { println!("{}", "\nNo packages were updated.".bright_yellow()); diff --git a/src/main.rs b/src/main.rs index ac822fa..aac81ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,10 @@ async fn main() -> Result<()> { let args = Args::parse(); let mut pkg_manager = PackageJsonManager::new(); - pkg_manager.locate_closest()?; - pkg_manager.read()?; + if !args.global { + pkg_manager.locate_closest()?; + pkg_manager.read()?; + } let update_checker = UpdateChecker::new(args, pkg_manager); update_checker.run().await?;