1use itertools::{Either, Itertools};
2use lux_lib::{
3 config::Config,
4 package::PackageName,
5 remote_package_db::RemotePackageDB,
6 rockspec::lua_dependency::{self},
7 workspace::Workspace,
8};
9
10use miette::Result;
11
12use crate::workspace::{
13 sync_build_dependencies_if_locked, sync_dependencies_if_locked,
14 sync_test_dependencies_if_locked, PackageReqOrGitShorthand,
15};
16
17#[derive(clap::Args)]
18pub struct Add {
19 package_req: Vec<PackageReqOrGitShorthand>,
29
30 #[arg(long, visible_short_alias = 'f')]
32 force: bool,
33
34 #[arg(short, long, alias = "dev", visible_short_aliases = ['d', 'b'])]
37 build: Option<Vec<PackageReqOrGitShorthand>>,
38
39 #[arg(short, long, visible_short_alias = 't')]
41 test: Option<Vec<PackageReqOrGitShorthand>>,
42
43 #[arg(short, long, visible_short_alias = 'p')]
45 package: Option<PackageName>,
46}
47
48pub async fn add(data: Add, config: Config) -> Result<()> {
49 let mut workspace = Workspace::current_or_err()?;
50 let project = workspace.single_member_or_select_mut(&data.package)?;
51 let db = RemotePackageDB::from_config(&config).await?;
52
53 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
54 data.package_req.iter().partition_map(|req| match req {
55 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
56 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
57 });
58
59 if !data.package_req.is_empty() {
60 project
61 .add(
62 lua_dependency::DependencyType::Regular(dependencies.iter().collect()),
63 &db,
64 )
65 .await?;
66 project
67 .add_git(lua_dependency::LuaDependencyType::Regular(
68 git_dependencies.iter().collect(),
69 ))
70 .await?;
71 }
72
73 let build_packages = data.build.unwrap_or_default();
74 if !build_packages.is_empty() {
75 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
76 build_packages.iter().partition_map(|req| match req {
77 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
78 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
79 });
80 project
81 .add(
82 lua_dependency::DependencyType::Build(dependencies.iter().collect()),
83 &db,
84 )
85 .await?;
86 project
87 .add_git(lua_dependency::LuaDependencyType::Build(
88 git_dependencies.iter().collect(),
89 ))
90 .await?;
91 }
92
93 let test_packages = data.test.unwrap_or_default();
94 if !test_packages.is_empty() {
95 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
96 test_packages.iter().partition_map(|req| match req {
97 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
98 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
99 });
100 project
101 .add(
102 lua_dependency::DependencyType::Test(dependencies.iter().collect()),
103 &db,
104 )
105 .await?;
106 project
107 .add_git(lua_dependency::LuaDependencyType::Test(
108 git_dependencies.iter().collect(),
109 ))
110 .await?;
111 }
112
113 if !data.package_req.is_empty() {
114 sync_dependencies_if_locked(&workspace, &config).await?;
115 }
116 if !build_packages.is_empty() {
117 sync_build_dependencies_if_locked(&workspace, &config).await?;
118 }
119 if !test_packages.is_empty() {
120 sync_test_dependencies_if_locked(&workspace, &config).await?;
121 }
122
123 Ok(())
124}
125
126#[cfg(test)]
127mod tests {
128 use assert_fs::{prelude::PathCopy, TempDir};
129 use lux_lib::config::ConfigBuilder;
130 use serial_test::serial;
131
132 use super::*;
133 use std::path::PathBuf;
134
135 #[serial]
136 #[tokio::test]
137 async fn test_add_regular_dependencies() {
138 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
139 println!("Skipping impure test");
140 return;
141 }
142 let sample_project =
143 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
144 let project_root = TempDir::new().unwrap();
145 project_root.copy_from(&sample_project, &["**"]).unwrap();
146 let cwd = std::env::current_dir().unwrap();
147 std::env::set_current_dir(&project_root).unwrap();
148 let config = ConfigBuilder::new().unwrap().build().unwrap();
149 let args = Add {
150 package: None,
151 package_req: vec!["penlight@1.5".parse().unwrap()],
152 force: false,
153 build: Option::None,
154 test: Option::None,
155 };
156 add(args, config.clone()).await.unwrap();
157 let lockfile_path = project_root.join("lux.lock");
158 let lockfile_content =
159 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
160 assert!(lockfile_content.contains("penlight"));
161 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
164 package: None,
165 package_req: vec!["md5".parse().unwrap()],
166 force: false,
167 build: Option::None,
168 test: Option::None,
169 };
170 add(args, config.clone()).await.unwrap();
171 let lockfile_path = project_root.join("lux.lock");
172 let lockfile_content =
173 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
174 assert!(lockfile_content.contains("penlight"));
175 assert!(lockfile_content.contains("luafilesystem"));
176 assert!(lockfile_content.contains("md5"));
177
178 std::env::set_current_dir(&cwd).unwrap();
179 }
180
181 #[serial]
182 #[tokio::test]
183 async fn test_add_build_dependencies() {
184 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
185 println!("Skipping impure test");
186 return;
187 }
188 let sample_project =
189 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
190 let project_root = TempDir::new().unwrap();
191 project_root.copy_from(&sample_project, &["**"]).unwrap();
192 let cwd = std::env::current_dir().unwrap();
193 std::env::set_current_dir(&project_root).unwrap();
194 let config = ConfigBuilder::new().unwrap().build().unwrap();
195 let args = Add {
196 package: None,
197 package_req: Vec::new(),
198 force: false,
199 build: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
200 test: Option::None,
201 };
202 add(args, config.clone()).await.unwrap();
203 let lockfile_path = project_root.join("lux.lock");
204 let lockfile_content =
205 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
206 assert!(lockfile_content.contains("penlight"));
207 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
210 package: None,
211 package_req: Vec::new(),
212 force: false,
213 build: Option::Some(vec!["md5".parse().unwrap()]),
214 test: Option::None,
215 };
216 add(args, config.clone()).await.unwrap();
217 let lockfile_path = project_root.join("lux.lock");
218 let lockfile_content =
219 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
220 assert!(lockfile_content.contains("penlight"));
221 assert!(lockfile_content.contains("luafilesystem"));
222 assert!(lockfile_content.contains("md5"));
223
224 std::env::set_current_dir(&cwd).unwrap();
225 }
226
227 #[serial]
228 #[tokio::test]
229 async fn test_add_test_dependencies() {
230 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
231 println!("Skipping impure test");
232 return;
233 }
234 let sample_project =
235 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
236 let project_root = TempDir::new().unwrap();
237 project_root.copy_from(&sample_project, &["**"]).unwrap();
238 let cwd = std::env::current_dir().unwrap();
239 std::env::set_current_dir(&project_root).unwrap();
240 let config = ConfigBuilder::new().unwrap().build().unwrap();
241 let args = Add {
242 package: None,
243 package_req: Vec::new(),
244 force: false,
245 build: Option::None,
246 test: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
247 };
248 add(args, config.clone()).await.unwrap();
249 let lockfile_path = project_root.join("lux.lock");
250 let lockfile_content =
251 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
252 assert!(lockfile_content.contains("penlight"));
253 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
256 package: None,
257 package_req: Vec::new(),
258 force: false,
259 build: Option::None,
260 test: Option::Some(vec!["md5".parse().unwrap()]),
261 };
262 add(args, config.clone()).await.unwrap();
263 let lockfile_path = project_root.join("lux.lock");
264 let lockfile_content =
265 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
266 assert!(lockfile_content.contains("penlight"));
267 assert!(lockfile_content.contains("luafilesystem"));
268 assert!(lockfile_content.contains("md5"));
269
270 std::env::set_current_dir(&cwd).unwrap();
271 }
272}