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 pub(crate) 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(
68 lua_dependency::LuaDependencyType::Regular(git_dependencies.iter().collect()),
69 &config,
70 )
71 .await?;
72 }
73
74 let build_packages = data.build.unwrap_or_default();
75 if !build_packages.is_empty() {
76 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
77 build_packages.iter().partition_map(|req| match req {
78 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
79 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
80 });
81 project
82 .add(
83 lua_dependency::DependencyType::Build(dependencies.iter().collect()),
84 &db,
85 )
86 .await?;
87 project
88 .add_git(
89 lua_dependency::LuaDependencyType::Build(git_dependencies.iter().collect()),
90 &config,
91 )
92 .await?;
93 }
94
95 let test_packages = data.test.unwrap_or_default();
96 if !test_packages.is_empty() {
97 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
98 test_packages.iter().partition_map(|req| match req {
99 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
100 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
101 });
102 project
103 .add(
104 lua_dependency::DependencyType::Test(dependencies.iter().collect()),
105 &db,
106 )
107 .await?;
108 project
109 .add_git(
110 lua_dependency::LuaDependencyType::Test(git_dependencies.iter().collect()),
111 &config,
112 )
113 .await?;
114 }
115
116 if !data.package_req.is_empty() {
117 sync_dependencies_if_locked(&workspace, &config).await?;
118 }
119 if !build_packages.is_empty() {
120 sync_build_dependencies_if_locked(&workspace, &config).await?;
121 }
122 if !test_packages.is_empty() {
123 sync_test_dependencies_if_locked(&workspace, &config).await?;
124 }
125
126 Ok(())
127}
128
129#[cfg(test)]
130mod tests {
131 use assert_fs::{prelude::PathCopy, TempDir};
132 use lux_lib::config::ConfigBuilder;
133 use serial_test::serial;
134
135 use super::*;
136 use std::path::PathBuf;
137
138 #[serial]
139 #[tokio::test]
140 async fn test_add_regular_dependencies() {
141 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
142 println!("Skipping impure test");
143 return;
144 }
145 let sample_project =
146 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
147 let project_root = TempDir::new().unwrap();
148 project_root.copy_from(&sample_project, &["**"]).unwrap();
149 let cwd = std::env::current_dir().unwrap();
150 std::env::set_current_dir(&project_root).unwrap();
151 let config = ConfigBuilder::new().unwrap().build().unwrap();
152 let args = Add {
153 package: None,
154 package_req: vec!["penlight@1.5".parse().unwrap()],
155 force: false,
156 build: Option::None,
157 test: Option::None,
158 };
159 add(args, config.clone()).await.unwrap();
160 let lockfile_path = project_root.join("lux.lock");
161 let lockfile_content =
162 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
163 assert!(lockfile_content.contains("penlight"));
164 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
167 package: None,
168 package_req: vec!["md5".parse().unwrap()],
169 force: false,
170 build: Option::None,
171 test: Option::None,
172 };
173 add(args, config.clone()).await.unwrap();
174 let lockfile_path = project_root.join("lux.lock");
175 let lockfile_content =
176 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
177 assert!(lockfile_content.contains("penlight"));
178 assert!(lockfile_content.contains("luafilesystem"));
179 assert!(lockfile_content.contains("md5"));
180
181 std::env::set_current_dir(&cwd).unwrap();
182 }
183
184 #[serial]
185 #[tokio::test]
186 async fn test_add_build_dependencies() {
187 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
188 println!("Skipping impure test");
189 return;
190 }
191 let sample_project =
192 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
193 let project_root = TempDir::new().unwrap();
194 project_root.copy_from(&sample_project, &["**"]).unwrap();
195 let cwd = std::env::current_dir().unwrap();
196 std::env::set_current_dir(&project_root).unwrap();
197 let config = ConfigBuilder::new().unwrap().build().unwrap();
198 let args = Add {
199 package: None,
200 package_req: Vec::new(),
201 force: false,
202 build: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
203 test: Option::None,
204 };
205 add(args, config.clone()).await.unwrap();
206 let lockfile_path = project_root.join("lux.lock");
207 let lockfile_content =
208 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
209 assert!(lockfile_content.contains("penlight"));
210 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
213 package: None,
214 package_req: Vec::new(),
215 force: false,
216 build: Option::Some(vec!["md5".parse().unwrap()]),
217 test: Option::None,
218 };
219 add(args, config.clone()).await.unwrap();
220 let lockfile_path = project_root.join("lux.lock");
221 let lockfile_content =
222 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
223 assert!(lockfile_content.contains("penlight"));
224 assert!(lockfile_content.contains("luafilesystem"));
225 assert!(lockfile_content.contains("md5"));
226
227 std::env::set_current_dir(&cwd).unwrap();
228 }
229
230 #[serial]
231 #[tokio::test]
232 async fn test_add_test_dependencies() {
233 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
234 println!("Skipping impure test");
235 return;
236 }
237 let sample_project =
238 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-projects/init/");
239 let project_root = TempDir::new().unwrap();
240 project_root.copy_from(&sample_project, &["**"]).unwrap();
241 let cwd = std::env::current_dir().unwrap();
242 std::env::set_current_dir(&project_root).unwrap();
243 let config = ConfigBuilder::new().unwrap().build().unwrap();
244 let args = Add {
245 package: None,
246 package_req: Vec::new(),
247 force: false,
248 build: Option::None,
249 test: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
250 };
251 add(args, config.clone()).await.unwrap();
252 let lockfile_path = project_root.join("lux.lock");
253 let lockfile_content =
254 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
255 assert!(lockfile_content.contains("penlight"));
256 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
259 package: None,
260 package_req: Vec::new(),
261 force: false,
262 build: Option::None,
263 test: Option::Some(vec!["md5".parse().unwrap()]),
264 };
265 add(args, config.clone()).await.unwrap();
266 let lockfile_path = project_root.join("lux.lock");
267 let lockfile_content =
268 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
269 assert!(lockfile_content.contains("penlight"));
270 assert!(lockfile_content.contains("luafilesystem"));
271 assert!(lockfile_content.contains("md5"));
272
273 std::env::set_current_dir(&cwd).unwrap();
274 }
275}