feat: with proper semver releaser
This commit is contained in:
parent
fc1ec96eab
commit
03cd8ba20f
227
src/gitea.rs
227
src/gitea.rs
@ -1,14 +1,17 @@
|
|||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct GitCommit {
|
pub struct GitCommit {
|
||||||
commit: GitCommitDetails,
|
pub commit: GitCommitDetails,
|
||||||
|
pub sha: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct GitCommitDetails {
|
pub struct GitCommitDetails {
|
||||||
message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_pull_request_commits(
|
pub async fn get_pull_request_commits(
|
||||||
@ -47,6 +50,226 @@ pub async fn get_pull_request_commits(
|
|||||||
Ok(commit_titles)
|
Ok(commit_titles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ReleaseRequest {
|
||||||
|
pub tag_name: String,
|
||||||
|
pub target_commitish: String,
|
||||||
|
pub name: String,
|
||||||
|
pub body: String,
|
||||||
|
pub draft: bool,
|
||||||
|
pub prerelease: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ReleaseResponse {
|
||||||
|
pub id: u64,
|
||||||
|
pub url: String,
|
||||||
|
// Add other fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_gitea_release(
|
||||||
|
base_url: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
access_token: &str,
|
||||||
|
release_request: &ReleaseRequest,
|
||||||
|
) -> eyre::Result<ReleaseResponse> {
|
||||||
|
let client = Client::new();
|
||||||
|
let releases_url = format!("{}/api/v1/repos/{}/{}/releases", base_url, owner, repo);
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&releases_url)
|
||||||
|
.bearer_auth(access_token)
|
||||||
|
.json(release_request)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
eyre::bail!("failed to handle request: {}", response.text().await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.json::<ReleaseResponse>().await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct Release {
|
||||||
|
pub id: u64,
|
||||||
|
pub tag_name: String,
|
||||||
|
pub draft: bool,
|
||||||
|
pub prerelease: bool, // Add other fields as needed
|
||||||
|
pub target_commitish: String,
|
||||||
|
pub name: String,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_newest_release(
|
||||||
|
base_url: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
personal_access_token: &str,
|
||||||
|
) -> eyre::Result<Release> {
|
||||||
|
let client = Client::new();
|
||||||
|
let releases_url = format!("{}/api/v1/repos/{}/{}/releases", base_url, owner, repo);
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.get(&releases_url)
|
||||||
|
.bearer_auth(personal_access_token)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
eyre::bail!("failed to handle request: {}", response.text().await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.json::<Vec<Release>>().await?;
|
||||||
|
|
||||||
|
match response.first() {
|
||||||
|
Some(release) => Ok(release.clone()),
|
||||||
|
None => Err(eyre::anyhow!("No releases found")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_newest_release_draft(
|
||||||
|
gitea_base_url: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
personal_access_token: &str,
|
||||||
|
) -> eyre::Result<(bool, Release)> {
|
||||||
|
let release = get_newest_release(gitea_base_url, owner, repo, personal_access_token).await?;
|
||||||
|
Ok((release.draft, release))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct UpdateReleaseRequest {
|
||||||
|
pub tag_name: Option<String>,
|
||||||
|
pub target_commitish: Option<String>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub body: Option<String>,
|
||||||
|
pub draft: Option<bool>,
|
||||||
|
pub prerelease: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn modify_release(
|
||||||
|
base_url: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
token: &str,
|
||||||
|
release_id: u64,
|
||||||
|
update_request: &UpdateReleaseRequest,
|
||||||
|
) -> eyre::Result<Release> {
|
||||||
|
tracing::info!(
|
||||||
|
base_url = base_url,
|
||||||
|
owner = owner,
|
||||||
|
repo = repo,
|
||||||
|
release_id = release_id,
|
||||||
|
"modifying release"
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
let release_url = format!(
|
||||||
|
"{}/api/v1/repos/{}/{}/releases/{}",
|
||||||
|
base_url, owner, repo, release_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.patch(&release_url)
|
||||||
|
.bearer_auth(token)
|
||||||
|
.json(&update_request)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
eyre::bail!("failed to handle request: {}", response.text().await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.json::<Release>().await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Commit {
|
||||||
|
pub sha: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Tag {
|
||||||
|
pub name: String,
|
||||||
|
pub commit: Commit, // Add other fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_highest_semver_from_tags(
|
||||||
|
base_url: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
token: &str,
|
||||||
|
) -> eyre::Result<(Version, Tag)> {
|
||||||
|
let client = Client::new();
|
||||||
|
let tags_url = format!("{}/api/v1/repos/{}/{}/tags", base_url, owner, repo);
|
||||||
|
|
||||||
|
let response = client.get(&tags_url).bearer_auth(token).send().await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
eyre::bail!("failed to handle request: {}", response.text().await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.json::<Vec<Tag>>().await?;
|
||||||
|
|
||||||
|
let highest_version = response
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|tag| {
|
||||||
|
Version::parse(&tag.name.replace("v", ""))
|
||||||
|
.and_then(|v| Ok((v, tag)))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.max_by(|(x, _), (y, _)| x.cmp(y));
|
||||||
|
|
||||||
|
match highest_version {
|
||||||
|
Some(version) => Ok(version),
|
||||||
|
None => Err(eyre::anyhow!("No valid SemVer tags found",)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_commits_from_commit_to_newest(
|
||||||
|
base_url: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
token: &str,
|
||||||
|
branch: &str,
|
||||||
|
start_commit_sha: &str,
|
||||||
|
) -> eyre::Result<Vec<GitCommit>> {
|
||||||
|
let client = Client::new();
|
||||||
|
let commits_url = format!(
|
||||||
|
"{}/api/v1/repos/{}/{}/commits?sha={}",
|
||||||
|
base_url, owner, repo, branch
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = client.get(&commits_url).bearer_auth(token).send().await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
eyre::bail!("failed to handle request: {}", response.text().await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
//tracing::info!(request = response.text().await?, "test");
|
||||||
|
|
||||||
|
let mut response = response.json::<Vec<GitCommit>>().await?;
|
||||||
|
//let mut response: Vec<GitCommit> = Vec::new();
|
||||||
|
|
||||||
|
let index_of_start_commit = response
|
||||||
|
.iter()
|
||||||
|
.position(|commit| commit.sha == start_commit_sha);
|
||||||
|
|
||||||
|
match index_of_start_commit {
|
||||||
|
Some(index) => {
|
||||||
|
response.truncate(index);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
None => Err(eyre::anyhow!("Start commit not found",)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use tracing_test::traced_test;
|
use tracing_test::traced_test;
|
||||||
|
141
src/main.rs
141
src/main.rs
@ -5,6 +5,8 @@ mod validate_pr;
|
|||||||
use clap::Command;
|
use clap::Command;
|
||||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
||||||
|
|
||||||
|
use self::gitea::{get_highest_semver_from_tags, ReleaseRequest, UpdateReleaseRequest};
|
||||||
|
|
||||||
const VERSION: &'static str = "<dev>";
|
const VERSION: &'static str = "<dev>";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -14,15 +16,16 @@ async fn main() -> eyre::Result<()> {
|
|||||||
}
|
}
|
||||||
color_eyre::install().unwrap();
|
color_eyre::install().unwrap();
|
||||||
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.pretty()
|
|
||||||
.with_env_filter(EnvFilter::default().add_directive(LevelFilter::INFO.into()))
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let matches = clap::Command::new("releaser")
|
let matches = clap::Command::new("releaser")
|
||||||
.version(VERSION)
|
.version(VERSION)
|
||||||
.author("kjuulh <contact@kjuulh.io>")
|
.author("kjuulh <contact@kjuulh.io>")
|
||||||
.about("A CLI app for releasing software and managing versions")
|
.about("A CLI app for releasing software and managing versions")
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("quiet")
|
||||||
|
.long("quiet")
|
||||||
|
.short('q')
|
||||||
|
.action(clap::ArgAction::SetTrue),
|
||||||
|
)
|
||||||
.subcommand(Command::new("release").about("Release the software"))
|
.subcommand(Command::new("release").about("Release the software"))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("validate").about("Validate the semantic versioning of the software"),
|
Command::new("validate").about("Validate the semantic versioning of the software"),
|
||||||
@ -44,9 +47,44 @@ async fn main() -> eyre::Result<()> {
|
|||||||
.required(true),
|
.required(true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(Command::new("bump").about("Bump the semantic versioning in git tags"))
|
.subcommand(
|
||||||
|
Command::new("bump")
|
||||||
|
.about("Bump the semantic versioning in git tags")
|
||||||
|
.arg(clap::Arg::new("owner").long("owner").required(true))
|
||||||
|
.arg(clap::Arg::new("repo").long("repo").required(true))
|
||||||
|
.arg(clap::Arg::new("branch").long("branch").required(true)),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("release:gitea")
|
||||||
|
.about("Create a release on gitea")
|
||||||
|
.arg(clap::Arg::new("owner").long("owner").required(true))
|
||||||
|
.arg(clap::Arg::new("repo").long("repo").required(true))
|
||||||
|
.arg(clap::Arg::new("version").long("version").required(true))
|
||||||
|
.arg(clap::Arg::new("branch").long("branch").required(true))
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("generate-changelog")
|
||||||
|
.long("generate-changelog")
|
||||||
|
.action(clap::ArgAction::SetTrue),
|
||||||
|
),
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
|
let quiet = matches.get_one::<bool>("quiet");
|
||||||
|
match quiet {
|
||||||
|
Some(true) => {}
|
||||||
|
_ => {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.pretty()
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into()),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let base_url = &std::env::var("GITEA_BASE_URL").unwrap();
|
||||||
|
let token = &std::env::var("GITEA_ACCESS_TOKEN").unwrap();
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
Some(("release", sub_matches)) => {}
|
Some(("release", sub_matches)) => {}
|
||||||
Some(("validate", sub_matches)) => {}
|
Some(("validate", sub_matches)) => {}
|
||||||
@ -60,12 +98,99 @@ async fn main() -> eyre::Result<()> {
|
|||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
Some(version) => {
|
Some(version) => {
|
||||||
tracing::info!(version = version, "bumping version")
|
tracing::info!(version = version, "bumping version");
|
||||||
|
|
||||||
|
print!("{}\n", version)
|
||||||
}
|
}
|
||||||
None => tracing::info!(version = current_version, "no version bump required!"),
|
None => tracing::info!(version = current_version, "no version bump required!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(("bump", sub_matches)) => {}
|
Some(("bump", sub_matches)) => {
|
||||||
|
let owner = sub_matches.get_one::<String>("owner").unwrap();
|
||||||
|
let repo = sub_matches.get_one::<String>("repo").unwrap();
|
||||||
|
let branch = sub_matches.get_one::<String>("branch").unwrap();
|
||||||
|
let default_version = "0.0.0";
|
||||||
|
|
||||||
|
let version = match get_highest_semver_from_tags(base_url, owner, repo, token).await {
|
||||||
|
Ok((version, tag)) => {
|
||||||
|
tracing::debug!(version = version.to_string(), "got tag from repo");
|
||||||
|
|
||||||
|
match validate_pr::validate_commits(
|
||||||
|
&owner,
|
||||||
|
&repo,
|
||||||
|
&tag.commit.sha,
|
||||||
|
branch,
|
||||||
|
&version.to_string(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(version) => {
|
||||||
|
tracing::info!(version = version, "bumping version");
|
||||||
|
|
||||||
|
print!("{}\n", version)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tracing::info!(version = default_version, "no version bump required!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version.to_string()
|
||||||
|
}
|
||||||
|
_ => default_version.to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(("release:gitea", sub_matches)) => {
|
||||||
|
let default_generate_changelog = false;
|
||||||
|
let owner = sub_matches.get_one::<String>("owner").unwrap();
|
||||||
|
let repo = sub_matches.get_one::<String>("repo").unwrap();
|
||||||
|
let version = sub_matches.get_one::<String>("version").unwrap();
|
||||||
|
let branch = sub_matches.get_one::<String>("branch").unwrap();
|
||||||
|
let generate_changelog = sub_matches
|
||||||
|
.get_one::<bool>("generate-changelog")
|
||||||
|
.unwrap_or(&default_generate_changelog);
|
||||||
|
|
||||||
|
match crate::gitea::is_newest_release_draft(base_url, owner, repo, token).await {
|
||||||
|
Ok((true, release)) => {
|
||||||
|
let resp = crate::gitea::modify_release(
|
||||||
|
base_url,
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
token,
|
||||||
|
release.id,
|
||||||
|
&UpdateReleaseRequest {
|
||||||
|
body: None,
|
||||||
|
draft: Some(release.draft),
|
||||||
|
tag_name: Some(version.clone()),
|
||||||
|
name: Some(version.clone()),
|
||||||
|
prerelease: None,
|
||||||
|
target_commitish: Some(branch.clone()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::info!("updated release")
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let resp = crate::gitea::create_gitea_release(
|
||||||
|
base_url,
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
token,
|
||||||
|
&ReleaseRequest {
|
||||||
|
tag_name: version.clone(),
|
||||||
|
target_commitish: branch.clone(),
|
||||||
|
name: version.clone(),
|
||||||
|
body: "".into(),
|
||||||
|
draft: true,
|
||||||
|
prerelease: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::info!(url = resp.url, "created release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use crate::gitea;
|
||||||
|
|
||||||
pub async fn validate_pr(
|
pub async fn validate_pr(
|
||||||
owner: &str,
|
owner: &str,
|
||||||
repo: &str,
|
repo: &str,
|
||||||
@ -16,3 +18,34 @@ pub async fn validate_pr(
|
|||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn validate_commits(
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
start_commit: &str,
|
||||||
|
branch: &str,
|
||||||
|
current_version: &str,
|
||||||
|
) -> eyre::Result<Option<String>> {
|
||||||
|
let base_url = &std::env::var("GITEA_BASE_URL").unwrap();
|
||||||
|
let token = &std::env::var("GITEA_ACCESS_TOKEN").unwrap();
|
||||||
|
|
||||||
|
let commits = crate::gitea::get_commits_from_commit_to_newest(
|
||||||
|
base_url,
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
token,
|
||||||
|
branch,
|
||||||
|
start_commit,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let commit_titles = commits
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.commit.message)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
match crate::semantic::get_most_significant_bump(&commit_titles) {
|
||||||
|
Some(bump) => Ok(Some(crate::semantic::bump_semver(current_version, bump)?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user