use eyre::Result;
use itertools::{Either, Itertools};
use lux_lib::{
config::Config,
package::PackageName,
progress::MultiProgress,
remote_package_db::RemotePackageDB,
rockspec::lua_dependency::{self},
workspace::Workspace,
};
use crate::workspace::{
sync_build_dependencies_if_locked, sync_dependencies_if_locked,
sync_test_dependencies_if_locked, PackageReqOrGitShorthand,
};
#[derive(clap::Args)]
pub struct Add {
package_req: Vec<PackageReqOrGitShorthand>,
#[arg(long, visible_short_alias = 'f')]
force: bool,
#[arg(short, long, alias = "dev", visible_short_aliases = ['d', 'b'])]
build: Option<Vec<PackageReqOrGitShorthand>>,
#[arg(short, long, visible_short_alias = 't')]
test: Option<Vec<PackageReqOrGitShorthand>>,
#[arg(short, long, visible_short_alias = 'p')]
package: Option<PackageName>,
}
pub async fn add(data: Add, config: Config) -> Result<()> {
let mut workspace = Workspace::current_or_err()?;
let project = workspace.single_member_or_select_mut(&data.package)?;
let progress = MultiProgress::new(&config);
let bar = progress.map(MultiProgress::new_bar);
let db = RemotePackageDB::from_config(&config, &bar).await?;
let progress = MultiProgress::new_arc(&config);
let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
data.package_req.iter().partition_map(|req| match req {
PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
});
if !data.package_req.is_empty() {
project
.add(
lua_dependency::DependencyType::Regular(dependencies.iter().collect()),
&db,
)
.await?;
project
.add_git(lua_dependency::LuaDependencyType::Regular(
git_dependencies.iter().collect(),
))
.await?;
}
let build_packages = data.build.unwrap_or_default();
if !build_packages.is_empty() {
let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
build_packages.iter().partition_map(|req| match req {
PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
});
project
.add(
lua_dependency::DependencyType::Build(dependencies.iter().collect()),
&db,
)
.await?;
project
.add_git(lua_dependency::LuaDependencyType::Build(
git_dependencies.iter().collect(),
))
.await?;
}
let test_packages = data.test.unwrap_or_default();
if !test_packages.is_empty() {
let (dependencies, git_dependencies): (Vec<_>, Vec<_>) =
test_packages.iter().partition_map(|req| match req {
PackageReqOrGitShorthand::PackageReq(req) => Either::Left(req.clone()),
PackageReqOrGitShorthand::GitShorthand(url) => Either::Right(url.clone()),
});
project
.add(
lua_dependency::DependencyType::Test(dependencies.iter().collect()),
&db,
)
.await?;
project
.add_git(lua_dependency::LuaDependencyType::Test(
git_dependencies.iter().collect(),
))
.await?;
}
if !data.package_req.is_empty() {
sync_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
}
if !build_packages.is_empty() {
sync_build_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
}
if !test_packages.is_empty() {
sync_test_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
}
Ok(())
}
#[cfg(test)]
mod test {
use assert_fs::{prelude::PathCopy, TempDir};
use lux_lib::config::ConfigBuilder;
use serial_test::serial;
use super::*;
use std::path::PathBuf;
#[serial]
#[tokio::test]
async fn test_add_regular_dependencies() {
if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
println!("Skipping impure test");
return;
}
let sample_project: PathBuf = "resources/test/sample-projects/init/".into();
let project_root = TempDir::new().unwrap();
project_root.copy_from(&sample_project, &["**"]).unwrap();
let cwd = std::env::current_dir().unwrap();
std::env::set_current_dir(&project_root).unwrap();
let config = ConfigBuilder::new().unwrap().build().unwrap();
let args = Add {
package: None,
package_req: vec!["penlight@1.5".parse().unwrap()],
force: false,
build: Option::None,
test: Option::None,
};
add(args, config.clone()).await.unwrap();
let lockfile_path = project_root.join("lux.lock");
let lockfile_content =
String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
assert!(lockfile_content.contains("penlight"));
assert!(lockfile_content.contains("luafilesystem"));
let args = Add {
package: None,
package_req: vec!["md5".parse().unwrap()],
force: false,
build: Option::None,
test: Option::None,
};
add(args, config.clone()).await.unwrap();
let lockfile_path = project_root.join("lux.lock");
let lockfile_content =
String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
assert!(lockfile_content.contains("penlight"));
assert!(lockfile_content.contains("luafilesystem"));
assert!(lockfile_content.contains("md5"));
std::env::set_current_dir(&cwd).unwrap();
}
#[serial]
#[tokio::test]
async fn test_add_build_dependencies() {
if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
println!("Skipping impure test");
return;
}
let sample_project: PathBuf = "resources/test/sample-projects/init/".into();
let project_root = TempDir::new().unwrap();
project_root.copy_from(&sample_project, &["**"]).unwrap();
let cwd = std::env::current_dir().unwrap();
std::env::set_current_dir(&project_root).unwrap();
let config = ConfigBuilder::new().unwrap().build().unwrap();
let args = Add {
package: None,
package_req: Vec::new(),
force: false,
build: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
test: Option::None,
};
add(args, config.clone()).await.unwrap();
let lockfile_path = project_root.join("lux.lock");
let lockfile_content =
String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
assert!(lockfile_content.contains("penlight"));
assert!(lockfile_content.contains("luafilesystem"));
let args = Add {
package: None,
package_req: Vec::new(),
force: false,
build: Option::Some(vec!["md5".parse().unwrap()]),
test: Option::None,
};
add(args, config.clone()).await.unwrap();
let lockfile_path = project_root.join("lux.lock");
let lockfile_content =
String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
assert!(lockfile_content.contains("penlight"));
assert!(lockfile_content.contains("luafilesystem"));
assert!(lockfile_content.contains("md5"));
std::env::set_current_dir(&cwd).unwrap();
}
#[serial]
#[tokio::test]
async fn test_add_test_dependencies() {
if std::env::var("LUX_SKIP_IMPURE_TESTS").unwrap_or("0".into()) == "1" {
println!("Skipping impure test");
return;
}
let sample_project: PathBuf = "resources/test/sample-projects/init/".into();
let project_root = TempDir::new().unwrap();
project_root.copy_from(&sample_project, &["**"]).unwrap();
let cwd = std::env::current_dir().unwrap();
std::env::set_current_dir(&project_root).unwrap();
let config = ConfigBuilder::new().unwrap().build().unwrap();
let args = Add {
package: None,
package_req: Vec::new(),
force: false,
build: Option::None,
test: Option::Some(vec!["penlight@1.5".parse().unwrap()]),
};
add(args, config.clone()).await.unwrap();
let lockfile_path = project_root.join("lux.lock");
let lockfile_content =
String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
assert!(lockfile_content.contains("penlight"));
assert!(lockfile_content.contains("luafilesystem"));
let args = Add {
package: None,
package_req: Vec::new(),
force: false,
build: Option::None,
test: Option::Some(vec!["md5".parse().unwrap()]),
};
add(args, config.clone()).await.unwrap();
let lockfile_path = project_root.join("lux.lock");
let lockfile_content =
String::from_utf8(tokio::fs::read(&lockfile_path).await.unwrap()).unwrap();
assert!(lockfile_content.contains("penlight"));
assert!(lockfile_content.contains("luafilesystem"));
assert!(lockfile_content.contains("md5"));
std::env::set_current_dir(&cwd).unwrap();
}
}