Skip to main content

cortex_runtime/cli/
compile_cmd.rs

1//! CLI handler for `cortex compile <domain>`.
2
3use crate::cli::output;
4use crate::compiler::{codegen, schema};
5use crate::intelligence::cache::MapCache;
6use anyhow::{bail, Result};
7use std::path::PathBuf;
8
9/// Run the compile command.
10pub async fn run(domain: &str, _all: bool, output_dir: Option<&str>) -> Result<()> {
11    // Load the cached map
12    let mut cache = MapCache::default_cache()?;
13    let site_map = match cache.load_map(domain)? {
14        Some(map) => map,
15        None => bail!("no cached map for '{domain}'. Run `cortex map {domain}` first."),
16    };
17
18    if !output::is_quiet() {
19        println!("  Compiling {domain}...\n");
20        println!(
21            "  Analyzing map ({} nodes)...\n",
22            site_map.header.node_count
23        );
24    }
25
26    // Infer schema
27    let compiled = schema::infer_schema(&site_map, domain);
28
29    if !output::is_quiet() {
30        println!("  Models:");
31        for model in &compiled.models {
32            println!(
33                "    {:<16} {:>6} instances   {:>2} fields   {:>2} actions",
34                model.name,
35                model.instance_count,
36                model.fields.len(),
37                compiled
38                    .actions
39                    .iter()
40                    .filter(|a| a.belongs_to == model.name)
41                    .count()
42            );
43        }
44        println!();
45    }
46
47    if !compiled.relationships.is_empty() && !output::is_quiet() {
48        println!("  Relationships:");
49        for rel in &compiled.relationships {
50            println!(
51                "    {} → {:<16} {:>12}  {:>6} edges",
52                rel.from_model,
53                rel.to_model,
54                format!("{:?}", rel.cardinality).to_lowercase(),
55                rel.edge_count
56            );
57        }
58        println!();
59    }
60
61    // Determine output directory
62    let out_dir = if let Some(dir) = output_dir {
63        PathBuf::from(dir)
64    } else {
65        let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
66        home.join(".cortex").join("compiled").join(domain)
67    };
68
69    // Generate all files
70    let files = codegen::generate_all(&compiled, &out_dir)?;
71
72    if !output::is_quiet() {
73        println!("  Generated:");
74        println!("    {}/", out_dir.display());
75        for file in &files.files {
76            println!("    ├── {:<24} {:>5} B", file.filename, file.size);
77        }
78        println!();
79        println!("  Usage:");
80
81        let domain_safe = domain.replace(['.', '-'], "_");
82        println!("    Python:     from cortex.compiled.{domain_safe} import Product, Cart");
83        println!(
84            "    TypeScript: import {{ searchProducts }} from '{}'",
85            out_dir.join("client").display()
86        );
87        println!("    OpenAPI:    Point any REST client at the spec");
88        println!("    GraphQL:    cortex graphql {domain} --port 7701");
89    }
90
91    if output::is_json() {
92        output::print_json(&serde_json::json!({
93            "domain": domain,
94            "models": compiled.stats.total_models,
95            "fields": compiled.stats.total_fields,
96            "actions": compiled.actions.len(),
97            "relationships": compiled.relationships.len(),
98            "output_dir": out_dir.display().to_string(),
99        }));
100    }
101
102    Ok(())
103}