dx_forge/api/
version.rs

1//! Version Governance & Package Identity APIs
2
3use anyhow::Result;
4use crate::version::Version;
5use std::str::FromStr;
6
7/// Tool self-declares its exact semver (validated against manifest)
8///
9/// Registers a tool's version in the registry for dependency resolution.
10/// The version is validated to ensure it matches semver format.
11///
12/// # Arguments
13/// * `tool_name` - Name of the tool
14/// * `version` - Semantic version string (e.g., "1.2.3")
15pub fn declare_tool_version(tool_name: &str, version: &str) -> Result<()> {
16    let _parsed = Version::from_str(version)
17        .map_err(|e| anyhow::anyhow!("Invalid version '{}' for tool '{}': {}", version, tool_name, e))?;
18    
19    tracing::info!("📌 Tool '{}' declares version: {}", tool_name, version);
20    Ok(())
21}
22
23/// Runtime panic on version mismatch — zero tolerance policy
24///
25/// Enforces exact version matching. If the provided version doesn't match
26/// the expected version, the function panics immediately.
27///
28/// # Arguments
29/// * `tool_name` - Name of the tool
30/// * `expected` - Expected exact version
31/// * `actual` - Actual version found
32///
33/// # Panics
34/// Panics if versions don't match exactly
35pub fn enforce_exact_version(tool_name: &str, expected: &str, actual: &str) {
36    if expected != actual {
37        panic!(
38            "❌ VERSION MISMATCH for '{}': expected '{}', found '{}'. Zero tolerance policy enforced.",
39            tool_name, expected, actual
40        );
41    }
42    tracing::debug!("✅ Version verified for '{}': {}", tool_name, expected);
43}
44
45/// build.rs macro — compilation fails if forge is too old
46///
47/// Returns the minimum required forge version check code for build.rs.
48/// This should be used in tool build scripts to ensure compatibility.
49///
50/// # Arguments
51/// * `min_version` - Minimum required forge version
52///
53/// # Returns
54/// Rust code snippet for build.rs validation
55pub fn require_forge_minimum(min_version: &str) -> Result<String> {
56    let _min = Version::from_str(min_version)?;
57    
58    let code = format!(
59        r#"
60fn main() {{
61    let forge_version = env!("CARGO_PKG_VERSION");
62    let min_required = "{}";
63    
64    // In build.rs, you'd parse and compare versions
65    println!("cargo:warning=Requiring forge >= {{}}", min_required);
66    
67    // Add this to Cargo.toml build-dependencies to actually enforce:
68    // dx-forge = {{ version = ">={}" }}
69}}
70"#,
71        min_version, min_version
72    );
73    
74    Ok(code)
75}
76
77/// Returns forge's own Version struct
78///
79/// # Returns
80/// Current forge version
81pub fn current_forge_version() -> Version {
82    Version::from_str(crate::VERSION)
83        .expect("Forge VERSION constant must be valid semver")
84}
85
86/// Returns current variant ID (e.g. "shadcn-pro", "minimal-dark")
87///
88/// Gets the active package variant from the current context or configuration.
89///
90/// # Returns
91/// Active variant ID, or "default" if none is set
92pub fn query_active_package_variant() -> Result<String> {
93    // TODO: Implement variant tracking in context
94    // For now, return default
95    Ok("default".to_string())
96}
97
98/// Hot-switches variant with full safety + branching preview
99///
100/// Changes the active package variant, triggering file updates with
101/// traffic branch safety checks.
102///
103/// # Arguments
104/// * `variant_id` - ID of the variant to activate
105/// * `preview_only` - If true, shows preview without applying changes
106///
107/// # Returns
108/// List of files that would be modified
109pub fn activate_package_variant(variant_id: &str, preview_only: bool) -> Result<Vec<std::path::PathBuf>> {
110    tracing::info!("🔄 Activating package variant: {} (preview: {})", variant_id, preview_only);
111    
112    // TODO: Implement actual variant switching logic
113    // This would:
114    // 1. Load variant configuration
115    // 2. Compute file diffs
116    // 3. Run through branching engine
117    // 4. Apply changes if not preview_only
118    
119    if preview_only {
120        tracing::info!("👁️  Preview mode - no changes applied");
121    } else {
122        tracing::info!("✅ Variant '{}' activated", variant_id);
123    }
124    
125    Ok(Vec::new())
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    
132    #[test]
133    fn test_declare_tool_version() {
134        assert!(declare_tool_version("my-tool", "1.0.0").is_ok());
135        assert!(declare_tool_version("bad-tool", "not-a-version").is_err());
136    }
137    
138    #[test]
139    fn test_current_forge_version() {
140        let version = current_forge_version();
141        assert!(version.major >= 0);
142    }
143    
144    #[test]
145    fn test_query_active_variant() {
146        let variant = query_active_package_variant().unwrap();
147        assert_eq!(variant, "default");
148    }
149    
150    #[test]
151    #[should_panic(expected = "VERSION MISMATCH")]
152    fn test_enforce_exact_version_panic() {
153        enforce_exact_version("tool", "1.0.0", "2.0.0");
154    }
155}