use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use crate::cli::CheckVersionBumpArgs;
use crate::commands::{ignores, lint};
use crate::domain::versionbump;
use crate::errors::{Error, Result};
use crate::io::diff::DiffBackend;
use crate::io::{configfs, diff, files};
pub fn run(args: CheckVersionBumpArgs) -> Result<i32> {
let cwd = lint::resolve_cwd(&args.cwd)?;
let cli_files = files::from_cli(&cwd, &args.files);
let backend = args.diff.unwrap_or_default();
let checked = check(&cwd, &cli_files, backend, args.diff_base.clone())?;
println!("llmlint: versioned configs OK ({checked} file(s) checked)");
Ok(0)
}
pub(crate) fn check(
cwd: &Path,
cli_files: &[PathBuf],
backend: DiffBackend,
base: Option<String>,
) -> Result<usize> {
let versioned = versioned_targets(cwd, cli_files)?;
if versioned.is_empty() {
return Ok(0);
}
let files_vec: Vec<PathBuf> = versioned.iter().cloned().collect();
let diffs = diff::provider(backend, base).diffs(cwd, &files_vec)?;
let mut offenders: Vec<String> = Vec::new();
for rel in &versioned {
if versionbump::changed_without_bump(diffs.get(rel).map(String::as_str)) {
offenders.push(format!(" {}", files::to_slash(rel)));
}
}
if offenders.is_empty() {
Ok(versioned.len())
} else {
Err(Error::VersionBump(offenders.join("\n")))
}
}
fn versioned_targets(cwd: &Path, cli_files: &[PathBuf]) -> Result<BTreeSet<PathBuf>> {
let candidates: BTreeSet<PathBuf> = if cli_files.is_empty() {
let loaded = configfs::load_config_lint(cwd)?;
ignores::target_files(cwd, &loaded.config, &loaded.scopes, &[])?
} else {
cli_files.iter().cloned().collect()
};
let mut out = BTreeSet::new();
for rel in candidates {
if let Some(text) = files::read_text(cwd, &rel)? {
if versionbump::declares_version(&text) {
out.insert(rel);
}
}
}
Ok(out)
}