Skip to main content

releasaurus_core/config/
toml.rs

1//! Configuration loading and parsing for `releasaurus.toml` files.
2//!
3//! Supports customizable changelog templates and multi-package repositories.
4use derive_builder::Builder;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    config::{
10        changelog::ChangelogConfig, package::PackageConfig,
11        prerelease::PrereleaseConfig,
12    },
13    result::{ReleasaurusError, Result},
14};
15
16/// Default configuration filename
17pub const DEFAULT_CONFIG_FILE: &str = "releasaurus.toml";
18/// Default number of commits to search when processing first release
19pub const DEFAULT_COMMIT_SEARCH_DEPTH: usize = 400;
20/// Default number of tags to search when looking for previous releases
21pub const DEFAULT_TAG_SEARCH_DEPTH: usize = 100;
22
23#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Builder)]
24#[schemars(rename = "Releasaurus TOML Configuration Schema")]
25#[serde(default)]
26#[builder(setter(into, strip_option), default)]
27/// Configuration properties for `releasaurus.toml`
28pub struct Config {
29    /// The base branch to target for release PRs, tagging, and releases
30    /// defaults to default_branch for repository
31    pub base_branch: Option<String>,
32    /// Maximum number of commits to search for the first release when no
33    /// tags exist
34    pub first_release_search_depth: usize,
35    /// Maximum number of tags to pull when searching for previous releases.
36    /// Set to 0 to search all tags
37    pub tag_search_depth: usize,
38    /// Generates different release PRs for each package defined in config
39    pub separate_pull_requests: bool,
40    /// Global prerelease configuration (suffix + strategy). Packages can
41    /// override this configuration
42    pub prerelease: PrereleaseConfig,
43    /// Global config to auto start next release for all packages. Packages
44    /// can override this configuration
45    pub auto_start_next: Option<bool>,
46    /// Always increments major version on breaking commits
47    pub breaking_always_increment_major: bool,
48    /// Always increments minor version on feature commits
49    pub features_always_increment_minor: bool,
50    /// Custom regex pattern matched against commit messages to trigger a
51    /// major version bump. This is additive — breaking change commits always
52    /// trigger major bumps regardless of this setting. In TOML double-quoted
53    /// strings, escape backslashes (e.g. `"\\[BREAKING\\]"` matches
54    /// `[BREAKING]`).
55    pub custom_major_increment_regex: Option<String>,
56    /// Custom regex pattern matched against commit messages to trigger a
57    /// minor version bump. This is additive — `feat:` commits always trigger
58    /// minor bumps regardless of this setting. In TOML double-quoted strings,
59    /// escape backslashes (e.g. `"\\[FEATURE\\]"` matches `[FEATURE]`).
60    pub custom_minor_increment_regex: Option<String>,
61    /// Changelog generation settings.
62    pub changelog: ChangelogConfig,
63    /// Packages to manage in this repository (supports monorepos)
64    #[serde(rename = "package")]
65    pub packages: Vec<PackageConfig>,
66}
67
68impl Default for Config {
69    fn default() -> Self {
70        Self {
71            base_branch: None,
72            first_release_search_depth: DEFAULT_COMMIT_SEARCH_DEPTH,
73            tag_search_depth: DEFAULT_TAG_SEARCH_DEPTH,
74            separate_pull_requests: false,
75            prerelease: PrereleaseConfig::default(),
76            auto_start_next: None,
77            breaking_always_increment_major: true,
78            features_always_increment_minor: true,
79            custom_major_increment_regex: None,
80            custom_minor_increment_regex: None,
81            changelog: ChangelogConfig::default(),
82            packages: vec![PackageConfig::default()],
83        }
84    }
85}
86
87impl Config {
88    pub fn base_branch(&self) -> Result<String> {
89        self.base_branch
90            .clone()
91            .ok_or_else(|| ReleasaurusError::BaseBranchNotConfigured)
92    }
93
94    pub fn auto_start_next(&self, package: &PackageConfig) -> bool {
95        package
96            .auto_start_next
97            .or(self.auto_start_next)
98            .unwrap_or_default()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn loads_defaults() {
108        let config = Config::default();
109        assert!(!config.changelog.body.is_empty());
110        assert_eq!(
111            config.first_release_search_depth,
112            DEFAULT_COMMIT_SEARCH_DEPTH
113        );
114    }
115
116    #[test]
117    fn base_branch_returns_value_when_set() {
118        let config = Config {
119            base_branch: Some("main".into()),
120            ..Default::default()
121        };
122
123        assert_eq!(config.base_branch().unwrap(), "main");
124    }
125
126    #[test]
127    fn base_branch_returns_error_when_none() {
128        let config = Config {
129            base_branch: None,
130            ..Default::default()
131        };
132
133        assert!(config.base_branch().is_err());
134    }
135
136    #[test]
137    fn auto_start_next_uses_package_override() {
138        let config = Config {
139            auto_start_next: Some(false),
140            ..Default::default()
141        };
142        let package = PackageConfig {
143            auto_start_next: Some(true),
144            ..Default::default()
145        };
146
147        assert!(config.auto_start_next(&package));
148    }
149
150    #[test]
151    fn auto_start_next_uses_global_when_package_not_set() {
152        let config = Config {
153            auto_start_next: Some(true),
154            ..Default::default()
155        };
156        let package = PackageConfig::default();
157
158        assert!(config.auto_start_next(&package));
159    }
160
161    #[test]
162    fn auto_start_next_defaults_to_false() {
163        let config = Config::default();
164        let package = PackageConfig::default();
165
166        assert!(!config.auto_start_next(&package));
167    }
168}