diff --git a/src/changes.rs b/src/changes.rs new file mode 100644 index 00000000..4cf80c00 --- /dev/null +++ b/src/changes.rs @@ -0,0 +1,432 @@ +#![allow(clippy::all)] + +//! # Changes +//! +//! This module is responsible for managing the changes in the monorepo. +//! The changes are stored in a `.changes.json` file in the root of the project. +//! +//! # Example +//! { +//! "message": "chore(release): release new version", +//! "changes": { +//! "BRANCH-NAME": [{ +//! "package": "xxx", +//! "releaseAs": "patch", +//! "deploy": ["int"] +//! }], +//! } +//!} +use serde::{Deserialize, Serialize}; +use std::io::BufWriter; +use std::{ + collections::BTreeMap, + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; + +use super::git::git_current_branch; +use super::paths::get_project_root_path; + +type ChangesData = BTreeMap>; + +#[cfg(not(feature = "napi"))] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct ChangesOptions { + pub message: Option, +} + +#[cfg(feature = "napi")] +#[napi(object)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct ChangesOptions { + pub message: Option, +} + +#[cfg(not(feature = "napi"))] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct ChangesFileData { + pub message: Option, + pub changes: ChangesData, +} + +#[cfg(feature = "napi")] +#[napi(object)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct ChangesFileData { + pub message: Option, + pub changes: ChangesData, +} + +#[cfg(not(feature = "napi"))] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct Changes { + pub changes: ChangesData, +} + +#[cfg(feature = "napi")] +#[napi(object)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct Changes { + pub changes: ChangesData, +} + +#[cfg(not(feature = "napi"))] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct Change { + pub package: String, + pub release_as: String, + pub deploy: Vec, +} + +#[cfg(feature = "napi")] +#[napi(object)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct Change { + pub package: String, + pub release_as: String, + pub deploy: Vec, +} + +pub fn init_changes( + cwd: Option, + change_options: &Option, +) -> ChangesFileData { + let ref root = match cwd { + Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(), + None => get_project_root_path(None).unwrap(), + }; + + let root_path = Path::new(root); + let ref changes_path = root_path.join(String::from(".changes.json")); + + if changes_path.exists() { + let changes_file = File::open(changes_path).unwrap(); + let changes_reader = BufReader::new(changes_file); + + let changes: ChangesFileData = serde_json::from_reader(changes_reader).unwrap(); + return changes; + } else { + let message = match &change_options { + Some(options) => match &options.message { + Some(msg) => msg.to_string(), + None => String::from("chore(release): release new version"), + }, + None => String::from("chore(release): release new version"), + }; + + let changes = ChangesFileData { + message: Some(message), + changes: ChangesData::new(), + }; + + let changes_file = File::create(changes_path).unwrap(); + let changes_writer = BufWriter::new(changes_file); + + serde_json::to_writer(changes_writer, &changes).unwrap(); + + return changes; + } +} + +pub fn add_change(change: &Change, cwd: Option) -> bool { + let ref root = match cwd { + Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(), + None => get_project_root_path(None).unwrap(), + }; + + let root_path = Path::new(root); + let ref changes_path = root_path.join(String::from(".changes.json")); + + if changes_path.exists() { + let changes_file = File::open(changes_path).unwrap(); + let changes_reader = BufReader::new(changes_file); + + let mut changes: ChangesFileData = serde_json::from_reader(changes_reader).unwrap(); + + let current_branch = git_current_branch(Some(root.to_string())); + + let branch = match current_branch { + Some(branch) => branch, + None => String::from("main"), + }; + + if changes.changes.contains_key(&branch) { + let branch_changes = changes.changes.get_mut(&branch).unwrap(); + branch_changes.push(Change { + package: change.package.to_string(), + release_as: change.release_as.to_string(), + deploy: change.deploy.to_vec(), + }); + } else { + changes.changes.insert( + branch, + vec![Change { + package: change.package.to_string(), + release_as: change.release_as.to_string(), + deploy: change.deploy.to_vec(), + }], + ); + } + + let changes_file = File::create(changes_path).unwrap(); + let changes_writer = BufWriter::new(changes_file); + + serde_json::to_writer(changes_writer, &changes).unwrap(); + + return true; + } + + false +} + +pub fn remove_change(branch_name: String, cwd: Option) -> bool { + let ref root = match cwd { + Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(), + None => get_project_root_path(None).unwrap(), + }; + + let root_path = Path::new(root); + let ref changes_path = root_path.join(String::from(".changes.json")); + + if changes_path.exists() { + let changes_file = File::open(changes_path).unwrap(); + let changes_reader = BufReader::new(changes_file); + + let mut changes: ChangesFileData = serde_json::from_reader(changes_reader).unwrap(); + + if changes.changes.contains_key(&branch_name) { + changes.changes.remove(&branch_name); + + let changes_file = File::create(changes_path).unwrap(); + let changes_writer = BufWriter::new(changes_file); + + serde_json::to_writer(changes_writer, &changes).unwrap(); + + return true; + } + } + + false +} + +pub fn get_changes(cwd: Option) -> ChangesData { + let ref root = match cwd { + Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(), + None => get_project_root_path(None).unwrap(), + }; + + let root_path = Path::new(root); + let ref changes_path = root_path.join(String::from(".changes.json")); + + if changes_path.exists() { + let changes_file = File::open(changes_path).unwrap(); + let changes_reader = BufReader::new(changes_file); + + let changes: ChangesFileData = serde_json::from_reader(changes_reader).unwrap(); + return changes.changes; + } + + ChangesData::new() +} + +pub fn get_change(branch: String, cwd: Option) -> Vec { + let ref root = match cwd { + Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(), + None => get_project_root_path(None).unwrap(), + }; + + let root_path = Path::new(root); + let ref changes_path = root_path.join(String::from(".changes.json")); + + if changes_path.exists() { + let changes_file = File::open(changes_path).unwrap(); + let changes_reader = BufReader::new(changes_file); + + let changes: ChangesFileData = serde_json::from_reader(changes_reader).unwrap(); + + if changes.changes.contains_key(&branch) { + return changes.changes.get(&branch).unwrap().to_vec(); + } else { + return vec![]; + } + } + + vec![] +} + +pub fn change_exist(branch: String, cwd: Option) -> bool { + let ref root = match cwd { + Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(), + None => get_project_root_path(None).unwrap(), + }; + + let root_path = Path::new(root); + let ref changes_path = root_path.join(String::from(".changes.json")); + + if changes_path.exists() { + let changes_file = File::open(changes_path).unwrap(); + let changes_reader = BufReader::new(changes_file); + + let changes: ChangesFileData = serde_json::from_reader(changes_reader).unwrap(); + + return changes.changes.contains_key(&branch); + } + + false +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::manager::PackageManager; + use crate::paths::get_project_root_path; + use crate::utils::create_test_monorepo; + use std::fs::remove_dir_all; + + #[test] + fn test_init_changes() -> Result<(), Box> { + let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?; + let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())); + + let ref root = project_root.unwrap().to_string(); + + let changes_data_file = init_changes(Some(root.to_string()), &None); + let ref changes_path = monorepo_dir.join(String::from(".changes.json")); + + assert_eq!(changes_data_file.message.is_some(), true); + assert_eq!(changes_path.is_file(), true); + remove_dir_all(&monorepo_dir)?; + Ok(()) + } + + #[test] + fn test_add_change() -> Result<(), Box> { + let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?; + let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())); + + let ref root = project_root.unwrap().to_string(); + + let change = Change { + package: String::from("test-package"), + release_as: String::from("1.0.0"), + deploy: vec![String::from("production")], + }; + + init_changes(Some(root.to_string()), &None); + + let ref changes_path = monorepo_dir.join(String::from(".changes.json")); + let result = add_change(&change, Some(root.to_string())); + + assert_eq!(result, true); + assert_eq!(changes_path.is_file(), true); + remove_dir_all(&monorepo_dir)?; + Ok(()) + } + + #[test] + fn test_remove_change() -> Result<(), Box> { + let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?; + let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())); + + let ref root = project_root.unwrap().to_string(); + + let change = Change { + package: String::from("test-package"), + release_as: String::from("1.0.0"), + deploy: vec![String::from("production")], + }; + + init_changes(Some(root.to_string()), &None); + + let ref changes_path = monorepo_dir.join(String::from(".changes.json")); + add_change(&change, Some(root.to_string())); + + let result = remove_change(String::from("main"), Some(root.to_string())); + + assert_eq!(result, true); + assert_eq!(changes_path.is_file(), true); + remove_dir_all(&monorepo_dir)?; + Ok(()) + } + + #[test] + fn test_get_changes() -> Result<(), Box> { + let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?; + let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())); + + let ref root = project_root.unwrap().to_string(); + + let change = Change { + package: String::from("test-package"), + release_as: String::from("1.0.0"), + deploy: vec![String::from("production")], + }; + + init_changes(Some(root.to_string()), &None); + + let ref changes_path = monorepo_dir.join(String::from(".changes.json")); + add_change(&change, Some(root.to_string())); + + let changes = get_changes(Some(root.to_string())); + + assert_eq!(changes.contains_key(&String::from("main")), true); + assert_eq!(changes.get(&String::from("main")).unwrap().len(), 1); + assert_eq!(changes_path.is_file(), true); + remove_dir_all(&monorepo_dir)?; + Ok(()) + } + + #[test] + fn test_get_change() -> Result<(), Box> { + let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?; + let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())); + + let ref root = project_root.unwrap().to_string(); + + let change = Change { + package: String::from("test-package"), + release_as: String::from("1.0.0"), + deploy: vec![String::from("production")], + }; + + init_changes(Some(root.to_string()), &None); + + let ref changes_path = monorepo_dir.join(String::from(".changes.json")); + add_change(&change, Some(root.to_string())); + + let changes = get_change(String::from("main"), Some(root.to_string())); + + assert_eq!(changes.len(), 1); + assert_eq!(changes_path.is_file(), true); + remove_dir_all(&monorepo_dir)?; + Ok(()) + } + + #[test] + fn test_change_exist() -> Result<(), Box> { + let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?; + let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())); + + let ref root = project_root.unwrap().to_string(); + + let change = Change { + package: String::from("test-package"), + release_as: String::from("1.0.0"), + deploy: vec![String::from("production")], + }; + + init_changes(Some(root.to_string()), &None); + + let ref changes_path = monorepo_dir.join(String::from(".changes.json")); + add_change(&change, Some(root.to_string())); + + let result = change_exist(String::from("main"), Some(root.to_string())); + + assert_eq!(result, true); + assert_eq!(changes_path.is_file(), true); + remove_dir_all(&monorepo_dir)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index ef926201..110e6bed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,3 +20,5 @@ pub mod packages; pub mod conventional; pub mod bumps; + +pub mod changes;