use std::path::PathBuf;
use anyhow::{
Context,
Result,
};
use cargo_plugin_utils::common::get_package_version_from_manifest;
use clap::Parser;
#[derive(Parser, Debug)]
pub struct PostBumpHookArgs {
#[arg(long)]
manifest_path: Option<PathBuf>,
#[arg(long, default_value = ".")]
repo_path: PathBuf,
#[arg(long, env = "COG_VERSION")]
target_version: Option<String>,
#[arg(long, env = "COG_LATEST")]
previous_version: Option<String>,
#[arg(long, default_value = "true")]
exit_on_error: bool,
}
pub fn post_bump_hook(args: PostBumpHookArgs) -> Result<()> {
let mut logger = cargo_plugin_utils::logger::Logger::new();
logger.status("Reading", "package version");
let manifest_path = args
.manifest_path
.as_deref()
.unwrap_or_else(|| std::path::Path::new("./Cargo.toml"));
let cargo_version = get_package_version_from_manifest(manifest_path)
.with_context(|| format!("Failed to get version from {}", manifest_path.display()))?;
logger.finish();
if let Some(target) = &args.target_version {
let target_trimmed = target.trim();
if cargo_version != target_trimmed {
eprintln!(
"❌ Error: Cargo.toml version ({}) doesn't match expected target ({})",
cargo_version, target_trimmed
);
if args.exit_on_error {
anyhow::bail!("Version bump verification failed");
}
} else {
logger.print_message(&format!("✓ Version bump verified: {}", cargo_version));
}
} else {
logger.print_message("✓ Post-bump check passed");
logger.print_message(&format!(" Current version: {}", cargo_version));
}
if let Some(previous) = &args.previous_version {
let previous_trimmed = previous.trim();
if cargo_version == previous_trimmed {
eprintln!(
"⚠️ Warning: Version didn't change (still {})",
cargo_version
);
if args.exit_on_error {
anyhow::bail!("Version bump appears to have failed");
}
} else {
logger.print_message(&format!(" Previous version: {}", previous_trimmed));
logger.print_message(&format!(" New version: {}", cargo_version));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn create_temp_cargo_project(content: &str) -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let manifest_path = dir.path().join("Cargo.toml");
std::fs::write(&manifest_path, content).unwrap();
let src_dir = dir.path().join("src");
std::fs::create_dir_all(&src_dir).unwrap();
std::fs::write(src_dir.join("lib.rs"), "// Test library\n").unwrap();
dir
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_success() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
version = "1.0.0"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: Some("1.0.0".to_string()),
previous_version: Some("0.9.0".to_string()),
exit_on_error: true,
};
assert!(post_bump_hook(args).is_ok());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_version_mismatch() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
version = "0.9.1"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: Some("1.0.0".to_string()),
previous_version: Some("0.9.0".to_string()),
exit_on_error: true,
};
assert!(post_bump_hook(args).is_err());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_version_unchanged() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
version = "0.9.0"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: None,
previous_version: Some("0.9.0".to_string()),
exit_on_error: true,
};
assert!(post_bump_hook(args).is_err());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_no_target_version() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
version = "2.0.0"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: None,
previous_version: None,
exit_on_error: true,
};
assert!(post_bump_hook(args).is_ok());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_no_previous_version() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
version = "1.5.0"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: Some("1.5.0".to_string()),
previous_version: None,
exit_on_error: true,
};
assert!(post_bump_hook(args).is_ok());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_file_not_found() {
let args = PostBumpHookArgs {
manifest_path: Some("/nonexistent/Cargo.toml".into()),
repo_path: ".".into(),
target_version: None,
previous_version: None,
exit_on_error: true,
};
assert!(post_bump_hook(args).is_err());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_no_version() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: None,
previous_version: None,
exit_on_error: true,
};
assert!(post_bump_hook(args).is_ok());
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_exit_on_error_false() {
let _dir = create_temp_cargo_project(
r#"
[package]
name = "test"
version = "0.9.1"
"#,
);
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: Some("1.0.0".to_string()),
previous_version: Some("0.9.0".to_string()),
exit_on_error: false, };
let result = post_bump_hook(args);
let _ = result;
}
#[test]
#[serial_test::serial]
fn test_post_bump_hook_workspace_version() {
let _dir = tempfile::tempdir().unwrap();
std::fs::write(
_dir.path().join("Cargo.toml"),
r#"
[workspace.package]
version = "2.1.0"
[workspace]
members = ["member1"]
"#,
)
.unwrap();
let member_dir = _dir.path().join("member1");
std::fs::create_dir_all(member_dir.join("src")).unwrap();
std::fs::write(
member_dir.join("Cargo.toml"),
r#"
[package]
name = "member1"
version.workspace = true
"#,
)
.unwrap();
std::fs::write(member_dir.join("src").join("lib.rs"), "// Test library\n").unwrap();
let manifest_path = _dir.path().join("Cargo.toml");
let args = PostBumpHookArgs {
manifest_path: Some(manifest_path),
repo_path: ".".into(),
target_version: Some("2.1.0".to_string()),
previous_version: Some("2.0.0".to_string()),
exit_on_error: true,
};
assert!(post_bump_hook(args).is_ok());
}
}