holochain_release_util/
lib.rs1use crate::prepare_release::{
2 generate_changelog, get_next_version, get_released_version_tag, run_semver_checks, set_version,
3};
4use crate::publish_release::{create_gh_release, is_releasable_change, publish};
5use crate::utils::{get_current_version_from_cargo_toml, get_revision_for_tag, push_tag, tag};
6use anyhow::Context;
7use std::fs::read_to_string;
8use std::path::Path;
9
10mod prepare_release;
11mod publish_release;
12pub mod utils;
13
14pub const RELEASE_LABEL: &str = "hra-release";
15
16pub fn prepare_release(
23 dir: impl AsRef<Path>,
24 cliff_config: String,
25 force_version: Option<String>,
26 skip_semver_checks: bool,
27 i_am_so_sorry_but_my_features_clash: bool,
28) -> anyhow::Result<()> {
29 let repository = git2::Repository::open(&dir).context("Failed to open git repository")?;
30
31 let force_tag = input_version_to_version_tag(force_version)?;
32
33 generate_changelog(&dir, &cliff_config, &force_tag)?;
35 let next_version_tag = get_next_version(&dir, &cliff_config, &force_tag)?;
36
37 set_version(&dir, &next_version_tag)?;
39
40 if skip_semver_checks {
42 let msg = "Semver checks were skipped for this release. Ensure the version bump is intentional.";
43 if std::env::var("GITHUB_ACTIONS").as_deref() == Ok("true") {
44 println!("::warning title=Semver Checks Skipped::{msg}");
45 } else {
46 eprintln!("WARNING: {msg}");
47 }
48 } else {
49 match get_released_version_tag(&dir, &cliff_config, &force_tag) {
50 Ok(released_version_tag) => {
51 println!("Retrieving revision for tag: {}", released_version_tag);
52 let revision = get_revision_for_tag(&repository, &released_version_tag)?;
53 run_semver_checks(&dir, &revision, i_am_so_sorry_but_my_features_clash)?;
54 }
55 Err(e) => {
56 eprintln!("No previous release found, skipping semver checks: {e:?}");
57 }
58 }
59 }
60
61 Ok(())
62}
63
64pub fn publish_release(
72 dir: impl AsRef<Path>,
73 git_token: String,
74 danger_skip_releasable_changes_check: bool,
75 danger_skip_create_gh_release: bool,
76) -> anyhow::Result<()> {
77 let repository = git2::Repository::open(&dir).context("Failed to open git repository")?;
78
79 if !danger_skip_releasable_changes_check {
80 let maybe_pr_number = is_releasable_change(&repository, &dir)?;
81 let Some(pr_number) = maybe_pr_number else {
82 println!("Not a releasable change, stopping.");
83 return Ok(());
84 };
85 println!("Found releasable change with PR number: {}", pr_number);
86 }
87
88 let cargo_toml =
89 read_to_string(dir.as_ref().join("Cargo.toml")).context("Failed to read Cargo.toml")?;
90 let current_version = get_current_version_from_cargo_toml(&cargo_toml)
91 .context("Failed to find version in Cargo.toml")?;
92 let current_tag = format!("v{current_version}");
93
94 tag(&repository, ¤t_tag, ¤t_tag).context("Failed to tag the release")?;
95 println!("Tagged current HEAD with: {}", current_tag);
96
97 push_tag(&repository, &git_token, ¤t_tag).context("Failed to push tag to remote")?;
98 println!("Pushed tag to remote: {}", current_tag);
99
100 publish(&dir).context("Failed to publish crates")?;
101
102 if !danger_skip_create_gh_release {
103 create_gh_release(&dir, ¤t_tag).context("Failed to create GitHub release")?;
104 }
105
106 println!("Release-util completed successfully. Another successful release on the 📔📘!");
107
108 Ok(())
109}
110
111pub(crate) fn input_version_to_version_tag(
112 force_version: Option<String>,
113) -> anyhow::Result<Option<String>> {
114 let force_tag = match force_version {
115 Some(input) if input.is_empty() => None,
116 Some(maybe_version_tag) => match maybe_version_tag.strip_prefix("v") {
117 Some(version) => {
118 if semver::Version::parse(version).is_ok() {
119 Some(maybe_version_tag)
120 } else {
121 anyhow::bail!("Invalid version format: {}", version);
122 }
123 }
124 None => {
125 if semver::Version::parse(&maybe_version_tag).is_ok() {
126 Some(format!("v{}", maybe_version_tag))
127 } else {
128 anyhow::bail!("Invalid version format: {}", maybe_version_tag);
129 }
130 }
131 },
132 None => None,
133 };
134
135 Ok(force_tag)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn convert_input_version_to_version_tag() {
144 assert_eq!(input_version_to_version_tag(None).unwrap(), None);
146
147 assert_eq!(
149 input_version_to_version_tag(Some("v1.2.3".to_string())).unwrap(),
150 Some("v1.2.3".to_string())
151 );
152
153 assert_eq!(
155 input_version_to_version_tag(Some("1.2.3".to_string())).unwrap(),
156 Some("v1.2.3".to_string())
157 );
158
159 assert!(input_version_to_version_tag(Some("invalid".to_string())).is_err());
161 assert!(input_version_to_version_tag(Some("vinvalid".to_string())).is_err());
163 }
164}