mars_agents/cli/
outdated.rs1use serde::Serialize;
4
5use crate::error::MarsError;
6
7use super::output;
8
9#[derive(Debug, clap::Args)]
11pub struct OutdatedArgs {}
12
13#[derive(Debug, Serialize)]
15struct OutdatedEntry {
16 source: String,
17 locked: String,
18 constraint: String,
19 updateable: String,
20 latest: String,
21}
22
23pub fn run(_args: &OutdatedArgs, ctx: &super::MarsContext, json: bool) -> Result<i32, MarsError> {
25 let lock = crate::lock::load(&ctx.managed_root)?;
26 let config = crate::config::load(&ctx.managed_root)?;
27 let cache = crate::source::GlobalCache::new()?;
28
29 let mut entries = Vec::new();
30
31 for (name, source_entry) in &config.sources {
32 let url = match &source_entry.url {
34 Some(u) => u,
35 None => continue, };
37
38 let locked_version = lock
39 .sources
40 .get(name)
41 .and_then(|s| s.version.clone())
42 .unwrap_or_else(|| "-".to_string());
43
44 let constraint = source_entry
45 .version
46 .clone()
47 .unwrap_or_else(|| "latest".to_string());
48
49 let versions = match crate::source::list_versions(url, &cache) {
51 Ok(v) => v,
52 Err(_) => continue,
53 };
54
55 if versions.is_empty() {
56 let current_head = crate::source::git::ls_remote_head(url.as_ref())
58 .map(|sha| {
59 if sha.len() >= 12 {
60 sha[..12].to_string()
61 } else {
62 sha
63 }
64 })
65 .unwrap_or_else(|_| "-".to_string());
66 let locked_commit = lock
67 .sources
68 .get(name)
69 .and_then(|s| s.commit.as_ref().map(|c| c.to_string()))
70 .unwrap_or_else(|| "-".to_string());
71 let locked_short = if locked_commit.len() >= 12 {
72 locked_commit[..12].to_string()
73 } else {
74 locked_commit
75 };
76 entries.push(OutdatedEntry {
77 source: name.to_string(),
78 locked: locked_short,
79 constraint: "HEAD".to_string(),
80 updateable: current_head.clone(),
81 latest: current_head,
82 });
83 continue;
84 }
85
86 let latest = versions
88 .iter()
89 .max_by(|a, b| a.version.cmp(&b.version))
90 .map(|v| v.tag.clone())
91 .unwrap_or_else(|| "-".to_string());
92
93 let parsed_constraint =
95 crate::resolve::parse_version_constraint(source_entry.version.as_deref());
96 let updateable = match &parsed_constraint {
97 crate::resolve::VersionConstraint::Semver(req) => versions
98 .iter()
99 .filter(|v| req.matches(&v.version))
100 .max_by(|a, b| a.version.cmp(&b.version))
101 .map(|v| v.tag.clone())
102 .unwrap_or_else(|| locked_version.clone()),
103 crate::resolve::VersionConstraint::Latest => latest.clone(),
104 crate::resolve::VersionConstraint::RefPin(_) => locked_version.clone(),
105 };
106
107 entries.push(OutdatedEntry {
108 source: name.to_string(),
109 locked: locked_version,
110 constraint,
111 updateable,
112 latest,
113 });
114 }
115
116 if json {
117 output::print_json(&entries);
118 } else {
119 print_outdated_table(&entries);
120 }
121
122 Ok(0)
123}
124
125fn print_outdated_table(entries: &[OutdatedEntry]) {
126 if entries.is_empty() {
127 output::print_info("no git sources to check");
128 return;
129 }
130
131 let name_w = entries
132 .iter()
133 .map(|e| e.source.len())
134 .max()
135 .unwrap_or(6)
136 .max(6);
137 let locked_w = entries
138 .iter()
139 .map(|e| e.locked.len())
140 .max()
141 .unwrap_or(6)
142 .max(6);
143 let constraint_w = entries
144 .iter()
145 .map(|e| e.constraint.len())
146 .max()
147 .unwrap_or(10)
148 .max(10);
149 let update_w = entries
150 .iter()
151 .map(|e| e.updateable.len())
152 .max()
153 .unwrap_or(10)
154 .max(10);
155
156 println!(
157 "{:<name_w$} {:<locked_w$} {:<constraint_w$} {:<update_w$} LATEST",
158 "SOURCE", "LOCKED", "CONSTRAINT", "UPDATEABLE"
159 );
160
161 for entry in entries {
162 println!(
163 "{:<name_w$} {:<locked_w$} {:<constraint_w$} {:<update_w$} {}",
164 entry.source, entry.locked, entry.constraint, entry.updateable, entry.latest
165 );
166 }
167}