cli: add version mismatch warning to ls; wire ErrorSeverity in status

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I170f944127333c552e8a230972ed89d66a6a6964
This commit is contained in:
raf 2026-02-12 23:21:16 +03:00
commit 4079485179
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 90 additions and 7 deletions

View file

@ -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}");
}
}

View file

@ -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<String> {
_ => 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(),
}
}