use crate::application::extract_deps::extract_deps_for_file;
use crate::application::runtime::InvocationContext;
use crate::error::CarryCtxError;
use crate::repository::GraphRepository;
use std::path::Path;
use std::process::Command;
pub const DEFAULT_EXTENSIONS: &[&str] = &["rs", "ts", "js", "tsx", "jsx"];
pub struct ScanResult {
pub scanned: usize,
pub skipped: usize,
pub nodes_created: usize,
pub edges_created: usize,
pub errors: Vec<ScanError>,
}
pub struct ScanError {
pub file: String,
pub message: String,
}
pub fn scan_project(
dir: &Path,
extensions: &[&str],
dry_run: bool,
repo: &GraphRepository,
ctx: &InvocationContext,
) -> Result<ScanResult, CarryCtxError> {
let output = Command::new("git")
.args(["ls-files", "--cached", "--others", "--exclude-standard"])
.current_dir(dir)
.output()
.map_err(|e| CarryCtxError::git_error(format!("Failed to run git ls-files: {e}")))?;
if !output.status.success() {
return Err(CarryCtxError::git_error(format!(
"git ls-files failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let files: Vec<&str> = stdout
.lines()
.filter(|f| {
let p = Path::new(f);
let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("");
extensions.contains(&ext)
})
.collect();
let mut result = ScanResult {
scanned: 0,
skipped: 0,
nodes_created: 0,
edges_created: 0,
errors: vec![],
};
let nodes_before = count_nodes(repo);
for file in &files {
result.scanned += 1;
if dry_run {
if !dir.join(file).exists() {
result.skipped += 1;
}
continue;
}
let file_path = dir.join(file);
let file_str_raw = file_path.to_string_lossy();
let file_str = file_str_raw
.strip_prefix("./")
.unwrap_or(&file_str_raw)
.to_string();
match extract_deps_for_file(&file_str, repo, ctx) {
Ok(edges) => {
result.edges_created += edges.len();
}
Err(e) => {
result.errors.push(ScanError {
file: file.to_string(),
message: e.message.clone(),
});
}
}
}
if !dry_run {
let nodes_after = count_nodes(repo);
result.nodes_created = nodes_after.saturating_sub(nodes_before);
}
Ok(result)
}
fn count_nodes(repo: &GraphRepository) -> usize {
repo.count_nodes().unwrap_or(0)
}