diff --git a/.gitignore b/.gitignore index 9c4c004..44911c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ .cuddle/ +.env diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..171caf5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.2.0] - 2023-08-01 + +### Added +- add docker setup +- refactor frontend configuration +- with all the way through +- with create pull request and release +- with gitea +- with prepend as well +- add cliff +- remove tokio +- with doctor +- with git client +- with fixes +- with conventional parse +- with tags command +- add semver +- can get commit chain +- with start of environment engine +- with gitea client +- fmt +- add gitea client stub +- add tests for git setup +- split headings into local and global +- rename to cuddle_please +- add config parsing +- with basic get dir +- add mkdocs +- add base + +### Other +- release command +- add cuddle.release to this repository +- add granular docker setup +- fix checks +- chck refactor commands +- move doctor command +- fmt +- rename release command +- move gitea command into its own file +- move config list +- move gitea out of the way +- move config building out of main execution loop +- move commands and misc out of main binary package +- fmt +- check hide commands +- move cuddle-please to cuddle-please release +- remove no-vcs option (moved to a later stage if github is someday adopted +- fix clippy warnings +- clippy fix +- fix +- cleanup diff --git a/crates/cuddle-please-commands/src/command.rs b/crates/cuddle-please-commands/src/command.rs index 5b5c3bb..4645d47 100644 --- a/crates/cuddle-please-commands/src/command.rs +++ b/crates/cuddle-please-commands/src/command.rs @@ -90,11 +90,11 @@ impl Command { pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> { let config = self.build_config(current_dir)?; let git_client = self.get_git(config.get_source())?; - let gitea_client = self.get_gitea_client(); + let gitea_client = self.get_gitea_client(&config); match &self.commands { Some(Commands::Release {}) => { - ReleaseCommandHandler::new(config, git_client, gitea_client) + ReleaseCommandHandler::new(self.ui, config, git_client, gitea_client) .execute(self.global.dry_run)?; } Some(Commands::Config { command }) => { @@ -139,11 +139,11 @@ impl Command { } } - fn get_gitea_client(&self) -> DynRemoteGitClient { + fn get_gitea_client(&self, config: &PleaseConfig) -> DynRemoteGitClient { match self.global.engine { cuddle_please_misc::RemoteEngine::Local => Box::new(LocalGitClient::new()), cuddle_please_misc::RemoteEngine::Gitea => Box::new(GiteaClient::new( - &self.config.api_url.clone().expect("api_url to be set"), + config.get_api_url(), self.global.token.as_deref(), )), } diff --git a/crates/cuddle-please-commands/src/release_command.rs b/crates/cuddle-please-commands/src/release_command.rs index d694c97..565ef9c 100644 --- a/crates/cuddle-please-commands/src/release_command.rs +++ b/crates/cuddle-please-commands/src/release_command.rs @@ -4,11 +4,12 @@ use ::semver::Version; use anyhow::Context; use cuddle_please_misc::{ - changelog_parser, get_most_significant_version, ChangeLogBuilder, DynRemoteGitClient, - NextVersion, VcsClient, + changelog_parser, get_most_significant_version, ChangeLogBuilder, Commit, DynRemoteGitClient, + DynUi, NextVersion, Tag, VcsClient, }; pub struct ReleaseCommandHandler { + ui: DynUi, config: PleaseConfig, git_client: VcsClient, gitea_client: DynRemoteGitClient, @@ -16,11 +17,13 @@ pub struct ReleaseCommandHandler { impl ReleaseCommandHandler { pub fn new( + ui: DynUi, config: PleaseConfig, git_client: VcsClient, gitea_client: DynRemoteGitClient, ) -> Self { Self { + ui, config, git_client, gitea_client, @@ -34,87 +37,90 @@ impl ReleaseCommandHandler { let branch = self.config.get_branch(); let source = self.config.get_source(); - // 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence - // 2a. if not existing use default. - // 2b. if not in a git repo abort. (unless --no-vcs is turned added) + self.check_git_remote_connection(owner, repository)?; - // 3. Create gitea client and do a health check - self.gitea_client - .connect(owner, repository) - .context("failed to connect to gitea repository")?; - // 4. Fetch git tags for the current repository - let tags = self.gitea_client.get_tags(owner, repository)?; + let significant_tag = self.get_most_significant_tag(owner, repository)?; - let significant_tag = get_most_significant_version(tags.iter().collect()); + let commits = + self.fetch_commits_since_last_tag(owner, repository, &significant_tag, branch)?; + let current_version = get_current_version(significant_tag); - // 5. Fetch git commits since last git tag - let commits = self.gitea_client.get_commits_since( - owner, - repository, - significant_tag.map(|st| st.commit.sha.as_str()), - branch, - )?; - - // 7. Create a versioning client - let current_version = significant_tag - .map(|st| Version::try_from(st).unwrap()) - .unwrap_or(Version::new(0, 1, 0)); - - // 8. Parse conventional commits and determine next version - - let commit_strs = commits - .iter() - .map(|c| c.commit.message.as_str()) - .collect::>(); - - if commit_strs.is_empty() { - tracing::info!("no commits to base release on"); + let conventional_commit_results = parse_conventional_commits(current_version, commits)?; + if conventional_commit_results.is_none() { + tracing::debug!("found no new commits, aborting early"); + self.ui + .write_str_ln("no new commits found, no release required"); return Ok(()); } + let (commit_strs, next_version) = conventional_commit_results.unwrap(); - let next_version = current_version.next(&commit_strs); + let (changelog_placement, changelog, changelog_last_changes) = + compose_changelog(&commit_strs, &next_version, source)?; - // Compose changelog - let builder = ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build(); - - let changelog_placement = source.join("CHANGELOG.md"); - - let changelog = match std::fs::read_to_string(&changelog_placement).ok() { - Some(existing_changelog) => builder.prepend(existing_changelog)?, - None => builder.generate()?, - }; - - let changelog_last_changes = changelog_parser::last_changes(&changelog)?; - - // 9b. check for release commit and release, if release exists continue - // 10b. create release if let Some(first_commit) = commit_strs.first() { if first_commit.contains("chore(release): ") { - if !dry_run { - self.gitea_client.create_release( - owner, - repository, - &next_version.to_string(), - &changelog_last_changes.unwrap(), - !next_version.pre.is_empty(), - )?; - } else { - tracing::debug!("creating release (dry_run)"); - } + self.create_release( + dry_run, + owner, + repository, + &next_version, + changelog_last_changes, + )?; return Ok(()); } } - // 9a. Create / Update Pr - // Create or update branch + self.create_pull_request( + changelog_placement, + changelog, + next_version, + dry_run, + owner, + repository, + changelog_last_changes, + branch, + )?; + + Ok(()) + } + + fn create_release( + &self, + dry_run: bool, + owner: &str, + repository: &str, + next_version: &Version, + changelog_last_changes: Option, + ) -> Result<(), anyhow::Error> { + Ok(if !dry_run { + self.gitea_client.create_release( + owner, + repository, + &next_version.to_string(), + &changelog_last_changes.unwrap(), + !next_version.pre.is_empty(), + )?; + } else { + tracing::debug!("creating release (dry_run)"); + }) + } + + fn create_pull_request( + &self, + changelog_placement: std::path::PathBuf, + changelog: String, + next_version: Version, + dry_run: bool, + owner: &str, + repository: &str, + changelog_last_changes: Option, + branch: &str, + ) -> Result<(), anyhow::Error> { self.git_client.checkout_branch()?; - std::fs::write(changelog_placement, changelog.as_bytes())?; - self.git_client .commit_and_push(next_version.to_string(), dry_run)?; - let _pr_number = match self.gitea_client.get_pull_request(owner, repository)? { Some(existing_pr) => { if !dry_run { @@ -145,7 +151,84 @@ impl ReleaseCommandHandler { } } }; + Ok(()) + } + fn fetch_commits_since_last_tag( + &self, + owner: &str, + repository: &str, + significant_tag: &Option, + branch: &str, + ) -> Result, anyhow::Error> { + let commits = self.gitea_client.get_commits_since( + owner, + repository, + significant_tag.as_ref().map(|st| st.commit.sha.as_str()), + branch, + )?; + Ok(commits) + } + + fn get_most_significant_tag( + &self, + owner: &str, + repository: &str, + ) -> Result, anyhow::Error> { + let tags = self.gitea_client.get_tags(owner, repository)?; + let significant_tag = get_most_significant_version(tags.iter().collect()); + Ok(significant_tag.map(|t| t.clone())) + } + + fn check_git_remote_connection( + &self, + owner: &str, + repository: &str, + ) -> Result<(), anyhow::Error> { + self.gitea_client + .connect(owner, repository) + .context("failed to connect to gitea repository")?; Ok(()) } } + +fn compose_changelog( + commit_strs: &Vec, + next_version: &Version, + source: &std::path::PathBuf, +) -> Result<(std::path::PathBuf, String, Option), anyhow::Error> { + let builder = ChangeLogBuilder::new(commit_strs, next_version.to_string()).build(); + let changelog_placement = source.join("CHANGELOG.md"); + let changelog = match std::fs::read_to_string(&changelog_placement).ok() { + Some(existing_changelog) => builder.prepend(existing_changelog)?, + None => builder.generate()?, + }; + let changelog_last_changes = changelog_parser::last_changes(&changelog)?; + Ok((changelog_placement, changelog, changelog_last_changes)) +} + +fn parse_conventional_commits( + current_version: Version, + commits: Vec, +) -> anyhow::Result, Version)>> { + let commit_strs = commits + .iter() + .map(|c| c.commit.message.clone()) + .collect::>(); + + if commit_strs.is_empty() { + tracing::info!("no commits to base release on"); + return Ok(None); + } + + let next_version = current_version.next(&commit_strs); + + Ok(Some((commit_strs, next_version))) +} + +fn get_current_version(significant_tag: Option) -> Version { + let current_version = significant_tag + .map(|st| Version::try_from(st).unwrap()) + .unwrap_or(Version::new(0, 0, 0)); + current_version +} diff --git a/crates/cuddle-please-misc/src/lib.rs b/crates/cuddle-please-misc/src/lib.rs index a7a4fc6..e7f35be 100644 --- a/crates/cuddle-please-misc/src/lib.rs +++ b/crates/cuddle-please-misc/src/lib.rs @@ -9,7 +9,7 @@ mod versioning; pub use args::{GlobalArgs, RemoteEngine, StdinFn}; pub use cliff::{changelog_parser, ChangeLogBuilder}; pub use git_client::VcsClient; -pub use gitea_client::{DynRemoteGitClient, GiteaClient, RemoteGitEngine}; +pub use gitea_client::{Commit, DynRemoteGitClient, GiteaClient, RemoteGitEngine, Tag}; pub use local_git_client::LocalGitClient; pub use ui::{ConsoleUi, DynUi, Ui}; pub use versioning::{next_version::NextVersion, semver::get_most_significant_version}; diff --git a/cuddle.yaml b/cuddle.yaml index dd76cb4..646e08b 100644 --- a/cuddle.yaml +++ b/cuddle.yaml @@ -7,6 +7,14 @@ vars: registry: kasperhermansen mkdocs_image: "squidfunk/mkdocs-material:9.1" +please: + project: + owner: kjuulh + repository: cuddle-please + branch: main + settings: + api_url: https://git.front.kjuulh.io + scripts: "mkdocs:new": type: shell