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