lux_cli/
pin.rs

1use clap::Args;
2use eyre::eyre;
3use eyre::Context;
4use eyre::Result;
5use itertools::Itertools;
6use lux_lib::config::{Config, LuaVersion};
7use lux_lib::lockfile::PinnedState;
8use lux_lib::operations;
9use lux_lib::package::PackageName;
10use lux_lib::package::PackageReq;
11use lux_lib::progress::MultiProgress;
12use lux_lib::project::Project;
13use lux_lib::rockspec::lua_dependency;
14use lux_lib::tree::RockMatches;
15
16#[derive(Args)]
17pub struct ChangePin {
18    /// Installed package or dependency to pin.
19    /// If pinning a dependency in a project, this should
20    /// be the package name.
21    package: Vec<PackageReq>,
22
23    /// Pin a development dependency.
24    /// Also called `dev`.
25    #[arg(short, long, alias = "dev", visible_short_aliases = ['d', 'b'])]
26    build: Option<Vec<PackageName>>,
27
28    /// Pin a test dependency.
29    #[arg(short, long)]
30    test: Option<Vec<PackageName>>,
31}
32
33pub async fn set_pinned_state(data: ChangePin, config: Config, pin: PinnedState) -> Result<()> {
34    match Project::current()? {
35        Some(mut project) => {
36            let progress = MultiProgress::new_arc();
37            if data.package.iter().any(|pkg| !pkg.version_req().is_any()) {
38                return Err(eyre!(
39                    "Cannot pin project dependencies using version constraints."
40                ));
41            }
42            let packages = data
43                .package
44                .iter()
45                .map(|pkg| pkg.name())
46                .cloned()
47                .collect_vec();
48            if !packages.is_empty() {
49                project
50                    .set_pinned_state(lua_dependency::LuaDependencyType::Regular(packages), pin)
51                    .await?;
52                operations::Sync::new(&project, &config)
53                    .progress(progress.clone())
54                    .sync_dependencies()
55                    .await
56                    .wrap_err("syncing dependencies with the project lockfile failed.")?;
57            }
58            let build_packages = data.build.unwrap_or_default();
59            if !build_packages.is_empty() {
60                project
61                    .set_pinned_state(
62                        lua_dependency::LuaDependencyType::Build(build_packages),
63                        pin,
64                    )
65                    .await?;
66                operations::Sync::new(&project, &config)
67                    .progress(progress.clone())
68                    .sync_build_dependencies()
69                    .await
70                    .wrap_err("syncing build dependencies with the project lockfile failed.")?;
71            }
72            let test_packages = data.test.unwrap_or_default();
73            if !test_packages.is_empty() {
74                project
75                    .set_pinned_state(lua_dependency::LuaDependencyType::Test(test_packages), pin)
76                    .await?;
77                operations::Sync::new(&project, &config)
78                    .progress(progress.clone())
79                    .sync_test_dependencies()
80                    .await
81                    .wrap_err("syncing test dependencies with the project lockfile failed.")?;
82            }
83        }
84        None => {
85            let tree = config.user_tree(LuaVersion::from(&config)?.clone())?;
86
87            for package in &data.package {
88                match tree.match_rocks_and(package, |package| pin != package.pinned())? {
89                    RockMatches::Single(rock) => {
90                        operations::set_pinned_state(&rock, &tree, pin)?;
91                    }
92                    RockMatches::Many(_) => {
93                        todo!("Add an error here about many conflicting types and to use `all:`")
94                    }
95                    RockMatches::NotFound(_) => return Err(eyre!("Rock {} not found!", package)),
96                }
97            }
98        }
99    }
100    Ok(())
101}