use eyre::{OptionExt, Result};
use itertools::{Either, Itertools};
use lux_lib::{
config::Config,
progress::MultiProgress,
project::Project,
remote_package_db::RemotePackageDB,
rockspec::lua_dependency::{self},
};
use crate::utils::project::{
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>>,
}
pub async fn add(data: Add, config: Config) -> Result<()> {
let mut project = Project::current()?.ok_or_eyre("No project found")?;
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), &db)
.await?;
project
.add_git(lua_dependency::LuaDependencyType::Regular(git_dependencies))
.await?;
sync_dependencies_if_locked(&project, progress.clone(), &config).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), &db)
.await?;
project
.add_git(lua_dependency::LuaDependencyType::Build(git_dependencies))
.await?;
sync_build_dependencies_if_locked(&project, progress.clone(), &config).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), &db)
.await?;
project
.add_git(lua_dependency::LuaDependencyType::Test(git_dependencies))
.await?;
sync_test_dependencies_if_locked(&project, 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_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_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_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_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_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_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();
}
}