ferrflow 7.13.3

Universal semantic versioning for monorepos and classic repos
Documentation
use std::collections::BTreeMap;
use std::path::Path;

use crate::config::Config;

#[cfg(feature = "cli")]
use crate::error_code::{self, ErrorCodeExt};
#[cfg(feature = "cli")]
use crate::git::{get_repo_root, open_repo};
#[cfg(feature = "cli")]
use anyhow::Result;

mod checks;
#[cfg(feature = "cli")]
mod output;
mod result;
mod source;

#[allow(unused_imports)]
pub use result::{EntryOutput, ValidationEntry, ValidationLevel, ValidationResult};
pub use source::{FileSource, LocalSource, MemorySource, load_config_from_source};
#[cfg(feature = "cli")]
pub use source::{GitHubSource, GitLabSource, RemoteProvider, parse_repo_spec};

use checks::{
    check_changelog_paths, check_duplicate_names, check_duplicate_paths, check_groups,
    check_package_paths, check_shared_paths, check_suggestions, check_tag_templates,
    check_version_consistency, check_version_templates, check_versioned_files,
    check_versioned_files_exist,
};
#[cfg(feature = "cli")]
use output::output_result;

#[cfg(feature = "cli")]
pub fn run(
    config_path: Option<&Path>,
    json: bool,
    repo: Option<&str>,
    git_ref: Option<&str>,
) -> Result<()> {
    if git_ref.is_some() && repo.is_none() {
        Err(anyhow::anyhow!("--ref requires --repo"))
            .error_code(error_code::VALIDATE_REF_REQUIRES_REPO)?;
    }

    let source: Box<dyn FileSource> = if let Some(repo_spec) = repo {
        let (provider, owner, repo_name) = parse_repo_spec(repo_spec)?;
        match provider {
            RemoteProvider::GitHub => Box::new(GitHubSource {
                owner,
                repo: repo_name,
                git_ref: git_ref.map(|s| s.to_string()),
                token: std::env::var("FERRFLOW_TOKEN")
                    .ok()
                    .or_else(|| std::env::var("GITHUB_TOKEN").ok()),
            }),
            RemoteProvider::GitLab => Box::new(GitLabSource {
                owner,
                repo: repo_name,
                git_ref: git_ref.map(|s| s.to_string()),
                token: std::env::var("FERRFLOW_TOKEN")
                    .ok()
                    .or_else(|| std::env::var("GITLAB_TOKEN").ok()),
            }),
        }
    } else {
        let repo = open_repo(&std::env::current_dir()?)?;
        let root = get_repo_root(&repo)?;
        Box::new(LocalSource { root })
    };

    let config_path_str = config_path.and_then(|p| p.to_str());

    let (config, config_filename) = match load_config_from_source(source.as_ref(), config_path_str)
    {
        Ok(result) => result,
        Err(e) => {
            let mut result = ValidationResult::from_entries(vec![ValidationEntry {
                level: ValidationLevel::Error,
                path: "(config)".to_string(),
                message: e.to_string(),
            }]);
            result.config_file = None;
            return output_result(&result, json);
        }
    };

    let entries = collect_entries(&config, source.as_ref());

    let mut result = ValidationResult::from_entries(entries);
    result.config_file = Some(config_filename);
    result.package_count = config.packages.len();
    output_result(&result, json)
}

fn collect_entries(
    config: &crate::config::Config,
    source: &dyn FileSource,
) -> Vec<ValidationEntry> {
    let mut entries = Vec::new();
    entries.extend(check_duplicate_names(config));
    entries.extend(check_duplicate_paths(config));
    entries.extend(check_tag_templates(config));
    entries.extend(check_version_templates(config));
    entries.extend(check_package_paths(config, source));
    entries.extend(check_versioned_files_exist(config, source));
    let (file_entries, versions) = check_versioned_files(config, source);
    entries.extend(file_entries);
    entries.extend(check_version_consistency(&versions));
    entries.extend(check_changelog_paths(config, source));
    entries.extend(check_shared_paths(config, source));
    entries.extend(check_groups(config, &versions));
    entries.extend(check_suggestions(config));
    entries
}

#[cfg_attr(feature = "cli", allow(dead_code))]
pub fn validate_files(config: &Config, files: BTreeMap<String, Vec<u8>>) -> ValidationResult {
    let source = MemorySource::new(files);
    let mut result = ValidationResult::from_entries(collect_entries(config, &source));
    result.package_count = config.packages.len();
    result
}

pub fn local_entries(config: &Config, root: &Path) -> Vec<ValidationEntry> {
    let source = LocalSource {
        root: root.to_path_buf(),
    };
    collect_entries(config, &source)
}

#[cfg(test)]
mod tests;