use release_plz_core::{ReleaseRequest, UpdateRequest};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
use url::Url;
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub workspace: Workspace,
#[serde(default)]
package: Vec<PackageSpecificConfigWithName>,
}
impl Config {
fn packages(&self) -> HashMap<&str, &PackageSpecificConfig> {
self.package
.iter()
.map(|p| (p.name.as_str(), &p.config))
.collect()
}
pub fn fill_update_config(
&self,
is_changelog_update_disabled: bool,
update_request: UpdateRequest,
) -> UpdateRequest {
let mut default_update_config = self.workspace.packages_defaults.update.clone();
if is_changelog_update_disabled {
default_update_config.changelog_update = false.into();
}
let mut update_request =
update_request.with_default_package_config(default_update_config.into());
for (package, config) in self.packages() {
let mut update_config = config.clone();
update_config = update_config.merge(self.workspace.packages_defaults.clone());
if is_changelog_update_disabled {
update_config.update.changelog_update = false.into();
}
update_request = update_request.with_package_config(package, update_config.into());
}
update_request
}
pub fn fill_release_config(
&self,
allow_dirty: bool,
no_verify: bool,
release_request: ReleaseRequest,
) -> ReleaseRequest {
let mut default_config = self.workspace.packages_defaults.release.clone();
if no_verify {
default_config.release.no_verify = Some(true);
}
if allow_dirty {
default_config.release.allow_dirty = Some(true);
}
let mut release_request =
release_request.with_default_package_config(default_config.into());
for (package, config) in self.packages() {
let mut release_config = config.clone();
release_config = release_config.merge(self.workspace.packages_defaults.clone());
if no_verify {
release_config.release.release.no_verify = Some(true);
}
if allow_dirty {
release_config.release.release.allow_dirty = Some(true);
}
release_request = release_request.with_package_config(package, release_config.into());
}
release_request
}
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug)]
pub struct Workspace {
#[serde(flatten)]
pub update: UpdateConfig,
#[serde(flatten)]
pub release_pr: ReleasePrConfig,
#[serde(flatten)]
pub common: CommonCmdConfig,
#[serde(flatten)]
pub packages_defaults: PackageConfig,
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug)]
pub struct CommonCmdConfig {
pub repo_url: Option<Url>,
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug)]
pub struct UpdateConfig {
pub dependencies_update: Option<bool>,
pub changelog_config: Option<PathBuf>,
pub allow_dirty: Option<bool>,
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug)]
pub struct ReleasePrConfig {
#[serde(default)]
pub pr_draft: bool,
#[serde(default)]
pub pr_labels: Vec<String>,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct PackageSpecificConfig {
#[serde(flatten)]
update: PackageUpdateConfig,
#[serde(flatten)]
release: PackageReleaseConfig,
changelog_path: Option<PathBuf>,
changelog_include: Option<Vec<String>>,
}
impl PackageSpecificConfig {
pub fn merge(self, default: PackageConfig) -> PackageSpecificConfig {
PackageSpecificConfig {
update: self.update.merge(default.update),
release: self.release.merge(default.release),
changelog_path: self.changelog_path,
changelog_include: self.changelog_include,
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct PackageSpecificConfigWithName {
pub name: String,
#[serde(flatten)]
pub config: PackageSpecificConfig,
}
impl From<PackageSpecificConfig> for release_plz_core::PackageReleaseConfig {
fn from(config: PackageSpecificConfig) -> Self {
let generic = config.release.into();
Self {
generic,
changelog_path: config.changelog_path,
}
}
}
impl From<PackageReleaseConfig> for release_plz_core::ReleaseConfig {
fn from(value: PackageReleaseConfig) -> Self {
let is_publish_enabled = value.release.publish != Some(false);
let is_git_release_enabled = value.git_release.enable != Some(false);
let is_git_release_draft = value.git_release.draft == Some(true);
let is_git_tag_enabled = value.git_tag.enable != Some(false);
let mut cfg = Self::default()
.with_publish(release_plz_core::PublishConfig::enabled(is_publish_enabled))
.with_git_release(
release_plz_core::GitReleaseConfig::enabled(is_git_release_enabled)
.set_draft(is_git_release_draft),
)
.with_git_tag(release_plz_core::GitTagConfig::enabled(is_git_tag_enabled));
if let Some(no_verify) = value.release.no_verify {
cfg = cfg.with_no_verify(no_verify);
}
if let Some(allow_dirty) = value.release.allow_dirty {
cfg = cfg.with_allow_dirty(allow_dirty);
}
cfg
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default, Clone)]
pub struct PackageConfig {
#[serde(flatten)]
update: PackageUpdateConfig,
#[serde(flatten)]
release: PackageReleaseConfig,
}
impl From<PackageUpdateConfig> for release_plz_core::UpdateConfig {
fn from(config: PackageUpdateConfig) -> Self {
Self {
semver_check: config.semver_check != Some(false),
changelog_update: config.changelog_update != Some(false),
}
}
}
impl From<PackageSpecificConfig> for release_plz_core::PackageUpdateConfig {
fn from(config: PackageSpecificConfig) -> Self {
Self {
generic: config.update.into(),
changelog_path: config.changelog_path,
changelog_include: config.changelog_include.unwrap_or_default(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default, Clone)]
pub struct PackageUpdateConfig {
pub semver_check: Option<bool>,
pub changelog_update: Option<bool>,
}
impl PackageUpdateConfig {
pub fn merge(self, default: PackageUpdateConfig) -> PackageUpdateConfig {
PackageUpdateConfig {
semver_check: self.semver_check.or(default.semver_check),
changelog_update: self.changelog_update.or(default.changelog_update),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default, Clone)]
pub struct PackageReleaseConfig {
#[serde(flatten, default)]
pub git_release: GitReleaseConfig,
#[serde(flatten, default)]
pub git_tag: GitTagConfig,
#[serde(flatten, default)]
pub release: ReleaseConfig,
}
impl PackageReleaseConfig {
pub fn merge(self, default: Self) -> Self {
Self {
git_release: self.git_release.merge(default.git_release),
release: self.release.merge(default.release),
git_tag: self.git_tag.merge(default.git_tag),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default, Clone)]
pub struct ReleaseConfig {
pub publish: Option<bool>,
#[serde(rename = "publish_allow_dirty")]
pub allow_dirty: Option<bool>,
#[serde(rename = "publish_no_verify")]
pub no_verify: Option<bool>,
}
impl ReleaseConfig {
pub fn merge(self, default: Self) -> Self {
Self {
publish: self.publish.or(default.publish),
allow_dirty: self.allow_dirty.or(default.allow_dirty),
no_verify: self.no_verify.or(default.no_verify),
}
}
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum SemverCheck {
#[default]
Yes,
No,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)]
pub struct GitTagConfig {
#[serde(rename = "git_tag_enable")]
enable: Option<bool>,
}
impl GitTagConfig {
pub fn merge(self, default: GitTagConfig) -> Self {
Self {
enable: self.enable.or(default.enable),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)]
pub struct GitReleaseConfig {
#[serde(rename = "git_release_enable")]
enable: Option<bool>,
#[serde(rename = "git_release_type")]
pub release_type: Option<ReleaseType>,
#[serde(rename = "git_release_draft")]
pub draft: Option<bool>,
}
impl GitReleaseConfig {
pub fn merge(self, default: Self) -> Self {
Self {
enable: self.enable.or(default.enable),
release_type: self.release_type.or(default.release_type),
draft: self.draft.or(default.draft),
}
}
}
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum ReleaseType {
#[default]
Prod,
Pre,
Auto,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_without_update_config_is_deserialized() {
let config = r#"
[workspace]
dependencies_update = false
changelog_config = "../git-cliff.toml"
repo_url = "https://github.com/MarcoIeni/release-plz"
git_release_enable = true
git_release_type = "prod"
git_release_draft = false
"#;
let expected_config = Config {
workspace: Workspace {
update: UpdateConfig {
dependencies_update: Some(false),
changelog_config: Some("../git-cliff.toml".into()),
allow_dirty: None,
},
common: CommonCmdConfig {
repo_url: Some("https://github.com/MarcoIeni/release-plz".parse().unwrap()),
},
packages_defaults: PackageConfig {
update: PackageUpdateConfig {
semver_check: None,
changelog_update: None,
},
release: PackageReleaseConfig {
git_release: GitReleaseConfig {
enable: Some(true),
release_type: Some(ReleaseType::Prod),
draft: Some(false),
},
..Default::default()
},
},
release_pr: ReleasePrConfig {
pr_draft: false,
pr_labels: vec![],
},
},
package: [].into(),
};
let config: Config = toml::from_str(config).unwrap();
assert_eq!(config, expected_config)
}
#[test]
fn config_is_deserialized() {
let config = r#"
[workspace]
changelog_config = "../git-cliff.toml"
allow_dirty = false
repo_url = "https://github.com/MarcoIeni/release-plz"
changelog_update = true
git_release_enable = true
git_release_type = "prod"
git_release_draft = false
"#;
let expected_config = Config {
workspace: Workspace {
update: UpdateConfig {
dependencies_update: None,
changelog_config: Some("../git-cliff.toml".into()),
allow_dirty: Some(false),
},
common: CommonCmdConfig {
repo_url: Some("https://github.com/MarcoIeni/release-plz".parse().unwrap()),
},
release_pr: ReleasePrConfig {
pr_draft: false,
pr_labels: vec![],
},
packages_defaults: PackageConfig {
update: PackageUpdateConfig {
semver_check: None,
changelog_update: true.into(),
},
release: PackageReleaseConfig {
git_release: GitReleaseConfig {
enable: true.into(),
release_type: Some(ReleaseType::Prod),
draft: Some(false),
},
git_tag: GitTagConfig { enable: None },
release: ReleaseConfig {
publish: None,
allow_dirty: None,
no_verify: None,
},
},
},
},
package: [].into(),
};
let config: Config = toml::from_str(config).unwrap();
assert_eq!(config, expected_config)
}
#[test]
fn config_is_serialized() {
let config = Config {
workspace: Workspace {
update: UpdateConfig {
dependencies_update: None,
changelog_config: Some("../git-cliff.toml".into()),
allow_dirty: None,
},
common: CommonCmdConfig {
repo_url: Some("https://github.com/MarcoIeni/release-plz".parse().unwrap()),
},
release_pr: ReleasePrConfig {
pr_draft: false,
pr_labels: vec!["label1".to_string()],
},
packages_defaults: PackageConfig {
update: PackageUpdateConfig {
semver_check: None,
changelog_update: true.into(),
},
release: PackageReleaseConfig {
git_release: GitReleaseConfig {
enable: true.into(),
release_type: Some(ReleaseType::Prod),
draft: Some(false),
},
..Default::default()
},
},
},
package: [PackageSpecificConfigWithName {
name: "crate1".to_string(),
config: PackageSpecificConfig {
update: PackageUpdateConfig {
semver_check: Some(false),
changelog_update: true.into(),
},
release: PackageReleaseConfig {
git_release: GitReleaseConfig {
enable: true.into(),
release_type: Some(ReleaseType::Prod),
draft: Some(false),
},
..Default::default()
},
changelog_path: Some("./CHANGELOG.md".into()),
changelog_include: Some(vec!["pkg1".to_string()]),
},
}]
.into(),
};
expect_test::expect![[r#"
[workspace]
changelog_config = "../git-cliff.toml"
pr_draft = false
pr_labels = ["label1"]
repo_url = "https://github.com/MarcoIeni/release-plz"
changelog_update = true
git_release_enable = true
git_release_type = "prod"
git_release_draft = false
[[package]]
name = "crate1"
semver_check = false
changelog_update = true
git_release_enable = true
git_release_type = "prod"
git_release_draft = false
changelog_path = "./CHANGELOG.md"
changelog_include = ["pkg1"]
"#]]
.assert_eq(&toml::to_string(&config).unwrap());
}
}