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 log::error!("{e}");
53 std::process::exit(1);
54 }
55 };
56
57 spinner.finish_and_clear();
58
59 if !args.package.is_empty() {
60 for name in &args.package {
61 if !packages
62 .keys()
63 .any(|unit| update::unit_matches_filter(unit, name))
64 {
65 log::error!("workspace package '{}' not found", name);
66 std::process::exit(1);
67 }
68 }
69 }
70
71 let updates = update::resolve_updates(&packages, &strategy, &args.package);
72 let has_updates = !updates.is_empty();
73 let fail_on_updates = args.fail_on_updates;
74
75 if args.interactive {
76 if updates.is_empty() {
77 update::print_summary(&updates);
78 if args.upgrade {
79 run_cargo_update();
80 }
81 return;
82 }
83
84 let selected = match interactive::prompt_updates(&updates, args.compact) {
85 Ok(s) => s,
86 Err(e) => {
87 log::error!("{e}");
88 std::process::exit(1);
89 }
90 };
91
92 if selected.is_empty() {
93 if args.upgrade {
94 run_cargo_update();
95 }
96 println!("No packages selected.");
97 return;
98 }
99
100 let count = selected.len();
101 if let Err(e) =
102 check_updates.update_versions(selected.iter().map(|(u, p, r)| (*u, *p, r.clone())))
103 {
104 log::error!("{e}");
105 std::process::exit(1);
106 }
107
108 if args.upgrade {
109 run_cargo_update();
110 }
111
112 println!(
113 "\n Upgraded {count} {}.",
114 if count == 1 {
115 "dependency"
116 } else {
117 "dependencies"
118 }
119 );
120 } else if args.update || args.upgrade {
121 update::print_summary(&updates);
122
123 if updates.is_empty() {
124 if args.upgrade {
125 run_cargo_update();
126 }
127 return;
128 }
129
130 let count: usize = updates.values().map(|v| v.len()).sum();
131
132 if let Err(e) = check_updates.update_versions(updates.values().flat_map(|unit_updates| {
133 unit_updates
134 .iter()
135 .map(|u| (u.usage, u.package, u.new_req.clone()))
136 })) {
137 log::error!("{e}");
138 std::process::exit(1);
139 }
140
141 if args.upgrade {
142 run_cargo_update();
143 }
144
145 println!(
146 "\n Upgraded {count} {}.",
147 if count == 1 {
148 "dependency"
149 } else {
150 "dependencies"
151 }
152 );
153 } else {
154 update::print_summary(&updates);
155 if !updates.is_empty() {
156 println!(
157 "\n{}",
158 Style::new().dim().apply_to("Run with -u or -U to upgrade.")
159 );
160 }
161 }
162
163 if fail_on_updates && has_updates {
164 std::process::exit(2);
165 }
166}
167
168fn run_cargo_update() {
169 let status = std::process::Command::new("cargo")
170 .arg("update")
171 .status()
172 .expect("failed to run cargo update");
173
174 if !status.success() {
175 log::error!("cargo update failed");
176 std::process::exit(1);
177 }
178}