Skip to main content

lux_cli/
pin.rs

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