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            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}