pub mod pull_requests; pub mod release_notes; use app::constants::{APP_USER_AGENT, FLIGHTCORE_REPO_NAME, SECTION_ORDER}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use ts_rs::TS; #[derive(Serialize, Deserialize, Debug, Clone, TS)] #[ts(export)] pub struct Tag { name: String, } /// Wrapper type needed for frontend #[derive(Serialize, Deserialize, Debug, Clone, TS)] #[ts(export)] pub struct TagWrapper { label: String, value: Tag, } #[derive(Debug, Deserialize)] struct CommitInfo { sha: String, commit: Commit, } #[derive(Debug, Deserialize)] struct Commit { message: String, } #[derive(Debug, Deserialize)] struct Comparison { commits: Vec<CommitInfo>, } /// Get a list of tags on the FlightCore repo #[tauri::command] pub fn get_list_of_tags() -> Result<Vec<TagWrapper>, String> { // Set the repository name. // Create a `reqwest` client with a user agent. let client = reqwest::blocking::Client::builder() .user_agent(APP_USER_AGENT) .build() .unwrap(); // Fetch the list of tags for the repository as a `Vec<Tag>`. let tags_url = format!("https://api.github.com/repos/{}/tags", FLIGHTCORE_REPO_NAME); let tags: Vec<Tag> = client.get(tags_url).send().unwrap().json().unwrap(); // Map each `Tag` element to a `TagWrapper` element with the desired label and `Tag` value. let tag_wrappers: Vec<TagWrapper> = tags .into_iter() .map(|tag| TagWrapper { label: tag.name.clone(), value: tag, }) .collect(); Ok(tag_wrappers) } /// Use GitHub API to compare two tags of the same repo against each other and get the resulting changes #[tauri::command] pub fn compare_tags(first_tag: Tag, second_tag: Tag) -> Result<String, String> { // Fetch the list of commits between the two tags. // Create a `reqwest` client with a user agent. let client = reqwest::blocking::Client::builder() .user_agent(APP_USER_AGENT) .build() .unwrap(); let repo = "R2NorthstarTools/FlightCore"; let mut full_patch_notes = "".to_string(); let mut patch_notes: Vec<String> = [].to_vec(); println!("{}", repo); // let repo = "R2Northstar/NorthstarLauncher"; let comparison_url = format!( "https://api.github.com/repos/{}/compare/{}...{}", repo, first_tag.name, second_tag.name ); let comparison: Comparison = client.get(comparison_url).send().unwrap().json().unwrap(); let commits = comparison.commits; // Display the list of commits. println!( "Commits between {} and {}:", first_tag.name, second_tag.name ); // Iterate over all commits in the diff for commit in commits { println!( " * {} : {}", commit.sha, commit.commit.message.split('\n').next().unwrap() ); patch_notes.push( commit .commit .message .split('\n') .next() .unwrap() .to_string(), ); } full_patch_notes += &generate_flightcore_release_notes(patch_notes); Ok(full_patch_notes.to_string()) } /// Generate release notes in the format used for FlightCore fn generate_flightcore_release_notes(commits: Vec<String>) -> String { let grouped_commits = group_commits_by_type(commits); let mut release_notes = String::new(); // Go over commit types and generate notes for commit_type in SECTION_ORDER { if let Some(commit_list) = grouped_commits.get(commit_type) { if !commit_list.is_empty() { let section_title = match commit_type { "feat" => "**Features:**", "fix" => "**Bug Fixes:**", "docs" => "**Documentation:**", "style" => "**Styles:**", "refactor" => "**Code Refactoring:**", "build" => "**Build:**", "test" => "**Tests:**", "chore" => "**Chores:**", _ => "**Other:**", }; release_notes.push_str(&format!("{}\n", section_title)); for commit_message in commit_list { release_notes.push_str(&format!("- {}\n", commit_message)); } release_notes.push('\n'); } } } release_notes } /// Group semantic commit messages by type /// Commmit messages that are not formatted accordingly are marked as "other" fn group_commits_by_type(commits: Vec<String>) -> HashMap<String, Vec<String>> { let mut grouped_commits: HashMap<String, Vec<String>> = HashMap::new(); let mut other_commits: Vec<String> = vec![]; for commit in commits { let commit_parts: Vec<&str> = commit.splitn(2, ':').collect(); if commit_parts.len() == 2 { let commit_type = commit_parts[0].to_lowercase(); let commit_description = commit_parts[1].trim().to_string(); // Check if known commit type if SECTION_ORDER.contains(&commit_type.as_str()) { let commit_list = grouped_commits.entry(commit_type.to_string()).or_default(); commit_list.push(commit_description); } else { // otherwise add to list of "other" other_commits.push(commit.to_string()); } } else { other_commits.push(commit.to_string()); } } grouped_commits.insert("other".to_string(), other_commits); grouped_commits }