aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src-tauri/bindings/Tag.ts3
-rw-r--r--src-tauri/bindings/TagWrapper.ts4
-rw-r--r--src-tauri/src/constants.rs8
-rw-r--r--src-tauri/src/github/mod.rs176
-rw-r--r--src-tauri/src/main.rs3
-rw-r--r--src-vue/src/views/DeveloperView.vue95
6 files changed, 289 insertions, 0 deletions
diff --git a/src-tauri/bindings/Tag.ts b/src-tauri/bindings/Tag.ts
new file mode 100644
index 00000000..adbbff33
--- /dev/null
+++ b/src-tauri/bindings/Tag.ts
@@ -0,0 +1,3 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export interface Tag { name: string, } \ No newline at end of file
diff --git a/src-tauri/bindings/TagWrapper.ts b/src-tauri/bindings/TagWrapper.ts
new file mode 100644
index 00000000..f9f56a51
--- /dev/null
+++ b/src-tauri/bindings/TagWrapper.ts
@@ -0,0 +1,4 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { Tag } from "./Tag";
+
+export interface TagWrapper { label: string, value: Tag, } \ No newline at end of file
diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs
index 7b13206a..0f4e9ab4 100644
--- a/src-tauri/src/constants.rs
+++ b/src-tauri/src/constants.rs
@@ -29,6 +29,11 @@ pub const BLACKLISTED_MODS: [&str; 3] = [
// Titanfall2 game IDs on Origin/EA-App
pub const TITANFALL2_ORIGIN_IDS: [&str; 2] = ["Origin.OFR.50.0001452", "Origin.OFR.50.0001456"];
+// Order in which the sections for release notes should be displayed
+pub const SECTION_ORDER: [&str; 9] = [
+ "feat", "fix", "docs", "style", "refactor", "build", "test", "chore", "other",
+];
+
// GitHub API endpoints for launcher/mods PRs
pub const PULLS_API_ENDPOINT_LAUNCHER: &str =
"https://api.github.com/repos/R2Northstar/NorthstarLauncher/pulls";
@@ -37,3 +42,6 @@ pub const PULLS_API_ENDPOINT_MODS: &str =
// Statistics (players and servers counts) refresh delay
pub const REFRESH_DELAY: Duration = Duration::from_secs(5 * 60);
+
+// Flightcore repo name and org name on GitHub
+pub const FLIGHTCORE_REPO_NAME: &str = "R2NorthstarTools/FlightCore";
diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs
index 942f0db2..87ea629c 100644
--- a/src-tauri/src/github/mod.rs
+++ b/src-tauri/src/github/mod.rs
@@ -1,2 +1,178 @@
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(format!(
+ "{}",
+ commit.commit.message.split('\n').next().unwrap()
+ ));
+ }
+
+ 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_str("\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
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index dfceece7..f4712cd4 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -21,6 +21,7 @@ use github::pull_requests::{
use github::release_notes::{
check_is_flightcore_outdated, get_newest_flightcore_version, get_northstar_release_notes,
};
+use github::{compare_tags, get_list_of_tags};
mod repair_and_verify;
use repair_and_verify::{
@@ -137,6 +138,8 @@ fn main() {
delete_thunderstore_mod,
open_repair_window,
query_thunderstore_packages_api,
+ get_list_of_tags,
+ compare_tags,
get_pull_requests_wrapper,
apply_launcher_pr,
apply_mods_pr,
diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue
index 52117b8d..bca473fb 100644
--- a/src-vue/src/views/DeveloperView.vue
+++ b/src-vue/src/views/DeveloperView.vue
@@ -5,6 +5,38 @@
This page is designed for developers. Some of the buttons here can break your Northstar install if you do not know what you're doing!
</el-alert>
+ <el-button type="primary" @click="getTags">
+ Get tags
+ </el-button>
+
+ <el-select v-model="firstTag" class="m-2" placeholder="First tag">
+ <el-option
+ v-for="item in ns_release_tags"
+ :key="item.value"
+ :label="item.label"
+ :value="item"
+ />
+ </el-select>
+ <el-select v-model="secondTag" class="m-2" placeholder="Second tag">
+ <el-option
+ v-for="item in ns_release_tags"
+ :key="item.value"
+ :label="item.label"
+ :value="item"
+ />
+ </el-select>
+
+ <el-button type="primary" @click="compareTags">
+ Compare Tags
+ </el-button>
+
+ <el-input
+ v-model="release_notes_text"
+ type="textarea"
+ :rows="5"
+ placeholder="Output"
+ />
+
<h3>Basic:</h3>
<el-button type="primary" @click="disableDevMode">
@@ -53,6 +85,7 @@ import { defineComponent } from "vue";
import { invoke } from "@tauri-apps/api";
import { ElNotification } from "element-plus";
import { GameInstall } from "../utils/GameInstall";
+import { TagWrapper } from "../../../src-tauri/bindings/TagWrapper";
import PullRequestsSelector from "../components/PullRequestsSelector.vue";
export default defineComponent({
@@ -63,8 +96,30 @@ export default defineComponent({
data() {
return {
mod_to_install_field_string : "",
+ release_notes_text : "",
+ first_tag: { label: '', value: {name: ''} },
+ second_tag: { label: '', value: {name: ''} },
+ ns_release_tags: [] as TagWrapper[],
}
},
+ computed: {
+ firstTag: {
+ get(): TagWrapper {
+ return this.first_tag;
+ },
+ set(value: TagWrapper) {
+ this.first_tag = value;
+ }
+ },
+ secondTag: {
+ get(): TagWrapper {
+ return this.second_tag;
+ },
+ set(value: TagWrapper) {
+ this.second_tag = value;
+ }
+ },
+ },
methods: {
disableDevMode() {
this.$store.commit('toggleDeveloperMode');
@@ -152,6 +207,46 @@ export default defineComponent({
});
});
},
+ async getTags() {
+ await invoke<TagWrapper[]>("get_list_of_tags")
+ .then((message) => {
+ this.ns_release_tags = message;
+ ElNotification({
+ title: "Done",
+ message: "Fetched tags",
+ type: 'success',
+ position: 'bottom-right'
+ });
+ })
+ .catch((error) => {
+ ElNotification({
+ title: 'Error',
+ message: error,
+ type: 'error',
+ position: 'bottom-right'
+ });
+ });
+ },
+ async compareTags() {
+ await invoke<string>("compare_tags", {firstTag: this.firstTag.value, secondTag: this.secondTag.value})
+ .then((message) => {
+ this.release_notes_text = message;
+ ElNotification({
+ title: "Done",
+ message: "Generated release notes",
+ type: 'success',
+ position: 'bottom-right'
+ });
+ })
+ .catch((error) => {
+ ElNotification({
+ title: 'Error',
+ message: error,
+ type: 'error',
+ position: 'bottom-right'
+ });
+ });
+ },
}
});
</script>