Skip to main content

lux_cli/
outdated.rs

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