standard_version/lib.rs
1//! Semantic version bump calculation from conventional commits.
2//!
3//! Computes the next version from a list of parsed conventional commits and
4//! bump rules. Also provides the [`VersionFile`] trait for ecosystem-specific
5//! version file detection and updating, with built-in support for
6//! `Cargo.toml` via [`CargoVersionFile`], `pyproject.toml` via
7//! [`PyprojectVersionFile`], `package.json` via [`JsonVersionFile`],
8//! `deno.json`/`deno.jsonc` via [`DenoVersionFile`], `pubspec.yaml` via
9//! [`PubspecVersionFile`], `gradle.properties` via [`GradleVersionFile`],
10//! and plain `VERSION` files via [`PlainVersionFile`].
11//!
12//! # Main entry points
13//!
14//! - [`determine_bump`] — analyse commits and return the bump level
15//! - [`apply_bump`] — apply a bump level to a semver version
16//! - [`apply_prerelease`] — bump with a pre-release tag (e.g. `rc.0`)
17//! - [`replace_version_in_toml`] — update the version in a `Cargo.toml` string
18//! - [`update_version_files`] — discover and update version files at a repo root
19//!
20//! # Example
21//!
22//! ```
23//! use standard_version::{determine_bump, apply_bump, BumpLevel};
24//!
25//! let commits = vec![
26//! standard_commit::parse("feat: add login").unwrap(),
27//! standard_commit::parse("fix: handle timeout").unwrap(),
28//! ];
29//!
30//! let level = determine_bump(&commits).unwrap();
31//! assert_eq!(level, BumpLevel::Minor);
32//!
33//! let current = semver::Version::new(1, 2, 3);
34//! let next = apply_bump(¤t, level);
35//! assert_eq!(next, semver::Version::new(1, 3, 0));
36//! ```
37
38pub mod bump;
39pub mod calver;
40pub mod cargo;
41pub mod gradle;
42pub mod json;
43pub mod project;
44pub mod pubspec;
45pub mod pyproject;
46pub mod regex_engine;
47pub mod scan;
48pub mod toml_helpers;
49pub mod version_file;
50pub mod version_plain;
51
52pub use bump::{BumpLevel, BumpSummary, apply_bump, apply_prerelease, determine_bump, summarise};
53pub use cargo::CargoVersionFile;
54pub use gradle::GradleVersionFile;
55pub use json::{DenoVersionFile, JsonVersionFile};
56pub use project::{ProjectJsonVersionFile, ProjectTomlVersionFile, ProjectYamlVersionFile};
57pub use pubspec::PubspecVersionFile;
58pub use pyproject::PyprojectVersionFile;
59pub use regex_engine::RegexVersionFile;
60pub use version_file::{
61 CustomVersionFile, DetectedFile, UpdateResult, VersionFile, VersionFileError,
62 detect_version_files, update_version_files,
63};
64pub use version_plain::PlainVersionFile;
65
66/// Replace the `version` value in a TOML string's `[package]` section while
67/// preserving formatting.
68///
69/// Scans for the first `version = "..."` line under `[package]` and rewrites
70/// just the value. Lines in other sections (e.g. `[dependencies]`) are left
71/// untouched.
72///
73/// # Errors
74///
75/// Returns an error if no `version` field is found under `[package]`.
76///
77/// # Example
78///
79/// ```
80/// let toml = r#"[package]
81/// name = "my-crate"
82/// version = "0.1.0"
83///
84/// [dependencies]
85/// serde = { version = "1.0" }
86/// "#;
87///
88/// let updated = standard_version::replace_version_in_toml(toml, "2.0.0").unwrap();
89/// assert!(updated.contains(r#"version = "2.0.0""#));
90/// // dependency version unchanged
91/// assert!(updated.contains(r#"serde = { version = "1.0" }"#));
92/// ```
93pub fn replace_version_in_toml(
94 content: &str,
95 new_version: &str,
96) -> Result<String, VersionFileError> {
97 CargoVersionFile.write_version(content, new_version)
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn replace_version_in_toml_basic() {
106 let input = r#"[package]
107name = "my-crate"
108version = "0.1.0"
109edition = "2021"
110"#;
111 let result = replace_version_in_toml(input, "1.0.0").unwrap();
112 assert!(result.contains("version = \"1.0.0\""));
113 assert!(result.contains("name = \"my-crate\""));
114 assert!(result.contains("edition = \"2021\""));
115 }
116
117 #[test]
118 fn replace_version_only_in_package_section() {
119 let input = r#"[package]
120name = "my-crate"
121version = "0.1.0"
122
123[dependencies]
124foo = { version = "1.0" }
125"#;
126 let result = replace_version_in_toml(input, "2.0.0").unwrap();
127 assert!(result.contains("[package]"));
128 assert!(result.contains("version = \"2.0.0\""));
129 // Dependency version should be unchanged.
130 assert!(result.contains("foo = { version = \"1.0\" }"));
131 }
132}