use crate::stack::types::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BumpType {
Patch,
Minor,
Major,
}
impl BumpType {
pub fn apply(&self, version: &semver::Version) -> semver::Version {
match self {
BumpType::Patch => {
semver::Version::new(version.major, version.minor, version.patch + 1)
}
BumpType::Minor => semver::Version::new(version.major, version.minor + 1, 0),
BumpType::Major => semver::Version::new(version.major + 1, 0, 0),
}
}
}
#[derive(Debug, Clone)]
pub struct ReleaseConfig {
pub bump_type: Option<BumpType>,
pub no_verify: bool,
pub dry_run: bool,
pub publish: bool,
pub min_coverage: f64,
pub lint_command: String,
pub coverage_command: String,
pub comply_command: String,
pub fail_on_comply_violations: bool,
pub quality_gate_command: String,
pub fail_on_quality_gate: bool,
pub tdg_command: String,
pub min_tdg_score: f64,
pub fail_on_tdg: bool,
pub dead_code_command: String,
pub fail_on_dead_code: bool,
pub complexity_command: String,
pub max_complexity: u32,
pub fail_on_complexity: bool,
pub satd_command: String,
pub max_satd_items: u32,
pub fail_on_satd: bool,
pub popper_command: String,
pub min_popper_score: f64,
pub fail_on_popper: bool,
pub book_command: String,
pub fail_on_book: bool,
pub examples_command: String,
pub fail_on_examples: bool,
}
impl Default for ReleaseConfig {
fn default() -> Self {
Self {
bump_type: None,
no_verify: false,
dry_run: false,
publish: false,
min_coverage: 90.0,
lint_command: "make lint".to_string(),
coverage_command: "make coverage".to_string(),
comply_command: "pmat comply".to_string(),
fail_on_comply_violations: true,
quality_gate_command: "pmat quality-gate".to_string(),
fail_on_quality_gate: true,
tdg_command: "pmat tdg --format json".to_string(),
min_tdg_score: 80.0,
fail_on_tdg: true,
dead_code_command: "pmat analyze dead-code --format json".to_string(),
fail_on_dead_code: false, complexity_command: "pmat analyze complexity --format json".to_string(),
max_complexity: 20,
fail_on_complexity: true,
satd_command: "pmat analyze satd --format json".to_string(),
max_satd_items: 10,
fail_on_satd: false, popper_command: "pmat popper-score --format json".to_string(),
min_popper_score: 60.0,
fail_on_popper: true,
book_command: "mdbook build book".to_string(),
fail_on_book: true,
examples_command: "cargo run --example".to_string(),
fail_on_examples: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ReleaseResult {
pub success: bool,
pub released_crates: Vec<ReleasedCrate>,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct ReleasedCrate {
pub name: String,
pub version: semver::Version,
pub published: bool,
}
pub fn format_plan_text(plan: &ReleasePlan) -> String {
let mut output = String::new();
if plan.dry_run {
output.push_str("📋 Release Plan (DRY RUN)\n");
} else {
output.push_str("📋 Release Plan\n");
}
output.push_str(&"═".repeat(50));
output.push_str("\n\n");
output.push_str("Release order (topological):\n");
for (i, release) in plan.releases.iter().enumerate() {
output.push_str(&format!(
" {}. {} {} → {}\n",
i + 1,
release.crate_name,
release.current_version,
release.new_version
));
}
output.push_str("\nPre-flight status:\n");
for release in &plan.releases {
let status = plan
.preflight_results
.get(&release.crate_name)
.map(|r| if r.passed { "✓" } else { "✗" })
.unwrap_or("?");
output.push_str(&format!(" {} {}\n", status, release.crate_name));
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bump_type_patch() {
let version = semver::Version::new(1, 2, 3);
let bumped = BumpType::Patch.apply(&version);
assert_eq!(bumped, semver::Version::new(1, 2, 4));
}
#[test]
fn test_bump_type_minor() {
let version = semver::Version::new(1, 2, 3);
let bumped = BumpType::Minor.apply(&version);
assert_eq!(bumped, semver::Version::new(1, 3, 0));
}
#[test]
fn test_bump_type_major() {
let version = semver::Version::new(1, 2, 3);
let bumped = BumpType::Major.apply(&version);
assert_eq!(bumped, semver::Version::new(2, 0, 0));
}
#[test]
fn test_bump_type_clone() {
let bump = BumpType::Minor;
let cloned = bump;
assert_eq!(bump, cloned);
}
#[test]
fn test_release_config_default() {
let config = ReleaseConfig::default();
assert!(config.bump_type.is_none());
assert!(!config.no_verify);
assert!(!config.dry_run);
assert!(!config.publish);
assert_eq!(config.min_coverage, 90.0);
}
#[test]
fn test_release_config_pmat_defaults() {
let config = ReleaseConfig::default();
assert_eq!(config.quality_gate_command, "pmat quality-gate");
assert!(config.fail_on_quality_gate);
assert_eq!(config.min_tdg_score, 80.0);
assert!(config.fail_on_tdg);
assert!(!config.fail_on_dead_code); assert_eq!(config.max_complexity, 20);
assert!(config.fail_on_complexity);
assert!(!config.fail_on_satd); assert_eq!(config.min_popper_score, 60.0);
assert!(config.fail_on_popper);
}
#[test]
fn test_release_config_book_defaults() {
let config = ReleaseConfig::default();
assert_eq!(config.book_command, "mdbook build book");
assert!(config.fail_on_book);
assert_eq!(config.examples_command, "cargo run --example");
assert!(config.fail_on_examples);
}
#[test]
fn test_release_config_custom_thresholds() {
let config = ReleaseConfig {
min_tdg_score: 90.0,
min_popper_score: 70.0,
max_complexity: 15,
max_satd_items: 5,
fail_on_dead_code: true,
fail_on_satd: true,
..Default::default()
};
assert_eq!(config.min_tdg_score, 90.0);
assert_eq!(config.min_popper_score, 70.0);
assert_eq!(config.max_complexity, 15);
assert_eq!(config.max_satd_items, 5);
assert!(config.fail_on_dead_code);
assert!(config.fail_on_satd);
}
#[test]
fn test_release_config_disabled_checks() {
let config = ReleaseConfig {
quality_gate_command: String::new(),
tdg_command: String::new(),
dead_code_command: String::new(),
complexity_command: String::new(),
satd_command: String::new(),
popper_command: String::new(),
..Default::default()
};
assert!(config.quality_gate_command.is_empty());
assert!(config.tdg_command.is_empty());
assert!(config.dead_code_command.is_empty());
assert!(config.complexity_command.is_empty());
assert!(config.satd_command.is_empty());
assert!(config.popper_command.is_empty());
}
#[test]
fn test_release_result_success() {
let result = ReleaseResult {
success: true,
released_crates: vec![ReleasedCrate {
name: "test".to_string(),
version: semver::Version::new(1, 0, 0),
published: true,
}],
message: "Success".to_string(),
};
assert!(result.success);
assert_eq!(result.released_crates.len(), 1);
}
#[test]
fn test_released_crate_clone() {
let crate_info = ReleasedCrate {
name: "test".to_string(),
version: semver::Version::new(1, 0, 0),
published: true,
};
let cloned = crate_info.clone();
assert_eq!(crate_info.name, cloned.name);
assert_eq!(crate_info.version, cloned.version);
assert_eq!(crate_info.published, cloned.published);
}
}