cargo-governor 2.0.3

Machine-First, LLM-Ready, CI/CD-Native release automation tool for Rust crates
Documentation
//! Version determination for simulation service

use crate::error::Result;
use governor_core::{
    domain::commit::{Commit, CommitType},
    domain::version::{BumpType, SemanticVersion},
    traits::source_control::SourceControl,
};
use governor_git::GitAdapter;
use serde_json::json;
use std::path::PathBuf;

/// Get current version from workspace
pub fn get_current_version(workspace_path: &str) -> Result<String> {
    let cargo_toml_path = PathBuf::from(workspace_path).join("Cargo.toml");
    let content = std::fs::read_to_string(&cargo_toml_path)
        .map_err(|e| crate::error::Error::Io(format!("Failed to read Cargo.toml: {e}")))?;

    let value: toml::Value = toml::from_str(&content)
        .map_err(|e| crate::error::Error::Config(format!("Failed to parse Cargo.toml: {e}")))?;

    let package = value
        .get("workspace")
        .and_then(|w| w.get("package"))
        .or_else(|| value.get("package"));

    package
        .and_then(|p| p.get("version"))
        .and_then(|v| v.as_str())
        .map(String::from)
        .ok_or_else(|| crate::error::Error::Config("No version found in Cargo.toml".to_string()))
}

/// Determine target version for simulation
pub async fn determine_target_version(
    workspace_path: &str,
    forced_version: Option<&str>,
    current: &SemanticVersion,
) -> Result<SemanticVersion> {
    if let Some(ver_str) = forced_version {
        return SemanticVersion::parse(ver_str).map_err(|e| {
            crate::error::Error::Version(format!("Failed to parse target version: {e}"))
        });
    }

    let config = governor_git::GitAdapterConfig {
        repository_path: Some(PathBuf::from(workspace_path)),
        ..Default::default()
    };

    let git = GitAdapter::open(config)
        .map_err(|e| crate::error::Error::Config(format!("Failed to open git repository: {e}")))?;

    let commits = git
        .get_commits_since(None)
        .await
        .map_err(|e| crate::error::Error::Git(format!("Failed to get commits: {e}")))?;

    let bump_type = determine_bump_from_commits(&commits);
    Ok(bump_type.apply_to(current))
}

/// Determine bump type from commits
fn determine_bump_from_commits(commits: &[Commit]) -> BumpType {
    let mut has_breaking = false;
    let mut has_features = false;
    let mut has_fixes = false;

    for commit in commits {
        match commit.commit_type {
            Some(CommitType::Feat | CommitType::Fix | CommitType::Perf | CommitType::Refactor)
                if commit.breaking =>
            {
                has_breaking = true;
            }
            Some(CommitType::Feat) => has_features = true,
            Some(CommitType::Fix | CommitType::Perf) => has_fixes = true,
            _ => {}
        }
    }

    if has_breaking {
        BumpType::Major
    } else if has_features {
        BumpType::Minor
    } else if has_fixes {
        BumpType::Patch
    } else {
        BumpType::None
    }
}

/// Get bump type string from version comparison
pub fn get_bump_type(current: &SemanticVersion, target: &SemanticVersion) -> String {
    if target.major() > current.major() {
        "major".to_string()
    } else if target.minor() > current.minor() {
        "minor".to_string()
    } else if target.patch() > current.patch() {
        "patch".to_string()
    } else {
        "none".to_string()
    }
}

/// Create version diff JSON value
pub fn create_version_diff(
    current: &SemanticVersion,
    target: &SemanticVersion,
) -> serde_json::Value {
    json!({
        "current": current.to_string(),
        "target": target.to_string(),
        "bump_type": get_bump_type(current, target),
    })
}