pub fn run(patch: bool) -> anyhow::Result<()> {
let current = env!("CARGO_PKG_VERSION");
println!();
println!("rok upgrade — guided version migration");
println!("{}", "─".repeat(50));
println!(" current rok-cli: v{current}");
let versions = collect_rok_versions();
if versions.is_empty() {
println!(" No rok-* crates found in Cargo.toml.");
} else {
println!();
println!(" Workspace rok-* dependencies:");
for (name, ver) in &versions {
println!(" {name:<35} {ver}");
}
}
println!();
if patch {
println!(" --patch mode: updating rok-* crates to latest patch versions...");
match std::process::Command::new("cargo")
.args(["update", "--precise", current])
.output()
{
Ok(o) if o.status.success() => println!(" ✓ Cargo.lock updated"),
Ok(o) => {
let err = String::from_utf8_lossy(&o.stderr);
println!(" ✗ cargo update failed: {}", err.lines().next().unwrap_or("unknown"));
}
Err(e) => println!(" ✗ cargo not found: {e}"),
}
} else {
println!(" Tips:");
println!(" - Run `rok upgrade --patch` to auto-update Cargo.lock");
println!(" - Run `rok self-update` to update rok-cli itself");
println!(" - Review CHANGELOG.md for breaking changes before bumping major");
}
println!();
Ok(())
}
fn collect_rok_versions() -> Vec<(String, String)> {
let Ok(content) = std::fs::read_to_string("Cargo.toml") else { return Vec::new() };
let mut results = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("rok-") || trimmed.contains("rok-") {
if let Some(eq_pos) = trimmed.find('=') {
let name = trimmed[..eq_pos].trim().trim_matches('"').to_string();
let rest = &trimmed[eq_pos + 1..];
if name.starts_with("rok-") {
let ver = extract_version(rest);
results.push((name, ver));
}
}
}
}
results.dedup_by(|a, b| a.0 == b.0);
results
}
fn extract_version(val: &str) -> String {
let val = val.trim().trim_matches('"');
if val.starts_with('{') {
for part in val.split(',') {
let p = part.trim().trim_matches('{').trim_matches('}').trim();
if p.starts_with("version") {
if let Some(v) = p.split('=').nth(1) {
return v.trim().trim_matches('"').to_string();
}
}
}
"workspace".into()
} else {
val.to_string()
}
}