From 6ae95bf83b44c0b0c8c670b6b119208e25e6dfa6 Mon Sep 17 00:00:00 2001 From: Sergio <77530549+sergi0g@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:28:18 +0200 Subject: [PATCH] Update API and improve CLI output --- docs/src/content/docs/integrations.mdx | 2 +- src/formatting/mod.rs | 177 +++++++++++++++++++++---- src/structs/image.rs | 5 +- src/structs/update.rs | 6 +- web/src/components/Image.tsx | 2 +- web/src/types.ts | 2 +- 6 files changed, 163 insertions(+), 31 deletions(-) diff --git a/docs/src/content/docs/integrations.mdx b/docs/src/content/docs/integrations.mdx index 3e4ae9e..336408e 100644 --- a/docs/src/content/docs/integrations.mdx +++ b/docs/src/content/docs/integrations.mdx @@ -47,7 +47,7 @@ The data returned from the API or from the CLI is in JSON and looks like this: "remote_digest": "sha256:170f1974d8fc8ca245bcfae5590bc326de347b19719972bf122400fb13dfa42c", // Latest digest available in the registry // If `type` is "version": "version_update_type": "major", // Loosely corresponds to SemVer versioning. Can also be `minor` or `patch`. - "new_version": "v3.3.3", // The tag of the latest image. + "new_tag": "v3.3.3", // The tag of the latest image. }, "error": null, // If checking for the image fails, will be a string with an error message. }, diff --git a/src/formatting/mod.rs b/src/formatting/mod.rs index 2d2479b..07d9ecc 100644 --- a/src/formatting/mod.rs +++ b/src/formatting/mod.rs @@ -1,39 +1,164 @@ pub mod spinner; +use rustc_hash::FxHashMap; + use crate::{ - structs::{status::Status, update::Update}, + structs::{ + status::Status, + update::{Update, UpdateInfo}, + }, utils::{json::to_simple_json, sort_update_vec::sort_update_vec}, }; pub fn print_updates(updates: &[Update], icons: &bool) { - let sorted_images = sort_update_vec(updates); - let term_width: usize = termsize::get() - .unwrap_or(termsize::Size { rows: 24, cols: 80 }) - .cols as usize; - for image in sorted_images { - let has_update = image.get_status(); - let description = has_update.to_string(); - let icon = if *icons { - match has_update { - Status::UpToDate => "\u{f058} ", - Status::Unknown(_) => "\u{f059} ", - _ => "\u{f0aa} ", + let sorted_updates = sort_update_vec(updates); + let updates_by_server = { + let mut servers: FxHashMap<&str, Vec<&Update>> = FxHashMap::default(); + sorted_updates.iter().for_each(|update| { + let key = update.server.as_deref().unwrap_or(""); + match servers.get_mut(&key) { + Some(server) => server.push(update), + None => { + let _ = servers.insert(key, vec![update]); + } } + }); + servers + }; + for (server, updates) in updates_by_server { + if server.is_empty() { + println!("\x1b[90;1m~ Local images\x1b[0m") } else { - "" - }; - let color = match has_update { - Status::UpdateAvailable | Status::UpdatePatch => "\u{001b}[38;5;12m", - Status::UpdateMinor => "\u{001b}[38;5;3m", - Status::UpdateMajor => "\u{001b}[38;5;1m", - Status::UpToDate => "\u{001b}[38;5;2m", - Status::Unknown(_) => "\u{001b}[38;5;8m", - }; - let dynamic_space = - " ".repeat(term_width - description.len() - icon.len() - image.reference.len()); + println!("\x1b[90;1m~ {}\x1b[0m", server) + } + let (reference_width, status_width, time_width) = + updates.iter().fold((9, 6, 9), |acc, update| { + let reference_length = update.reference.len(); + let status_length = update.get_status().to_string().len() + + match &update.result.info { + UpdateInfo::Version(info) => { + info.current_version.len() + info.new_version.len() + 6 + } + _ => 0, + }; + let time_length = update.time.to_string().len(); + return ( + if reference_length > acc.0 { + reference_length + } else { + acc.0 + }, + if status_length > acc.1 { + status_length + } else { + acc.1 + }, + if time_length > acc.2 { + time_length + } else { + acc.2 + }, + ); + }); println!( - "{}{}{}{}{}\u{001b}[0m", - color, icon, image.reference, dynamic_space, description + " \x1b[90;1m╭{:─ "\u{f058} ", + Status::Unknown(_) => "\u{f059} ", + _ => "\u{f0aa} ", + } + } else { + "" + }; + let color = match status { + Status::UpdateAvailable | Status::UpdatePatch => "\x1b[34m", + Status::UpdateMinor => "\x1b[33m", + Status::UpdateMajor => "\x1b[31m", + Status::UpToDate => "\x1b[32m", + Status::Unknown(_) => "\x1b[90m", + }; + let description = format!( + "{} {}", + status.to_string(), + match &update.result.info { + UpdateInfo::Version(info) => { + format!("({} → {})", info.current_version, info.new_version) + } + _ => String::new(), + } + ); + println!( + " \x1b[90;1m│\x1b[0m{: unreachable!(), } .to_string(), - new_version: format_str + new_tag: format_str .replacen("{}", &new_tag.major.to_string(), 1) .replacen("{}", &new_tag.minor.unwrap_or(0).to_string(), 1) .replacen("{}", &new_tag.patch.unwrap_or(0).to_string(), 1), + // Throwing these in, because they're useful for the CLI output, however we won't (de)serialize them + current_version: self.version_info.as_ref().unwrap().current_tag.to_string(), + new_version: self.version_info.as_ref().unwrap().latest_remote_tag.as_ref().unwrap().to_string() }) } "digest" => { diff --git a/src/structs/update.rs b/src/structs/update.rs index 25661fd..eb46076 100644 --- a/src/structs/update.rs +++ b/src/structs/update.rs @@ -36,6 +36,10 @@ pub enum UpdateInfo { #[cfg_attr(test, derive(PartialEq, Debug))] pub struct VersionUpdateInfo { pub version_update_type: String, + pub new_tag: String, + #[serde(skip_serializing, skip_deserializing)] + pub current_version: String, + #[serde(skip_serializing, skip_deserializing)] pub new_version: String, } @@ -54,7 +58,7 @@ impl Serialize for VersionUpdateInfo { let mut state = serializer.serialize_struct("VersionUpdateInfo", 3)?; let _ = state.serialize_field("type", "version"); let _ = state.serialize_field("version_update_type", &self.version_update_type); - let _ = state.serialize_field("new_version", &self.new_version); + let _ = state.serialize_field("new_version", &self.new_tag); state.end() } } diff --git a/web/src/components/Image.tsx b/web/src/components/Image.tsx index 69406e4..c1e6a14 100644 --- a/web/src/components/Image.tsx +++ b/web/src/components/Image.tsx @@ -36,7 +36,7 @@ export default function Image({ data }: { data: Image }) { }; const new_reference = data.result.info?.type == "version" - ? data.reference.split(":")[0] + ":" + data.result.info.new_version + ? data.reference.split(":")[0] + ":" + data.result.info.new_tag : data.reference; let url: string | null = null; if (clickable_registries.includes(data.parts.registry)) { diff --git a/web/src/types.ts b/web/src/types.ts index f381e1e..393c502 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -32,7 +32,7 @@ export interface Image { interface VersionInfo { type: "version"; version_update_type: "major" | "minor" | "patch"; - new_version: string; + new_tag: string; } interface DigestInfo {