frontendmap 0.1.4

Frontend project satellite map — index, query, and navigate your web project
mod model;
mod cli;
mod scanner;
mod store;
mod commands;

use anyhow::Result;
use clap::Parser;
use cli::{Cli, Commands, QueryCommands, NavCommands, AnalyzeCommands};
use commands::{query, nav, analyze};

fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Index { project, output } => {
            let map = scanner::scan(&project)?;
            store::save(&map, &output)?;
            println!("✓ Indexed {} components in {} files", 
                map.project.component_count, map.project.file_count);
            println!("  Output: {}", output);
        }
        Commands::Query { command } => {
            let map = store::load(".")?;
            match command {
                QueryCommands::Summary => {
                    println!("Project: {}", map.project.name);
                    println!("Framework: {}", map.project.framework.as_str());
                    println!("Components: {}", map.project.component_count);
                    println!("Files: {}", map.project.file_count);
                    println!("Routes: {}", map.routes.len());
                    println!("API Calls: {}", map.api_calls.len());
                    println!("Stores: {}", map.stores.len());
                    if !map.project.features.is_empty() {
                        println!("Features: {}", map.project.features.join(", "));
                    }
                    if !map.project.tech_stack.is_empty() {
                        println!("Tech Stack: {}", map.project.tech_stack.join(", "));
                    }
                }
                QueryCommands::Components { name, limit } => {
                    let components = match name {
                        Some(n) => map.components.iter()
                            .filter(|c| c.name.to_lowercase().contains(&n.to_lowercase()))
                            .collect::<Vec<_>>(),
                        None => map.components.iter().collect(),
                    };
                    for (i, comp) in components.iter().enumerate() {
                        if i >= limit { break; }
                        println!("{} ({}) - {}", comp.name, comp.kind, comp.file.display());
                    }
                    println!("\nTotal: {} components", components.len());
                }
                QueryCommands::Inspect { name } => {
                    if let Some(comp) = map.components.iter().find(|c| c.name == name) {
                        println!("Component: {}", comp.name);
                        println!("File: {}", comp.file.display());
                        println!("Type: {}", comp.kind);
                        println!("Line: {}", comp.line);
                        if !comp.props.is_empty() {
                            println!("Props:");
                            for prop in &comp.props {
                                let type_str = prop.type_annotation.as_deref().unwrap_or("any");
                                let req = if prop.required { "required" } else { "optional" };
                                println!("  - {}: {} ({})", prop.name, type_str, req);
                            }
                        }
                        if !comp.used_by.is_empty() {
                            println!("Used by:");
                            for user in &comp.used_by {
                                println!("  - {}", user.display());
                            }
                        }
                        if !comp.uses.is_empty() {
                            println!("Uses:");
                            for used in &comp.uses {
                                println!("  - {}", used);
                            }
                        }
                    } else {
                        eprintln!("Component '{}' not found", name);
                    }
                }
                QueryCommands::UsedBy { name } => {
                    if let Some(comp) = map.components.iter().find(|c| c.name == name) {
                        if comp.used_by.is_empty() {
                            println!("{} is not used by any component", name);
                        } else {
                            println!("{} is used by:", name);
                            for user in &comp.used_by {
                                println!("  - {}", user.display());
                            }
                        }
                    } else {
                        eprintln!("Component '{}' not found", name);
                    }
                }
                QueryCommands::Routes => {
                    if map.routes.is_empty() {
                        println!("No routes found");
                    } else {
                        for route in &map.routes {
                            println!("{}{} ({})", route.path, route.component, route.file.display());
                        }
                        println!("\nTotal: {} routes", map.routes.len());
                    }
                }
                QueryCommands::Apis { component } => {
                    let apis = match component {
                        Some(c) => map.api_calls.iter()
                            .filter(|a| a.component.to_lowercase().contains(&c.to_lowercase()))
                            .collect::<Vec<_>>(),
                        None => map.api_calls.iter().collect(),
                    };
                    for api in &apis {
                        println!("{} {}{} ({})", api.method, api.endpoint, 
                            api.component, api.file.display());
                    }
                    println!("\nTotal: {} API calls", apis.len());
                }
                QueryCommands::Stores => {
                    if map.stores.is_empty() {
                        println!("No stores found");
                    } else {
                        for store in &map.stores {
                            println!("{} ({}) - {}", store.name, store.kind, store.file.display());
                            if !store.subscribers.is_empty() {
                                println!("  Subscribers: {}", store.subscribers.join(", "));
                            }
                        }
                        println!("\nTotal: {} stores", map.stores.len());
                    }
                }
                QueryCommands::Entries => {
                    query::query_entries(&map);
                }
                QueryCommands::Similar { name, limit } => {
                    query::query_similar(&map, &name, limit);
                }
                QueryCommands::Deps { name, depth } => {
                    query::query_deps(&map, &name, depth);
                }
                QueryCommands::Impact { name, depth } => {
                    query::query_impact(&map, &name, depth);
                }
                QueryCommands::Flow { name, depth } => {
                    query::query_flow(&map, &name, depth);
                }
                QueryCommands::Scope { target } => {
                    query::query_scope(&map, &target);
                }
                QueryCommands::Path { from, to } => {
                    query::query_path(&map, &from, &to);
                }
                QueryCommands::Export { format, output } => {
                    query::query_export(&map, &format, output.as_deref());
                }
            }
        }
        Commands::Nav { command } => {
            let map = store::load(".")?;
            match command {
                NavCommands::Guide => {
                    nav::nav_guide(&map);
                }
                NavCommands::Quality => {
                    nav::nav_quality(&map);
                }
                NavCommands::Health => {
                    nav::nav_health(&map);
                }
                NavCommands::Report { output } => {
                    nav::nav_report(&map, output.as_deref());
                }
                NavCommands::Map { full } => {
                    nav::nav_map(&map, full);
                }
            }
        }
        Commands::Analyze { command } => {
            let map = store::load(".")?;
            match command {
                AnalyzeCommands::Deps { from } => {
                    analyze::analyze_deps(&map, from.as_deref());
                }
                AnalyzeCommands::Fanout { limit } => {
                    analyze::analyze_fanout(&map, limit);
                }
                AnalyzeCommands::Tests { name } => {
                    analyze::analyze_tests(&map, name.as_deref());
                }
            }
        }
    }

    Ok(())
}