Skip to main content

qtcloud_devops_cli/
contract.rs

1/// 契约模块 — 基于 `quanttide-devops` toolkit 的适配层。
2pub use quanttide_devops::contract::{
3    detect_language_by_files, normalize_version, read_all_config_versions, validate_version,
4    BuildTool, Contract, Language, Pipeline, Platform, Registry, Scope, SourceControl, SourceType,
5    StageBuild, StageRelease, StageTest, VersionSource,
6};
7pub use quanttide_devops::source::git::{GitSourceError, VersionStatus};
8
9use std::path::Path;
10
11// ═══════════════════════════════════════════════════════════════════════
12// 加载(保留向后兼容的行为)
13// ═══════════════════════════════════════════════════════════════════════
14
15pub fn load(repo_path: &Path) -> Contract {
16    match quanttide_devops::contract::load(repo_path) {
17        Ok(c) => c,
18        Err(e) => {
19            eprintln!("  ℹ contract.yaml: {},使用默认契约", e);
20            Contract::default()
21        }
22    }
23}
24
25pub fn load_scopes(repo_path: &Path) -> Vec<Scope> {
26    load(repo_path).scopes
27}
28
29pub fn detect_by_files(dir: &Path) -> Language {
30    detect_language_by_files(dir)
31}
32
33// ═══════════════════════════════════════════════════════════════════════
34// 版本状态(适配 toolkit 的 Result → 旧签名)
35// ═══════════════════════════════════════════════════════════════════════
36
37/// 检查 scope 版本一致性。失败时返回空的 VersionStatus。
38pub fn version_status(repo_path: &Path, scope: &Scope) -> VersionStatus {
39    quanttide_devops::source::git::version_status(repo_path, scope).unwrap_or_else(|e| {
40        eprintln!("  ⚠ 版本状态检查失败: {}", e);
41        VersionStatus {
42            tag_version: None,
43            config_version: None,
44            consistent: false,
45            config_files: vec![],
46        }
47    })
48}
49
50/// 显示当前契约的完整状态(调试/查看用)。
51pub fn status(repo_path: &Path) {
52    let mut stdout = std::io::stdout();
53    status_to(&mut stdout, repo_path).ok();
54}
55
56/// 写入指定 writer 的版本(可测试)。
57pub fn status_to(writer: &mut impl std::io::Write, repo_path: &Path) -> std::io::Result<()> {
58    let contract_path = repo_path.join(".quanttide/devops/contract.yaml");
59    let exists = contract_path.exists();
60
61    let c = load(repo_path);
62
63    writeln!(writer, "契约状态")?;
64    writeln!(writer, "{}", "-".repeat(40))?;
65
66    if exists {
67        writeln!(writer, "  配置文件:  {}", contract_path.display())?;
68        writeln!(writer, "  状态:      ✅ 已加载")?;
69    } else {
70        writeln!(writer, "  配置文件:  未找到,使用默认契约")?;
71        writeln!(writer, "  状态:      ⚠ 默认配置")?;
72    }
73    writeln!(writer)?;
74
75    // Stages
76    writeln!(writer, "  Stages:")?;
77    let b = &c.stages.build;
78    writeln!(
79        writer,
80        "    build:    {}",
81        b.command.as_deref().unwrap_or("—")
82    )?;
83    let t = &c.stages.test;
84    writeln!(
85        writer,
86        "    test:     {}(阈值 {}%)",
87        t.command.as_deref().unwrap_or("—"),
88        t.threshold
89    )?;
90    let r = &c.stages.release;
91    writeln!(
92        writer,
93        "    release:  {}(pre_publish: {:?})",
94        r.changelog, r.pre_publish
95    )?;
96    writeln!(writer)?;
97
98    // Platform
99    writeln!(writer, "  Platform:")?;
100    writeln!(
101        writer,
102        "    source_control:   {:?}",
103        c.platform.source_control
104    )?;
105    writeln!(writer, "    pipeline:         {:?}", c.platform.pipeline)?;
106    writeln!(
107        writer,
108        "    artifact_registry: {}",
109        c.platform.artifact_registry
110    )?;
111    writeln!(writer)?;
112
113    // Sources
114    writeln!(writer, "  Sources:")?;
115    writeln!(
116        writer,
117        "    version:  {:?} {:?}",
118        c.sources.version.source_type, c.sources.version.path
119    )?;
120    writeln!(writer)?;
121
122    // Scopes
123    writeln!(writer, "  Scopes:  {} 个", c.scopes.len())?;
124    if c.scopes.is_empty() {
125        writeln!(writer, "    未定义 scope")?;
126    } else {
127        for s in &c.scopes {
128            writeln!(
129                writer,
130                "    {:<12} dir: {:<24} {} / {}",
131                s.name,
132                s.dir,
133                s.language.as_str(),
134                s.build_tool.as_str()
135            )?;
136        }
137    }
138    Ok(())
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_version_status_git_error_returns_fallback() {
147        let scope = Scope {
148            name: "test".into(),
149            dir: ".".into(),
150            language: Language::Rust,
151            framework: String::new(),
152            build_tool: BuildTool::Cargo,
153            registry: Registry::None,
154            release: StageRelease::default(),
155            test_threshold: None,
156            ci_workflow: None,
157        };
158        let vs = version_status(Path::new("/nonexistent"), &scope);
159        assert!(!vs.consistent);
160        assert_eq!(vs.tag_version, None);
161        assert_eq!(vs.config_version, None);
162        assert!(vs.config_files.is_empty());
163    }
164
165    #[test]
166    fn test_status_to_with_contract() {
167        let d = tempfile::tempdir().unwrap();
168        // 创建 contract.yaml
169        let contract_dir = d.path().join(".quanttide/devops");
170        std::fs::create_dir_all(&contract_dir).unwrap();
171        std::fs::write(
172            contract_dir.join("contract.yaml"),
173            "scopes:\n  test:\n    dir: .\n    language: rust\n",
174        )
175        .unwrap();
176        let mut buf = Vec::new();
177        status_to(&mut buf, d.path()).unwrap();
178        let out = String::from_utf8_lossy(&buf);
179        assert!(out.contains("✅ 已加载"));
180        assert!(out.contains("test"));
181        assert!(out.contains("rust"));
182    }
183
184    #[test]
185    fn test_status_to_without_contract() {
186        let d = tempfile::tempdir().unwrap();
187        let mut buf = Vec::new();
188        status_to(&mut buf, d.path()).unwrap();
189        let out = String::from_utf8_lossy(&buf);
190        assert!(out.contains("⚠ 默认配置"));
191        assert!(out.contains("未定义 scope"));
192    }
193}