Skip to main content

batuta/stack/
releaser_types.rs

1//! Release Orchestrator Types
2//!
3//! Core types for release orchestration extracted from releaser.rs.
4//! Includes BumpType, ReleaseConfig, ReleaseResult, ReleasedCrate.
5
6use crate::stack::types::*;
7
8/// Bump type for version updates
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum BumpType {
11    /// Increment patch version (0.0.x)
12    Patch,
13    /// Increment minor version (0.x.0)
14    Minor,
15    /// Increment major version (x.0.0)
16    Major,
17}
18
19impl BumpType {
20    /// Apply bump to a version
21    pub fn apply(&self, version: &semver::Version) -> semver::Version {
22        match self {
23            BumpType::Patch => {
24                semver::Version::new(version.major, version.minor, version.patch + 1)
25            }
26            BumpType::Minor => semver::Version::new(version.major, version.minor + 1, 0),
27            BumpType::Major => semver::Version::new(version.major + 1, 0, 0),
28        }
29    }
30}
31
32/// Configuration for release orchestration
33#[derive(Debug, Clone)]
34pub struct ReleaseConfig {
35    /// Version bump type
36    pub bump_type: Option<BumpType>,
37
38    /// Whether to skip quality gates
39    pub no_verify: bool,
40
41    /// Whether this is a dry run
42    pub dry_run: bool,
43
44    /// Whether to actually publish to crates.io
45    pub publish: bool,
46
47    /// Minimum coverage percentage required
48    pub min_coverage: f64,
49
50    /// Lint command to run
51    pub lint_command: String,
52
53    /// Coverage command to run
54    pub coverage_command: String,
55
56    /// PMAT comply command for defect detection (CB-XXX violations)
57    pub comply_command: String,
58
59    /// Whether to fail on PMAT comply violations
60    pub fail_on_comply_violations: bool,
61
62    // =========================================================================
63    // PMAT Quality Gate Integration (PMAT-STACK-GATES)
64    // =========================================================================
65    /// PMAT quality-gate command for comprehensive checks
66    pub quality_gate_command: String,
67
68    /// Whether to fail on quality gate violations
69    pub fail_on_quality_gate: bool,
70
71    /// PMAT TDG (Technical Debt Grading) command
72    pub tdg_command: String,
73
74    /// Minimum TDG score required (0-100)
75    pub min_tdg_score: f64,
76
77    /// Whether to fail on TDG score below threshold
78    pub fail_on_tdg: bool,
79
80    /// PMAT dead-code analysis command
81    pub dead_code_command: String,
82
83    /// Whether to fail on dead code detection
84    pub fail_on_dead_code: bool,
85
86    /// PMAT complexity analysis command
87    pub complexity_command: String,
88
89    /// Maximum cyclomatic complexity allowed
90    pub max_complexity: u32,
91
92    /// Whether to fail on complexity violations
93    pub fail_on_complexity: bool,
94
95    /// PMAT SATD (Self-Admitted Technical Debt) command
96    pub satd_command: String,
97
98    /// Maximum SATD items allowed
99    pub max_satd_items: u32,
100
101    /// Whether to fail on SATD violations
102    pub fail_on_satd: bool,
103
104    /// PMAT Popper score command (falsifiability)
105    pub popper_command: String,
106
107    /// Minimum Popper score required (0-100)
108    pub min_popper_score: f64,
109
110    /// Whether to fail on Popper score below threshold
111    pub fail_on_popper: bool,
112
113    // =========================================================================
114    // Book and Examples Verification (RELEASE-DOCS)
115    // =========================================================================
116    /// Book build command (e.g., "mdbook build book")
117    pub book_command: String,
118
119    /// Whether to fail on book build errors
120    pub fail_on_book: bool,
121
122    /// Examples verification command pattern (e.g., "cargo run --example")
123    /// Will run for each example found in the project
124    pub examples_command: String,
125
126    /// Whether to fail on example execution errors
127    pub fail_on_examples: bool,
128}
129
130impl Default for ReleaseConfig {
131    fn default() -> Self {
132        Self {
133            bump_type: None,
134            no_verify: false,
135            dry_run: false,
136            publish: false,
137            min_coverage: 90.0,
138            lint_command: "make lint".to_string(),
139            coverage_command: "make coverage".to_string(),
140            comply_command: "pmat comply".to_string(),
141            fail_on_comply_violations: true,
142            // PMAT Quality Gate Integration defaults
143            quality_gate_command: "pmat quality-gate".to_string(),
144            fail_on_quality_gate: true,
145            tdg_command: "pmat tdg --format json".to_string(),
146            min_tdg_score: 80.0,
147            fail_on_tdg: true,
148            dead_code_command: "pmat analyze dead-code --format json".to_string(),
149            fail_on_dead_code: false, // Warning only by default
150            complexity_command: "pmat analyze complexity --format json".to_string(),
151            max_complexity: 20,
152            fail_on_complexity: true,
153            satd_command: "pmat analyze satd --format json".to_string(),
154            max_satd_items: 10,
155            fail_on_satd: false, // Warning only by default
156            popper_command: "pmat popper-score --format json".to_string(),
157            min_popper_score: 60.0,
158            fail_on_popper: true,
159            // Book and Examples Verification defaults
160            book_command: "mdbook build book".to_string(),
161            fail_on_book: true,
162            examples_command: "cargo run --example".to_string(),
163            fail_on_examples: true,
164        }
165    }
166}
167
168/// Result of a release execution
169#[derive(Debug, Clone)]
170pub struct ReleaseResult {
171    /// Whether the release succeeded
172    pub success: bool,
173
174    /// Crates that were released
175    pub released_crates: Vec<ReleasedCrate>,
176
177    /// Message describing the result
178    pub message: String,
179}
180
181/// Information about a released crate
182#[derive(Debug, Clone)]
183pub struct ReleasedCrate {
184    /// Crate name
185    pub name: String,
186
187    /// Released version
188    pub version: semver::Version,
189
190    /// Whether it was published to crates.io
191    pub published: bool,
192}
193
194/// Format a release plan as text
195pub fn format_plan_text(plan: &ReleasePlan) -> String {
196    let mut output = String::new();
197
198    if plan.dry_run {
199        output.push_str("📋 Release Plan (DRY RUN)\n");
200    } else {
201        output.push_str("📋 Release Plan\n");
202    }
203    output.push_str(&"═".repeat(50));
204    output.push_str("\n\n");
205
206    output.push_str("Release order (topological):\n");
207    for (i, release) in plan.releases.iter().enumerate() {
208        output.push_str(&format!(
209            "  {}. {} {} → {}\n",
210            i + 1,
211            release.crate_name,
212            release.current_version,
213            release.new_version
214        ));
215    }
216
217    output.push_str("\nPre-flight status:\n");
218    for release in &plan.releases {
219        let status = plan
220            .preflight_results
221            .get(&release.crate_name)
222            .map(|r| if r.passed { "✓" } else { "✗" })
223            .unwrap_or("?");
224
225        output.push_str(&format!("  {} {}\n", status, release.crate_name));
226    }
227
228    output
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    // ============================================================================
236    // BumpType Tests
237    // ============================================================================
238
239    #[test]
240    fn test_bump_type_patch() {
241        let version = semver::Version::new(1, 2, 3);
242        let bumped = BumpType::Patch.apply(&version);
243        assert_eq!(bumped, semver::Version::new(1, 2, 4));
244    }
245
246    #[test]
247    fn test_bump_type_minor() {
248        let version = semver::Version::new(1, 2, 3);
249        let bumped = BumpType::Minor.apply(&version);
250        assert_eq!(bumped, semver::Version::new(1, 3, 0));
251    }
252
253    #[test]
254    fn test_bump_type_major() {
255        let version = semver::Version::new(1, 2, 3);
256        let bumped = BumpType::Major.apply(&version);
257        assert_eq!(bumped, semver::Version::new(2, 0, 0));
258    }
259
260    #[test]
261    fn test_bump_type_clone() {
262        let bump = BumpType::Minor;
263        let cloned = bump;
264        assert_eq!(bump, cloned);
265    }
266
267    // ============================================================================
268    // ReleaseConfig Tests
269    // ============================================================================
270
271    #[test]
272    fn test_release_config_default() {
273        let config = ReleaseConfig::default();
274        assert!(config.bump_type.is_none());
275        assert!(!config.no_verify);
276        assert!(!config.dry_run);
277        assert!(!config.publish);
278        assert_eq!(config.min_coverage, 90.0);
279    }
280
281    #[test]
282    fn test_release_config_pmat_defaults() {
283        let config = ReleaseConfig::default();
284        assert_eq!(config.quality_gate_command, "pmat quality-gate");
285        assert!(config.fail_on_quality_gate);
286        assert_eq!(config.min_tdg_score, 80.0);
287        assert!(config.fail_on_tdg);
288        assert!(!config.fail_on_dead_code); // Warning only by default
289        assert_eq!(config.max_complexity, 20);
290        assert!(config.fail_on_complexity);
291        assert!(!config.fail_on_satd); // Warning only by default
292        assert_eq!(config.min_popper_score, 60.0);
293        assert!(config.fail_on_popper);
294    }
295
296    #[test]
297    fn test_release_config_book_defaults() {
298        let config = ReleaseConfig::default();
299        assert_eq!(config.book_command, "mdbook build book");
300        assert!(config.fail_on_book);
301        assert_eq!(config.examples_command, "cargo run --example");
302        assert!(config.fail_on_examples);
303    }
304
305    #[test]
306    fn test_release_config_custom_thresholds() {
307        let config = ReleaseConfig {
308            min_tdg_score: 90.0,
309            min_popper_score: 70.0,
310            max_complexity: 15,
311            max_satd_items: 5,
312            fail_on_dead_code: true,
313            fail_on_satd: true,
314            ..Default::default()
315        };
316
317        assert_eq!(config.min_tdg_score, 90.0);
318        assert_eq!(config.min_popper_score, 70.0);
319        assert_eq!(config.max_complexity, 15);
320        assert_eq!(config.max_satd_items, 5);
321        assert!(config.fail_on_dead_code);
322        assert!(config.fail_on_satd);
323    }
324
325    #[test]
326    fn test_release_config_disabled_checks() {
327        let config = ReleaseConfig {
328            quality_gate_command: String::new(),
329            tdg_command: String::new(),
330            dead_code_command: String::new(),
331            complexity_command: String::new(),
332            satd_command: String::new(),
333            popper_command: String::new(),
334            ..Default::default()
335        };
336
337        assert!(config.quality_gate_command.is_empty());
338        assert!(config.tdg_command.is_empty());
339        assert!(config.dead_code_command.is_empty());
340        assert!(config.complexity_command.is_empty());
341        assert!(config.satd_command.is_empty());
342        assert!(config.popper_command.is_empty());
343    }
344
345    // ============================================================================
346    // ReleaseResult Tests
347    // ============================================================================
348
349    #[test]
350    fn test_release_result_success() {
351        let result = ReleaseResult {
352            success: true,
353            released_crates: vec![ReleasedCrate {
354                name: "test".to_string(),
355                version: semver::Version::new(1, 0, 0),
356                published: true,
357            }],
358            message: "Success".to_string(),
359        };
360        assert!(result.success);
361        assert_eq!(result.released_crates.len(), 1);
362    }
363
364    #[test]
365    fn test_released_crate_clone() {
366        let crate_info = ReleasedCrate {
367            name: "test".to_string(),
368            version: semver::Version::new(1, 0, 0),
369            published: true,
370        };
371        let cloned = crate_info.clone();
372        assert_eq!(crate_info.name, cloned.name);
373        assert_eq!(crate_info.version, cloned.version);
374        assert_eq!(crate_info.published, cloned.published);
375    }
376}