diff --git a/src/cli/commands/ls.rs b/src/cli/commands/ls.rs index d225e04..4b9c1c3 100644 --- a/src/cli/commands/ls.rs +++ b/src/cli/commands/ls.rs @@ -2,6 +2,18 @@ use std::path::Path; use crate::{cli::LsArgs, error::Result, model::LockFile}; +/// Truncate a name to fit within `max_len` characters, adding "..." if +/// truncated +fn truncate_name(name: &str, max_len: usize) -> String { + if name.len() <= max_len { + name.to_string() + } else if max_len > 3 { + format!("{}...", &name[..max_len - 3]) + } else { + name[..max_len].to_string() + } +} + pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> { // Load expects directory path, so get parent directory let lockfile_dir = lockfile_path.parent().unwrap_or(Path::new(".")); @@ -15,10 +27,33 @@ pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> { println!("Installed projects ({}):", lockfile.projects.len()); println!(); + // Calculate max name length for alignment + let max_name_len = args.name_max_length.unwrap_or_else(|| { + lockfile + .projects + .iter() + .map(|p| p.get_name().len()) + .max() + .unwrap_or(20) + .min(50) + }); + for project in &lockfile.projects { + // Check for version mismatch across providers + let version_warning = if project.versions_match_across_providers() { + "" + } else { + // Use the detailed check_version_mismatch for logging + if let Some(mismatch_detail) = project.check_version_mismatch() { + log::warn!("{mismatch_detail}"); + } + " [!] versions do not match across providers" + }; + if args.detailed { let id = project.pakku_id.as_deref().unwrap_or("unknown"); - println!(" {} ({})", project.get_name(), id); + let name = truncate_name(&project.get_name(), max_name_len); + println!(" {name} ({id}){version_warning}"); println!(" Type: {:?}", project.r#type); println!(" Side: {:?}", project.side); @@ -30,19 +65,28 @@ pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> { ); } + // Show version details if there's a mismatch + if !version_warning.is_empty() { + println!(" Provider versions:"); + for file in &project.files { + println!(" {}: {}", file.file_type, file.file_name); + } + } + if !project.pakku_links.is_empty() { println!(" Dependencies: {}", project.pakku_links.len()); } println!(); } else { + let name = truncate_name(&project.get_name(), max_name_len); let file_info = project .files .first() .map(|f| format!(" ({})", f.file_name)) .unwrap_or_default(); - println!(" {}{}", project.get_name(), file_info); + println!(" {name}{file_info}{version_warning}"); } } diff --git a/src/cli/commands/status.rs b/src/cli/commands/status.rs index 41f84d7..6b50ed6 100644 --- a/src/cli/commands/status.rs +++ b/src/cli/commands/status.rs @@ -6,7 +6,7 @@ use tokio::sync::Semaphore; use yansi::Paint; use crate::{ - error::Result, + error::{ErrorSeverity, Result}, model::{Config, LockFile, Project}, platform::create_platform, }; @@ -36,13 +36,42 @@ pub async fn execute( // Display results display_update_results(&updates); - // Display errors if any + // Display errors if any, categorized by severity if !errors.is_empty() { println!(); - println!("{}", "Errors encountered:".red()); - for (project, error) in &errors { - println!(" - {}: {}", project.yellow(), error.red()); + + // Categorize errors by severity + let (warnings, errors_only): (Vec<_>, Vec<_>) = + errors.iter().partition(|(_, err)| { + // Network errors and "not found" are warnings (non-fatal) + err.contains("Failed to check") || err.contains("not found") + }); + + // Display warnings (ErrorSeverity::Warning) + if !warnings.is_empty() { + let severity = ErrorSeverity::Warning; + println!("{}", format_severity_header(severity, "Warnings")); + for (project, error) in &warnings { + println!(" - {}: {}", project.yellow(), error.dim()); + } } + + // Display errors (ErrorSeverity::Error) + if !errors_only.is_empty() { + let severity = ErrorSeverity::Error; + println!("{}", format_severity_header(severity, "Errors")); + for (project, error) in &errors_only { + println!(" - {}: {}", project.yellow(), error.red()); + } + } + + // Log info level summary + let _info_severity = ErrorSeverity::Info; + log::info!( + "Update check completed with {} warning(s) and {} error(s)", + warnings.len(), + errors_only.len() + ); } // Prompt to update if there are updates available @@ -52,6 +81,7 @@ pub async fn execute( // Call update command programmatically (update all projects) let update_args = crate::cli::UpdateArgs { inputs: vec![], + all: true, yes: true, // Auto-yes for status command }; crate::cli::commands::update::execute( @@ -368,3 +398,12 @@ fn get_api_key(platform: &str) -> Option { _ => None, } } + +/// Format severity header with appropriate color +fn format_severity_header(severity: ErrorSeverity, label: &str) -> String { + match severity { + ErrorSeverity::Error => format!("{label}:").red().to_string(), + ErrorSeverity::Warning => format!("{label}:").yellow().to_string(), + ErrorSeverity::Info => format!("{label}:").cyan().to_string(), + } +}