use std::path::Path;
use anyhow::{
Context,
Result,
};
use toml_edit::{
DocumentMut,
value,
};
pub fn update_cargo_toml_version(
manifest_path: &Path,
_old_version: &str,
new_version: &str,
) -> Result<()> {
let content = std::fs::read_to_string(manifest_path)
.with_context(|| format!("Failed to read {}", manifest_path.display()))?;
let mut doc = content
.parse::<DocumentMut>()
.with_context(|| format!("Failed to parse TOML in {}", manifest_path.display()))?;
let mut package_updated = false;
let mut workspace_updated = false;
if let Some(package) = doc.get_mut("package").and_then(|p| p.as_table_mut())
&& package.contains_key("version")
&& package.get("version").is_some_and(|v| v.as_str().is_some())
{
package.insert("version", value(new_version));
package_updated = true;
}
if let Some(workspace_package) = doc
.get_mut("workspace")
.and_then(|w| w.as_table_mut())
.and_then(|w| w.get_mut("package"))
.and_then(|p| p.as_table_mut())
&& workspace_package.contains_key("version")
{
workspace_package.insert("version", value(new_version));
workspace_updated = true;
}
if !package_updated && !workspace_updated {
anyhow::bail!(
"Could not find version in [package] or [workspace.package] section in {}",
manifest_path.display()
);
}
std::fs::write(manifest_path, doc.to_string())
.with_context(|| format!("Failed to write {}", manifest_path.display()))?;
Ok(())
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
use super::*;
fn create_temp_manifest(content: &str) -> (TempDir, std::path::PathBuf) {
let dir = tempfile::tempdir().unwrap();
let manifest_path = dir.path().join("Cargo.toml");
std::fs::write(&manifest_path, content).unwrap();
(dir, manifest_path)
}
#[test]
fn test_update_package_version() {
let (_dir, manifest_path) = create_temp_manifest(
r#"[package]
name = "test"
version = "0.1.0"
"#,
);
update_cargo_toml_version(&manifest_path, "0.1.0", "0.2.0").unwrap();
let content = std::fs::read_to_string(&manifest_path).unwrap();
assert!(content.contains("version = \"0.2.0\""));
assert!(!content.contains("0.1.0"));
}
#[test]
fn test_update_workspace_package_version() {
let (_dir, manifest_path) = create_temp_manifest(
r#"[workspace.package]
version = "1.0.0"
"#,
);
update_cargo_toml_version(&manifest_path, "1.0.0", "2.0.0").unwrap();
let content = std::fs::read_to_string(&manifest_path).unwrap();
assert!(content.contains("version = \"2.0.0\""));
}
#[test]
fn test_preserves_formatting() {
let (_dir, manifest_path) = create_temp_manifest(
r#"[package]
name = "test" # Package name
version = "0.1.0"
edition = "2021"
"#,
);
update_cargo_toml_version(&manifest_path, "0.1.0", "0.2.0").unwrap();
let content = std::fs::read_to_string(&manifest_path).unwrap();
assert!(content.contains("# Package name"));
assert!(content.contains("version = \"0.2.0\""));
assert!(!content.contains("0.1.0"));
}
#[test]
fn test_update_both_package_and_workspace_version() {
let (_dir, manifest_path) = create_temp_manifest(
r#"[workspace]
members = ["npm/dotenvage-napi"]
[workspace.package]
version = "0.2.1"
edition = "2024"
[package]
name = "dotenvage"
version = "0.2.1"
edition.workspace = true
"#,
);
update_cargo_toml_version(&manifest_path, "0.2.1", "0.2.2").unwrap();
let content = std::fs::read_to_string(&manifest_path).unwrap();
assert!(!content.contains("0.2.1"), "Old version should be gone");
let count = content.matches("\"0.2.2\"").count();
assert_eq!(count, 2, "New version should appear in both sections");
}
#[test]
fn test_package_with_workspace_inheritance_not_updated() {
let (_dir, manifest_path) = create_temp_manifest(
r#"[workspace.package]
version = "1.0.0"
[package]
name = "test"
version.workspace = true
"#,
);
update_cargo_toml_version(&manifest_path, "1.0.0", "2.0.0").unwrap();
let content = std::fs::read_to_string(&manifest_path).unwrap();
assert!(content.contains("[workspace.package]\nversion = \"2.0.0\""));
assert!(content.contains("version.workspace = true"));
}
#[test]
fn test_no_package_section_error() {
let (_dir, manifest_path) = create_temp_manifest(
r#"[dependencies]
some-crate = "1.0"
"#,
);
let result = update_cargo_toml_version(&manifest_path, "0.1.0", "0.2.0");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Could not find"));
}
}