Skip to main content

qtcloud_devops_cli/
contract.rs

1/// 契约模块 — 基于 `quanttide-devops` toolkit 的适配层。
2///
3/// 类型定义与 YAML 解析委托给 toolkit,本模块仅保留 CLI 特有的版本检测逻辑。
4pub use quanttide_devops::contract::{
5    detect_language_by_files, normalize_version, read_all_config_versions, validate_version,
6    BuildTool, Contract, Language, Pipeline, Platform, Registry, Scope, SourceControl, SourceType,
7    StageBuild, StageRelease, StageTest, VersionSource,
8};
9
10use std::path::Path;
11
12// ═══════════════════════════════════════════════════════════════════════
13// 加载(保留向后兼容的行为)
14// ═══════════════════════════════════════════════════════════════════════
15
16/// 从 `.quanttide/devops/contract.yaml` 加载完整契约。
17///
18/// 文件不存在或解析失败时降级为默认契约(兼容旧调用方)。
19pub fn load(repo_path: &Path) -> Contract {
20    match quanttide_devops::contract::load(repo_path) {
21        Ok(c) => c,
22        Err(e) => {
23            eprintln!("  ℹ contract.yaml: {},使用默认契约", e);
24            Contract::default()
25        }
26    }
27}
28
29/// 快速加载 scope 列表。
30pub fn load_scopes(repo_path: &Path) -> Vec<Scope> {
31    load(repo_path).scopes
32}
33
34/// 根据目录下的标志文件推测编程语言(`detect_language_by_files` 的别名)。
35pub fn detect_by_files(dir: &Path) -> Language {
36    detect_language_by_files(dir)
37}
38
39// ═══════════════════════════════════════════════════════════════════════
40// 版本状态(CLI 特有,toolkit 尚不支持)
41// ═══════════════════════════════════════════════════════════════════════
42
43/// 版本一致性检查结果。
44#[derive(Debug)]
45pub struct VersionStatus {
46    pub tag_version: Option<String>,
47    pub config_version: Option<String>,
48    pub consistent: bool,
49    /// 所有配置文件的版本号明细。(文件名, 版本号)
50    pub config_files: Vec<(String, Option<String>)>,
51}
52
53/// 检查 scope 下所有已知配置文件的版本,判断与 tag 是否一致。
54pub fn version_status(repo_path: &Path, scope: &Scope) -> VersionStatus {
55    let tag_version = latest_tag_for_scope(repo_path, &scope.name);
56    let scope_dir = repo_path.join(&scope.dir);
57    let config_files = quanttide_devops::contract::read_all_config_versions(&scope_dir);
58    let config_version = config_files
59        .iter()
60        .find(|(_, v)| v.is_some())
61        .and_then(|(_, v)| v.clone());
62    let consistent = match &tag_version {
63        Some(t) => config_files.iter().all(|(_, v)| match v {
64            Some(cv) => cv == t,
65            None => true,
66        }),
67        None => config_version.is_none(),
68    };
69    VersionStatus {
70        tag_version,
71        config_version,
72        consistent,
73        config_files,
74    }
75}
76
77fn latest_tag_for_scope(repo_path: &Path, scope_name: &str) -> Option<String> {
78    let output = std::process::Command::new("git")
79        .args(["tag", "--sort=-version:refname"])
80        .current_dir(repo_path)
81        .output()
82        .ok()?;
83    if !output.status.success() {
84        return None;
85    }
86    let prefix = format!("{}/", scope_name);
87    let tags: Vec<&str> = std::str::from_utf8(&output.stdout)
88        .ok()?
89        .lines()
90        .filter(|t| t.starts_with(&prefix) || !t.contains('/'))
91        .collect();
92    let scoped = tags.iter().find(|t| t.starts_with(&prefix));
93    match scoped {
94        Some(t) => Some(normalize_version(t)),
95        None => tags.first().map(|t| normalize_version(t)),
96    }
97}