use std::path::Path;
use colored::Colorize;
use crate::affected::{compute_affected, get_changed_files};
use crate::config::{TargetName, WorkspaceConfig};
use crate::discovery::{discover_projects, find_workspace_root};
use crate::error::AffectedError;
use crate::graph::{ProjectGraph, TaskGraph};
use crate::runner::{RunResult, TaskRunner};
pub async fn run_affected(
cwd: &Path,
target: &str,
base_branch: &str,
) -> Result<RunResult, AffectedError> {
let target_name: TargetName = target.parse().map_err(|e| AffectedError::InvalidTarget {
target: target.to_string(),
reason: format!("{e}"),
})?;
let root = find_workspace_root(cwd).map_err(|e| AffectedError::WorkspaceNotFound {
path: cwd.to_path_buf(),
reason: format!("{e}"),
})?;
let workspace = WorkspaceConfig::from_file(&root.join("guild.toml")).map_err(|e| {
AffectedError::ConfigError {
path: root.join("guild.toml"),
reason: format!("{e}"),
}
})?;
let all_projects = discover_projects(&workspace).map_err(|e| AffectedError::ConfigError {
path: root.clone(),
reason: format!("{e}"),
})?;
if all_projects.is_empty() {
return Ok(RunResult {
success_count: 0,
failure_count: 0,
skipped_count: 0,
cached_count: 0,
task_results: vec![],
total_duration: std::time::Duration::ZERO,
});
}
let full_project_graph =
ProjectGraph::build(all_projects.clone()).map_err(|e| AffectedError::GraphError {
reason: format!("{e}"),
})?;
let changed_files = get_changed_files(&root, base_branch)?;
let affected = compute_affected(&changed_files, &all_projects, &full_project_graph);
if affected.all.is_empty() {
println!(
"\n{} No projects affected since '{}'\n",
"guild".cyan().bold(),
base_branch
);
return Ok(RunResult {
success_count: 0,
failure_count: 0,
skipped_count: 0,
cached_count: 0,
task_results: vec![],
total_duration: std::time::Duration::ZERO,
});
}
let affected_projects: Vec<_> = all_projects
.into_iter()
.filter(|p| affected.all.contains(p.name()))
.collect();
let affected_graph =
ProjectGraph::build(affected_projects).map_err(|e| AffectedError::GraphError {
reason: format!("{e}"),
})?;
let task_graph =
TaskGraph::build(&affected_graph, &target_name).map_err(|e| AffectedError::GraphError {
reason: format!("{e}"),
})?;
let task_count = task_graph.len();
if task_count == 0 {
println!(
"{} No affected projects have target '{target}'",
"warning:".yellow().bold()
);
return Ok(RunResult {
success_count: 0,
failure_count: 0,
skipped_count: 0,
cached_count: 0,
task_results: vec![],
total_duration: std::time::Duration::ZERO,
});
}
println!(
"\n{} Running {} task{} for target '{}' on {} affected project{}\n",
"guild".cyan().bold(),
task_count,
if task_count == 1 { "" } else { "s" },
target,
affected.all.len(),
if affected.all.len() == 1 { "" } else { "s" }
);
if !affected.changed.is_empty() {
let changed_names: Vec<String> = affected.changed.iter().map(|n| n.to_string()).collect();
println!(" {} Changed: {}", "~".yellow(), changed_names.join(", "));
}
if !affected.dependents.is_empty() {
let dep_names: Vec<String> = affected.dependents.iter().map(|n| n.to_string()).collect();
println!(" {} Dependents: {}", "~".cyan(), dep_names.join(", "));
}
println!();
let concurrency = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let runner = TaskRunner::new(concurrency, root);
let result =
runner
.run(task_graph, &affected_graph)
.await
.map_err(|e| AffectedError::GraphError {
reason: format!("{e}"),
})?;
print_summary(&result, target);
Ok(result)
}
fn print_summary(result: &RunResult, target: &str) {
println!();
let duration_str = format!("{:.2}s", result.total_duration.as_secs_f64());
if result.is_success() {
if result.success_count == 0 {
println!(
"{} No tasks executed for target '{target}'",
"warning:".yellow().bold()
);
} else {
println!(
"{} {} {} completed successfully in {}",
"Success".green().bold(),
result.success_count,
if result.success_count == 1 {
"task"
} else {
"tasks"
},
duration_str
);
}
} else {
let mut parts = Vec::new();
if result.success_count > 0 {
parts.push(format!("{} {}", result.success_count, "succeeded".green()));
}
parts.push(format!("{} {}", result.failure_count, "failed".red()));
if result.skipped_count > 0 {
parts.push(format!("{} {}", result.skipped_count, "skipped".yellow()));
}
println!(
"{} {} in {}",
"Failed".red().bold(),
parts.join(", "),
duration_str
);
}
}