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