Skip to main content

lux_cli/
pin.rs

1use clap::Args;
2use itertools::Itertools;
3use lux_lib::config::Config;
4use lux_lib::lockfile::PinnedState;
5use lux_lib::lua_version::LuaVersion;
6use lux_lib::operations;
7use lux_lib::package::PackageName;
8use lux_lib::package::PackageReq;
9use lux_lib::rockspec::lua_dependency;
10use lux_lib::tree::RockMatches;
11use lux_lib::workspace::Workspace;
12
13use miette::miette;
14use miette::Context;
15use miette::Result;
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
43            if data
44                .package_req
45                .iter()
46                .any(|pkg| !pkg.version_req().is_any())
47            {
48                return Err(miette!(
49                    "Cannot pin project dependencies using version constraints."
50                ));
51            }
52            let packages = data
53                .package_req
54                .iter()
55                .map(|pkg| pkg.name())
56                .cloned()
57                .collect_vec();
58            if !packages.is_empty() {
59                project
60                    .set_pinned_state(
61                        lua_dependency::LuaDependencyType::Regular(packages.iter().collect()),
62                        pin,
63                    )
64                    .await?;
65            }
66            let build_packages = data.build.unwrap_or_default();
67            if !build_packages.is_empty() {
68                project
69                    .set_pinned_state(
70                        lua_dependency::LuaDependencyType::Build(build_packages.iter().collect()),
71                        pin,
72                    )
73                    .await?;
74            }
75            let test_packages = data.test.unwrap_or_default();
76            if !test_packages.is_empty() {
77                project
78                    .set_pinned_state(
79                        lua_dependency::LuaDependencyType::Test(test_packages.iter().collect()),
80                        pin,
81                    )
82                    .await?;
83            }
84            if !packages.is_empty() {
85                operations::Sync::new(&workspace, &config)
86                    .sync_dependencies()
87                    .await
88                    .wrap_err("syncing dependencies with the project lockfile failed.")?;
89            }
90            if !build_packages.is_empty() {
91                operations::Sync::new(&workspace, &config)
92                    .sync_build_dependencies()
93                    .await
94                    .wrap_err("syncing build dependencies with the project lockfile failed.")?;
95            }
96            if !test_packages.is_empty() {
97                operations::Sync::new(&workspace, &config)
98                    .sync_test_dependencies()
99                    .await
100                    .wrap_err("syncing test dependencies with the project lockfile failed.")?;
101            }
102        }
103        None => {
104            let tree = config.user_tree(LuaVersion::from(&config)?.clone())?;
105
106            for package in &data.package_req {
107                match tree.match_rocks_and(package, |package| pin != package.pinned())? {
108                    RockMatches::Single(rock) => {
109                        operations::set_pinned_state(&rock, &tree, pin)?;
110                    }
111                    RockMatches::Many(_) => {
112                        todo!("Add an error here about many conflicting types and to use `all:`")
113                    }
114                    RockMatches::NotFound(_) => return Err(miette!("Rock {} not found!", package)),
115                }
116            }
117        }
118    }
119    Ok(())
120}