1use check_updates::CheckUpdates;
2use console::Style;
3use indicatif::{ProgressBar, ProgressStyle};
4
5pub mod cli;
6mod interactive;
7mod update;
8mod version;
9
10pub fn run(args: cli::Args) {
11 env_logger::Builder::new()
12 .filter_level(if args.verbose {
13 log::LevelFilter::Debug
14 } else {
15 log::LevelFilter::Info
16 })
17 .init();
18
19 let strategy = version::VersionStrategy::from_args(&args);
20
21 let spinner = ProgressBar::new_spinner().with_style(
22 ProgressStyle::default_spinner()
23 .template("{spinner:.cyan} {msg}")
24 .expect("valid template"),
25 );
26 spinner.set_message("Fetching package data...");
27 spinner.enable_steady_tick(std::time::Duration::from_millis(80));
28
29 let check_updates = CheckUpdates::new(args.root.clone());
30 let packages = match check_updates.packages() {
31 Ok(p) => p,
32 Err(e) => {
33 spinner.finish_and_clear();
34 eprintln!("{} {e}", Style::new().red().bold().apply_to("error:"));
35 std::process::exit(1);
36 }
37 };
38
39 spinner.finish_and_clear();
40
41 if !args.package.is_empty() {
42 let known: std::collections::HashSet<&str> = packages
43 .values()
44 .flatten()
45 .map(|(_, _, pkg)| pkg.purl.name())
46 .collect();
47
48 for name in &args.package {
49 if !known.contains(name.as_str()) {
50 eprintln!(
51 "{} package '{}' not found in dependencies",
52 Style::new().red().bold().apply_to("error:"),
53 name
54 );
55 std::process::exit(1);
56 }
57 }
58 }
59
60 let updates = update::resolve_updates(&packages, &strategy, &args.package);
61
62 if args.interactive {
63 if updates.is_empty() {
64 update::print_summary(&updates);
65 return;
66 }
67
68 let selected = match interactive::prompt_updates(&updates, args.compact) {
69 Ok(s) => s,
70 Err(e) => {
71 eprintln!("{} {e}", Style::new().red().bold().apply_to("error:"));
72 std::process::exit(1);
73 }
74 };
75
76 if selected.is_empty() {
77 println!("\n No packages selected.");
78 return;
79 }
80
81 let count = selected.len();
82 if let Err(e) =
83 check_updates.update_versions(selected.iter().map(|(u, p, r)| (*u, *p, r.clone())))
84 {
85 eprintln!("{} {e}", Style::new().red().bold().apply_to("error:"));
86 std::process::exit(1);
87 }
88
89 if args.update {
90 run_cargo_update();
91 }
92
93 println!(
94 "\n Upgraded {count} {}.",
95 if count == 1 {
96 "dependency"
97 } else {
98 "dependencies"
99 }
100 );
101 } else if args.upgrade || args.update {
102 update::print_summary(&updates);
103
104 if updates.is_empty() {
105 if args.update {
106 run_cargo_update();
107 }
108 return;
109 }
110
111 let count: usize = updates.values().map(|v| v.len()).sum();
112
113 if let Err(e) = check_updates.update_versions(updates.values().flat_map(|unit_updates| {
114 unit_updates
115 .iter()
116 .map(|u| (u.usage, u.package, u.new_req.clone()))
117 })) {
118 eprintln!("{} {e}", Style::new().red().bold().apply_to("error:"));
119 std::process::exit(1);
120 }
121
122 if args.update {
123 run_cargo_update();
124 }
125
126 println!(
127 "\n Upgraded {count} {}.",
128 if count == 1 {
129 "dependency"
130 } else {
131 "dependencies"
132 }
133 );
134 } else {
135 update::print_summary(&updates);
136 if !updates.is_empty() {
137 println!(
138 "\n{}",
139 Style::new().dim().apply_to("Run with -u or -U to upgrade.")
140 );
141 }
142 }
143}
144
145fn run_cargo_update() {
146 let status = std::process::Command::new("cargo")
147 .arg("update")
148 .status()
149 .expect("failed to run cargo update");
150
151 if !status.success() {
152 eprintln!(
153 "{} cargo update failed",
154 Style::new().red().bold().apply_to("error:")
155 );
156 std::process::exit(1);
157 }
158}