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