use std::path::Path;
use anyhow::{Result, bail};
use crate::core::registry::RecipeRegistry;
use crate::core::detection::workspace::detect_workspaces;
use crate::core::ast::parser::parse_file;
use crate::core::ast::semantic::SemanticModel;
use walkdir::WalkDir;
pub fn execute(graph_type: &str, format: &str, path: &Path) -> Result<()> {
match graph_type {
"pipeline" => generate_pipeline_graph(format),
"workspace" => generate_workspace_graph(format, path),
"deps" => generate_file_deps_graph(format, path),
_ => bail!("Unknown graph type: {}", graph_type),
}
}
fn generate_pipeline_graph(format: &str) -> Result<()> {
let registry = RecipeRegistry::new();
let recipes = registry.all();
if format == "json" {
let mut nodes = Vec::new();
for recipe in recipes {
let metadata = recipe.metadata();
nodes.push(serde_json::json!({
"name": metadata.name,
"requires": metadata.required_recipes,
"incompatible": metadata.incompatible_recipes,
"should_run_before": metadata.should_run_before,
"should_run_after": metadata.should_run_after,
}));
}
println!("{}", serde_json::to_string_pretty(&nodes)?);
} else {
println!("flowchart TD");
for recipe in recipes {
let metadata = recipe.metadata();
println!(" {}", metadata.name);
for req in metadata.required_recipes {
println!(" {} --> {}", req, metadata.name);
}
for inc in metadata.incompatible_recipes {
println!(" {} -.->|incompatible| {}", metadata.name, inc);
}
for before in metadata.should_run_before {
println!(" {} -.->|before| {}", metadata.name, before);
}
for after in metadata.should_run_after {
println!(" {} -.->|after| {}", after, metadata.name);
}
}
}
Ok(())
}
fn generate_workspace_graph(format: &str, path: &Path) -> Result<()> {
let workspace = detect_workspaces(path);
if format == "json" {
println!("{}", serde_json::to_string_pretty(&workspace)?);
} else {
println!("flowchart LR");
println!(" subgraph Workspace");
for pkg in &workspace.packages {
println!(" {}", pkg.name);
}
println!(" end");
}
Ok(())
}
fn generate_file_deps_graph(format: &str, path: &Path) -> Result<()> {
let mut edges: Vec<(String, String)> = Vec::new();
for entry in WalkDir::new(path)
.max_depth(3)
.into_iter()
.filter_map(|e| e.ok())
{
let file_path = entry.path();
if file_path.is_file() {
let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
if matches!(ext, "js" | "jsx" | "ts" | "tsx") {
if let Ok(parsed) = parse_file(file_path) {
let model = SemanticModel::new(&parsed.module);
let source_file = file_path.file_name().unwrap().to_string_lossy().to_string();
for import_source in model.imports.keys() {
edges.push((source_file.clone(), import_source.clone()));
}
}
}
}
}
if format == "json" {
println!("{}", serde_json::to_string_pretty(&edges)?);
} else {
println!("graph TD");
for (src, target) in edges {
let safe_src = src.replace('.', "_").replace('-', "_");
let safe_target = target.replace('.', "_").replace('-', "_").replace('/', "_").replace('@', "_");
println!(" {} --> \"{}\"", safe_src, safe_target);
}
}
Ok(())
}