1use eyre::Result;
2use itertools::{Either, Itertools};
3use lux_lib::{
4 config::Config,
5 package::PackageName,
6 progress::MultiProgress,
7 remote_package_db::RemotePackageDB,
8 rockspec::lua_dependency::{self},
9 workspace::Workspace,
10};
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 progress = MultiProgress::new(&config);
52 let bar = progress.map(MultiProgress::new_bar);
53 let db = RemotePackageDB::from_config(&config, &bar).await?;
54
55 let progress = MultiProgress::new_arc(&config);
56
57 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
58 data.package_req.iter().partition_map(|req| match req {
59 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
60 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
61 });
62
63 if !data.package_req.is_empty() {
64 project
65 .add(
66 lua_dependency::DependencyType::Regular(dependencies.iter().collect()),
67 &db,
68 )
69 .await?;
70 project
71 .add_git(lua_dependency::LuaDependencyType::Regular(
72 git_dependencies.iter().collect(),
73 ))
74 .await?;
75 }
76
77 let build_packages = data.build.unwrap_or_default();
78 if !build_packages.is_empty() {
79 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
80 build_packages.iter().partition_map(|req| match req {
81 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
82 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
83 });
84 project
85 .add(
86 lua_dependency::DependencyType::Build(dependencies.iter().collect()),
87 &db,
88 )
89 .await?;
90 project
91 .add_git(lua_dependency::LuaDependencyType::Build(
92 git_dependencies.iter().collect(),
93 ))
94 .await?;
95 }
96
97 let test_packages = data.test.unwrap_or_default();
98 if !test_packages.is_empty() {
99 let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
100 test_packages.iter().partition_map(|req| match req {
101 PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
102 PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
103 });
104 project
105 .add(
106 lua_dependency::DependencyType::Test(dependencies.iter().collect()),
107 &db,
108 )
109 .await?;
110 project
111 .add_git(lua_dependency::LuaDependencyType::Test(
112 git_dependencies.iter().collect(),
113 ))
114 .await?;
115 }
116
117 if !data.package_req.is_empty() {
118 sync_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
119 }
120 if !build_packages.is_empty() {
121 sync_build_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
122 }
123 if !test_packages.is_empty() {
124 sync_test_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
125 }
126
127 Ok(())
128}
129
130#[cfg(test)]
131mod test {
132 use assert_fs::{prelude::PathCopy, TempDir};
133 use lux_lib::config::ConfigBuilder;
134 use serial_test::serial;
135
136 use super::*;
137 use std::path::PathBuf;
138
139 #[serial]
140 #[tokio::test]
141 async fn test_add_regular_dependencies() {
142 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
143 println!("Skipping impure test");
144 return;
145 }
146 let sample_project: PathBuf = "resources/test/sample-projects/init/".into();
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: PathBuf = "resources/test/sample-projects/init/".into();
192 let project_root = TempDir::new().unwrap();
193 project_root.copy_from(&sample_project, &["**"]).unwrap();
194 let cwd = std::env::current_dir().unwrap();
195 std::env::set_current_dir(&project_root).unwrap();
196 let config = ConfigBuilder::new().unwrap().build().unwrap();
197 let args = Add {
198 package: None,
199 package_req: Vec::new(),
200 force: false,
201 build: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
202 test: Option::None,
203 };
204 add(args, config.clone()).await.unwrap();
205 let lockfile_path = project_root.join("lux.lock");
206 let lockfile_content =
207 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
208 assert!(lockfile_content.contains("penlight"));
209 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
212 package: None,
213 package_req: Vec::new(),
214 force: false,
215 build: Option::Some(vec!["md5".parse().unwrap()]),
216 test: Option::None,
217 };
218 add(args, config.clone()).await.unwrap();
219 let lockfile_path = project_root.join("lux.lock");
220 let lockfile_content =
221 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
222 assert!(lockfile_content.contains("penlight"));
223 assert!(lockfile_content.contains("luafilesystem"));
224 assert!(lockfile_content.contains("md5"));
225
226 std::env::set_current_dir(&cwd).unwrap();
227 }
228
229 #[serial]
230 #[tokio::test]
231 async fn test_add_test_dependencies() {
232 if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
233 println!("Skipping impure test");
234 return;
235 }
236 let sample_project: PathBuf = "resources/test/sample-projects/init/".into();
237 let project_root = TempDir::new().unwrap();
238 project_root.copy_from(&sample_project, &["**"]).unwrap();
239 let cwd = std::env::current_dir().unwrap();
240 std::env::set_current_dir(&project_root).unwrap();
241 let config = ConfigBuilder::new().unwrap().build().unwrap();
242 let args = Add {
243 package: None,
244 package_req: Vec::new(),
245 force: false,
246 build: Option::None,
247 test: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
248 };
249 add(args, config.clone()).await.unwrap();
250 let lockfile_path = project_root.join("lux.lock");
251 let lockfile_content =
252 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
253 assert!(lockfile_content.contains("penlight"));
254 assert!(lockfile_content.contains("luafilesystem")); let args = Add {
257 package: None,
258 package_req: Vec::new(),
259 force: false,
260 build: Option::None,
261 test: Option::Some(vec!["md5".parse().unwrap()]),
262 };
263 add(args, config.clone()).await.unwrap();
264 let lockfile_path = project_root.join("lux.lock");
265 let lockfile_content =
266 String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
267 assert!(lockfile_content.contains("penlight"));
268 assert!(lockfile_content.contains("luafilesystem"));
269 assert!(lockfile_content.contains("md5"));
270
271 std::env::set_current_dir(&cwd).unwrap();
272 }
273}