diff --git a/README.md b/README.md index f1c308e..510206b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SolDeer ![Rust][rust-badge] [![License: MIT][license-badge]][license] +# Soldeer ![Rust][rust-badge] [![License: MIT][license-badge]][license] [rust-badge]: https://img.shields.io/badge/Built%20with%20-Rust-e43716.svg [license]: https://opensource.org/licenses/MIT @@ -123,6 +123,12 @@ soldeer install ~ This command will download the zip file of the dependency, unzip it, install it in the `dependencies` directory. +```bash +soldeer install +``` + +This command will install all the dependencies from the `soldeer.toml`/`foundry.toml` file. + ### How to push a new dependency to the repository In order to push a new dependency to the repository you have create an account on [https://soldeer.xyz](https://soldeer.xyz), create a project that it will match the dependency name. diff --git a/src/commands.rs b/src/commands.rs index 835a8b9..01ed261 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -27,8 +27,8 @@ pub enum Subcommands { override_usage = "soldeer install ~ [URL]" )] pub struct Install { - #[clap(required = true)] - pub dependency: String, + #[clap(required = false)] + pub dependency: Option, #[clap(required = false)] pub remote_url: Option, } diff --git a/src/config.rs b/src/config.rs index ab73a37..129b8fb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,9 +14,12 @@ use std::fs::{ self, File, }; -use std::io; use std::io::Write; use std::path::Path; +use std::{ + env, + io, +}; use toml::Table; use toml_edit::{ value, @@ -111,7 +114,13 @@ pub async fn read_config(filename: String) -> Result, ConfigErro } pub fn define_config_file() -> Result { - let mut filename: String = String::from(FOUNDRY_CONFIG_FILE.to_str().unwrap()); + let mut filename: String; + if cfg!(test) { + filename = + env::var("config_file").unwrap_or(String::from(FOUNDRY_CONFIG_FILE.to_str().unwrap())) + } else { + filename = String::from(FOUNDRY_CONFIG_FILE.to_str().unwrap()); + }; // check if the foundry.toml has the dependencies defined, if so then we setup the foundry.toml as the config file if fs::metadata(&filename).is_ok() { diff --git a/src/lib.rs b/src/lib.rs index e5909d9..7b12b22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,19 +63,23 @@ pub struct FOUNDRY { pub async fn run(command: Subcommands) -> Result<(), SoldeerError> { match command { Subcommands::Install(install) => { + if install.dependency.is_none() { + return update().await; + } println!("{}", Paint::green("🦌 Running soldeer install 🦌\n")); - if !install.dependency.contains('~') { + let dependency = install.dependency.unwrap(); + if !dependency.contains('~') { return Err(SoldeerError { message: format!( "Dependency {} does not specify a version.\nThe format should be [DEPENDENCY]~[VERSION]", - install.dependency + dependency ), }); } let dependency_name: String = - install.dependency.split('~').collect::>()[0].to_string(); + dependency.split('~').collect::>()[0].to_string(); let dependency_version: String = - install.dependency.split('~').collect::>()[1].to_string(); + dependency.split('~').collect::>()[1].to_string(); let dependency_url: String; let mut custom_url = false; if install.remote_url.is_some() { @@ -255,83 +259,7 @@ pub async fn run(command: Subcommands) -> Result<(), SoldeerError> { } } Subcommands::Update(_) => { - println!("{}", Paint::green("🦌 Running soldeer update 🦌\n")); - - let dependencies: Vec = match read_config(String::new()).await { - Ok(dep) => dep, - Err(err) => return Err(SoldeerError { message: err.cause }), - }; - - match download_dependencies(&dependencies, true).await { - Ok(_) => {} - Err(err) => { - return Err(SoldeerError { - message: format!( - "Error downloading a dependency {}~{}", - err.name, err.version - ), - }) - } - } - - match unzip_dependencies(&dependencies) { - Ok(_) => {} - Err(err) => { - return Err(SoldeerError { - message: format!("Error unzipping dependency {}~{}", err.name, err.version), - }); - } - } - - match healthcheck_dependencies(&dependencies) { - Ok(_) => {} - Err(err) => { - return Err(SoldeerError { - message: format!( - "Error health-checking dependencies {}~{}", - err.name, err.version - ), - }); - } - } - - match write_lock(&dependencies, true) { - Ok(_) => {} - Err(err) => { - return Err(SoldeerError { - message: format!("Error writing the lock: {}", err.cause), - }); - } - } - - match cleanup_after(&dependencies) { - Ok(_) => {} - Err(err) => { - return Err(SoldeerError { - message: format!("Error cleanup dependencies {}~{}", err.name, err.version), - }); - } - } - - // check the foundry setup, in case we have a foundry.toml, then the foundry.toml will be used for `dependencies` - let f_setup_vec: Vec = match get_foundry_setup() { - Ok(f_setup) => f_setup, - Err(err) => { - return Err(SoldeerError { message: err.cause }); - } - }; - let foundry_setup: FOUNDRY = FOUNDRY { - remappings: f_setup_vec[0], - }; - - if foundry_setup.remappings { - match remappings().await { - Ok(_) => {} - Err(err) => { - return Err(SoldeerError { message: err.cause }); - } - } - } + return update().await; } Subcommands::Login(_) => { println!("{}", Paint::green("🦌 Running soldeer login 🦌\n")); @@ -376,3 +304,285 @@ pub async fn run(command: Subcommands) -> Result<(), SoldeerError> { } Ok(()) } + +async fn update() -> Result<(), SoldeerError> { + println!("{}", Paint::green("🦌 Running soldeer update 🦌\n")); + + let dependencies: Vec = match read_config(String::new()).await { + Ok(dep) => dep, + Err(err) => return Err(SoldeerError { message: err.cause }), + }; + + match download_dependencies(&dependencies, true).await { + Ok(_) => {} + Err(err) => { + return Err(SoldeerError { + message: format!( + "Error downloading a dependency {}~{}", + err.name, err.version + ), + }) + } + } + + match unzip_dependencies(&dependencies) { + Ok(_) => {} + Err(err) => { + return Err(SoldeerError { + message: format!("Error unzipping dependency {}~{}", err.name, err.version), + }); + } + } + + match healthcheck_dependencies(&dependencies) { + Ok(_) => {} + Err(err) => { + return Err(SoldeerError { + message: format!( + "Error health-checking dependencies {}~{}", + err.name, err.version + ), + }); + } + } + + match write_lock(&dependencies, true) { + Ok(_) => {} + Err(err) => { + return Err(SoldeerError { + message: format!("Error writing the lock: {}", err.cause), + }); + } + } + + match cleanup_after(&dependencies) { + Ok(_) => {} + Err(err) => { + return Err(SoldeerError { + message: format!("Error cleanup dependencies {}~{}", err.name, err.version), + }); + } + } + + // check the foundry setup, in case we have a foundry.toml, then the foundry.toml will be used for `dependencies` + let f_setup_vec: Vec = match get_foundry_setup() { + Ok(f_setup) => f_setup, + Err(err) => { + return Err(SoldeerError { message: err.cause }); + } + }; + let foundry_setup: FOUNDRY = FOUNDRY { + remappings: f_setup_vec[0], + }; + + if foundry_setup.remappings { + match remappings().await { + Ok(_) => {} + Err(err) => { + return Err(SoldeerError { message: err.cause }); + } + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + + use std::env; + use std::fs::{ + remove_dir_all, + remove_file, + }; + use std::io::Write; + use std::path::Path; + use std::{ + fs::{ + self, + }, + path::PathBuf, + }; + + use commands::{ + Install, + Update, + }; + use rand::{ + distributions::Alphanumeric, + Rng, + }; + use serial_test::serial; // 0.8 + + use super::*; + + #[test] + #[serial] + fn soldeer_install_moves_to_update_no_custom_link() { + let _ = remove_dir_all(DEPENDENCY_DIR.clone()); + let _ = remove_file(LOCK_FILE.clone()); + let content = r#" +# Full reference https://github.com/foundry-rs/foundry/tree/master/crates/config + +[profile.default] +script = "script" +solc = "0.8.26" +src = "src" +test = "test" +libs = ["dependencies"] + +[dependencies] +"@gearbox-protocol-periphery-v3" = "1.6.1" +"@openzeppelin-contracts" = "5.0.2" +"#; + + let target_config = define_config(true); + + write_to_config(&target_config, content); + + env::set_var("base_url", "https://api.soldeer.xyz"); + + let command = Subcommands::Install(Install { + dependency: None, + remote_url: None, + }); + + match run(command) { + Ok(_) => {} + Err(_) => { + clean_test_env(target_config.clone()); + assert_eq!("Invalid State", "") + } + } + + let mut path_dependency = DEPENDENCY_DIR.join("@gearbox-protocol-periphery-v3-1.6.1"); + + assert!(Path::new(&path_dependency).exists()); + path_dependency = DEPENDENCY_DIR.join("@openzeppelin-contracts-5.0.2"); + assert!(Path::new(&path_dependency).exists()); + clean_test_env(target_config); + } + + #[test] + #[serial] + fn soldeer_install_moves_to_update_custom_link() { + let _ = remove_dir_all(DEPENDENCY_DIR.clone()); + let _ = remove_file(LOCK_FILE.clone()); + let content = r#" +# Full reference https://github.com/foundry-rs/foundry/tree/master/crates/config + +[profile.default] +script = "script" +solc = "0.8.26" +src = "src" +test = "test" +libs = ["dependencies"] + +[dependencies] +"@tt" = {version = "1.6.1", url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/3_3_0-rc_2_22-01-2024_13:12:57_contracts.zip"} +"#; + + let target_config = define_config(true); + + write_to_config(&target_config, content); + + env::set_var("base_url", "https://api.soldeer.xyz"); + + let command = Subcommands::Install(Install { + dependency: None, + remote_url: None, + }); + + match run(command) { + Ok(_) => {} + Err(_) => { + clean_test_env(target_config.clone()); + assert_eq!("Invalid State", "") + } + } + + let path_dependency = DEPENDENCY_DIR.join("@tt-1.6.1"); + + assert!(Path::new(&path_dependency).exists()); + clean_test_env(target_config); + } + + #[test] + #[serial] + fn soldeer_update_success() { + let _ = remove_dir_all(DEPENDENCY_DIR.clone()); + let _ = remove_file(LOCK_FILE.clone()); + let content = r#" +# Full reference https://github.com/foundry-rs/foundry/tree/master/crates/config + +[profile.default] +script = "script" +solc = "0.8.26" +src = "src" +test = "test" +libs = ["dependencies"] + +[dependencies] +"@tt" = {version = "1.6.1", url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/3_3_0-rc_2_22-01-2024_13:12:57_contracts.zip"} +"#; + + let target_config = define_config(true); + + write_to_config(&target_config, content); + + env::set_var("base_url", "https://api.soldeer.xyz"); + + let command = Subcommands::Update(Update {}); + + match run(command) { + Ok(_) => {} + Err(_) => { + clean_test_env(target_config.clone()); + assert_eq!("Invalid State", "") + } + } + + let path_dependency = DEPENDENCY_DIR.join("@tt-1.6.1"); + + assert!(Path::new(&path_dependency).exists()); + clean_test_env(target_config); + } + + fn clean_test_env(target_config: PathBuf) { + let _ = remove_dir_all(DEPENDENCY_DIR.clone()); + let _ = remove_file(LOCK_FILE.clone()); + let _ = remove_file(&target_config); + let parent = target_config.parent(); + let lock = parent.unwrap().join("soldeer.lock"); + let _ = remove_file(lock); + } + + fn write_to_config(target_file: &PathBuf, content: &str) { + if target_file.exists() { + let _ = remove_file(target_file); + } + let mut file: std::fs::File = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(target_file) + .unwrap(); + if let Err(e) = write!(file, "{}", content) { + eprintln!("Couldn't write to the config file: {}", e); + } + } + + fn define_config(foundry: bool) -> PathBuf { + let s: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(7) + .map(char::from) + .collect(); + let mut target = format!("foundry{}.toml", s); + if !foundry { + target = format!("soldeer{}.toml", s); + } + + let path = env::current_dir().unwrap().join("test").join(target); + env::set_var("config_file", path.clone().to_str().unwrap()); + path + } +} diff --git a/src/versioning.rs b/src/versioning.rs index 4fd3533..550f6b9 100644 --- a/src/versioning.rs +++ b/src/versioning.rs @@ -298,6 +298,13 @@ async fn push_to_repo( cause: "Unauthorized. Please login".to_string(), }); } + StatusCode::PAYLOAD_TOO_LARGE => { + return Err(PushError { + name: (&dependency_name).to_string(), + version: (&dependency_version).to_string(), + cause: "The package is too big, it has over 50 MB".to_string(), + }); + } _ => { return Err(PushError { name: (&dependency_name).to_string(), diff --git a/tests/ci/foundry.rs b/tests/ci/foundry.rs index b2ed5f5..0c6e722 100644 --- a/tests/ci/foundry.rs +++ b/tests/ci/foundry.rs @@ -32,7 +32,7 @@ fn soldeer_install_valid_dependency() { let test_project = env::current_dir().unwrap().join("test_project"); clean_test_env(&test_project); let command = Subcommands::Install(Install { - dependency: "forge-std~1.8.2".to_string(), + dependency: Some("forge-std~1.8.2".to_string()), remote_url: None, }); @@ -109,7 +109,7 @@ contract Test { #[serial] fn soldeer_install_invalid_dependency() { let command = Subcommands::Install(Install { - dependency: "forge-std".to_string(), + dependency: Some("forge-std".to_string()), remote_url: None, });