Skip to main content

lux_cli/
outdated.rs

1use std::collections::HashMap;
2
3use clap::Args;
4use eyre::Result;
5use itertools::Itertools;
6use lux_lib::{
7    config::Config, lockfile::LocalPackage, lua_version::LuaVersion, package::PackageVersion,
8    progress::MultiProgress, project::Project, remote_package_db::RemotePackageDB,
9};
10use text_trees::{FormatCharacters, StringTreeNode, TreeFormatting};
11
12use crate::utils::project::sync_dependencies_if_locked;
13
14#[derive(Args)]
15pub struct Outdated {
16    #[arg(long)]
17    porcelain: bool,
18}
19
20/// List rocks that are outdated
21/// If in a project, this lists rocks in the project tree
22pub async fn outdated(outdated_data: Outdated, config: Config) -> Result<()> {
23    let progress = MultiProgress::new(&config);
24    let bar = progress.map(MultiProgress::new_bar);
25    let project = Project::current()?;
26    let tree = match &project {
27        Some(project) => {
28            // Make sure dependencies are synced if in a project
29            sync_dependencies_if_locked(project, MultiProgress::new_arc(&config), &config).await?;
30            project.tree(&config)?
31        }
32        None => {
33            let lua_version = LuaVersion::from(&config)?.clone();
34            config.user_tree(lua_version)?
35        }
36    };
37
38    let package_db = RemotePackageDB::from_config(&config, &bar).await?;
39
40    bar.map(|b| b.set_message("🔎 Checking for outdated rocks...".to_string()));
41
42    // NOTE: This will display all installed versions and each possible upgrade.
43    // However, this should also take into account dependency constraints made by other rocks.
44    // This will naturally occur with lockfiles and should be accounted for directly in the
45    // `has_update` function.
46    let rock_list = tree.as_rock_list()?;
47    let rock_list = rock_list
48        .iter()
49        .map(|rock| {
50            rock.to_package()
51                .has_update(&package_db)
52                .map(|mb_version| mb_version.map(|version| (rock, version)))
53        })
54        .filter_map_ok(|mb_tuple| mb_tuple)
55        .try_collect::<_, Vec<(&LocalPackage, PackageVersion)>, _>()?;
56
57    let rock_list = rock_list
58        .iter()
59        .sorted_by_key(|(rock, _)| rock.name().to_owned())
60        .into_group_map_by(|(rock, _)| rock.name().to_owned());
61
62    bar.map(|b| b.finish_and_clear());
63
64    if outdated_data.porcelain {
65        let jsonified_rock_list = rock_list
66            .iter()
67            .map(|(key, values)| {
68                (
69                    key,
70                    values
71                        .iter()
72                        .map(|(k, v)| (k.version().to_string(), v.to_string()))
73                        .collect::<HashMap<_, _>>(),
74                )
75            })
76            .collect::<HashMap<_, _>>();
77
78        println!("{}", serde_json::to_string(&jsonified_rock_list)?);
79    } else {
80        let formatting = TreeFormatting::dir_tree(FormatCharacters::box_chars());
81
82        for (rock_name, updates) in rock_list {
83            let mut tree = StringTreeNode::new(rock_name.to_string());
84
85            for (rock, latest_version) in updates {
86                tree.push(format!("{} => {}", rock.version(), latest_version));
87            }
88
89            println!("{}", tree.to_string_with_format(&formatting)?);
90        }
91    }
92
93    Ok(())
94}