mod cli;
use anyhow::Result;
use clap::Parser;
use cli::{CacheCommand, Cli, Commands};
use guild_cli::{
Cache, ProjectGraph, WorkspaceConfig, discover_projects, find_workspace_root, print_error,
print_header, print_project_entry, print_success, print_warning, run_affected, run_init,
run_target,
};
#[tokio::main]
async fn main() {
let cli = Cli::parse();
if let Err(e) = run(cli).await {
print_error(&format!("{e}"));
std::process::exit(1);
}
}
async fn run(cli: Cli) -> Result<()> {
match cli.command {
None => {
use clap::CommandFactory;
Cli::command().print_help()?;
println!();
}
Some(Commands::List) => {
let cwd = std::env::current_dir()?;
let root = find_workspace_root(&cwd)?;
let workspace = WorkspaceConfig::from_file(&root.join("guild.toml"))?;
let projects = discover_projects(&workspace)?;
print_header(&format!("Workspace: {}", workspace.name()));
println!(" {} projects discovered\n", projects.len());
for project in &projects {
print_project_entry(
project.name().as_str(),
&project.root().display().to_string(),
project.tags(),
);
}
}
Some(Commands::Graph) => {
let cwd = std::env::current_dir()?;
let root = find_workspace_root(&cwd)?;
let workspace = WorkspaceConfig::from_file(&root.join("guild.toml"))?;
let projects = discover_projects(&workspace)?;
let graph = ProjectGraph::build(projects)?;
print_header("Project Dependency Graph");
let order = graph.topological_order()?;
for name in &order {
let deps = graph.dependencies(name).unwrap();
if deps.is_empty() {
println!(" {name}");
} else {
let dep_names: Vec<String> = deps.iter().map(|d| d.to_string()).collect();
println!(" {name} -> {}", dep_names.join(", "));
}
}
}
Some(Commands::Dev) => {
let cwd = std::env::current_dir()?;
let result = run_target(&cwd, "dev", None).await?;
if !result.is_success() {
std::process::exit(1);
}
}
Some(Commands::Build) => {
let cwd = std::env::current_dir()?;
let result = run_target(&cwd, "build", None).await?;
if !result.is_success() {
std::process::exit(1);
}
}
Some(Commands::Test) => {
let cwd = std::env::current_dir()?;
let result = run_target(&cwd, "test", None).await?;
if !result.is_success() {
std::process::exit(1);
}
}
Some(Commands::Lint) => {
let cwd = std::env::current_dir()?;
let result = run_target(&cwd, "lint", None).await?;
if !result.is_success() {
std::process::exit(1);
}
}
Some(Commands::Run { target, project }) => {
let cwd = std::env::current_dir()?;
let result = run_target(&cwd, &target, project.as_deref()).await?;
if !result.is_success() {
std::process::exit(1);
}
}
Some(Commands::Affected { target, base }) => {
let cwd = std::env::current_dir()?;
let result = run_affected(&cwd, &target, &base).await?;
if !result.is_success() {
std::process::exit(1);
}
}
Some(Commands::Cache { command }) => {
let cwd = std::env::current_dir()?;
let root = find_workspace_root(&cwd)?;
let cache = Cache::new(&root);
match command {
CacheCommand::Status => {
let stats = cache.stats()?;
print_header("Cache Status");
println!(" Cache directory: {}", cache.cache_dir().display());
println!(" Entries: {}", stats.entry_count);
println!(" Total size: {}", format_size(stats.total_size));
}
CacheCommand::Clean => {
if !cache.cache_dir().exists() {
print_warning("Cache directory does not exist, nothing to clean");
} else {
let removed = cache.clean()?;
print_success(&format!("Removed {removed} cache entries"));
}
}
}
}
Some(Commands::Init { yes }) => {
let cwd = std::env::current_dir()?;
let workspace_name = cwd
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "workspace".to_string());
print_header(&format!("Initializing Guild workspace: {workspace_name}"));
let result = run_init(&cwd, &workspace_name, yes)?;
println!();
if result.written.is_empty() && result.skipped.is_empty() {
print_success(
"No projects detected. Create project manifests first (package.json, Cargo.toml, go.mod, or pyproject.toml).",
);
} else {
print_success(&format!(
"Initialized {} guild.toml file(s), skipped {} existing",
result.written.len(),
result.skipped.len()
));
}
}
}
Ok(())
}
fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{bytes} bytes")
}
}