Skip to main content

check_updates_cli/
lib.rs

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}