Skip to main content

chub_cli/commands/
build.rs

1use chub_core::build::builder::{build_registry, write_build_output_with_opts, BuildOptions};
2use chub_core::error::Result;
3use chub_core::team::analytics;
4use clap::Args;
5use indicatif::{ProgressBar, ProgressStyle};
6use std::path::PathBuf;
7
8use crate::output;
9
10#[derive(Args)]
11pub struct BuildArgs {
12    /// Content directory to build from
13    content_dir: PathBuf,
14
15    /// Output directory (default: <content-dir>/dist)
16    #[arg(short, long)]
17    output: Option<PathBuf>,
18
19    /// Base URL for CDN deployment
20    #[arg(long)]
21    base_url: Option<String>,
22
23    /// Validate without writing output
24    #[arg(long)]
25    validate_only: bool,
26
27    /// Disable incremental builds (copy all files regardless of changes)
28    #[arg(long)]
29    no_incremental: bool,
30}
31
32pub fn run(args: BuildArgs, json: bool) -> Result<()> {
33    let content_dir = &args.content_dir;
34    let output_dir = args.output.unwrap_or_else(|| content_dir.join("dist"));
35
36    let opts = BuildOptions {
37        base_url: args.base_url,
38        validate_only: args.validate_only,
39        incremental: !args.no_incremental,
40    };
41
42    // Show spinner during discovery + index build
43    let pb = if !json {
44        let pb = ProgressBar::new_spinner();
45        pb.set_style(
46            ProgressStyle::with_template("{spinner:.cyan} {msg}")
47                .unwrap()
48                .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏-"),
49        );
50        pb.set_message("Scanning content directory...");
51        pb.enable_steady_tick(std::time::Duration::from_millis(80));
52        Some(pb)
53    } else {
54        None
55    };
56
57    let build_start = std::time::Instant::now();
58    let result = build_registry(content_dir, &opts)?;
59    let build_duration = build_start.elapsed().as_millis() as u64;
60
61    analytics::record_build(
62        result.docs_count + result.skills_count,
63        build_duration,
64        result.warnings.len(),
65        args.validate_only,
66    );
67
68    // Print warnings
69    for w in &result.warnings {
70        output::warn(w);
71    }
72
73    if args.validate_only {
74        if let Some(pb) = pb {
75            pb.finish_and_clear();
76        }
77        if json {
78            println!(
79                "{}",
80                serde_json::json!({
81                    "docs": result.docs_count,
82                    "skills": result.skills_count,
83                    "warnings": result.warnings.len(),
84                })
85            );
86        } else {
87            output::success(&format!(
88                "Valid: {} docs, {} skills, {} warnings",
89                result.docs_count,
90                result.skills_count,
91                result.warnings.len()
92            ));
93        }
94        return Ok(());
95    }
96
97    if let Some(ref pb) = pb {
98        pb.set_message("Writing output...");
99    }
100
101    // Write output
102    write_build_output_with_opts(content_dir, &output_dir, &result, &opts)?;
103
104    if let Some(pb) = pb {
105        pb.finish_and_clear();
106    }
107
108    if json {
109        println!(
110            "{}",
111            serde_json::json!({
112                "docs": result.docs_count,
113                "skills": result.skills_count,
114                "warnings": result.warnings.len(),
115                "output": output_dir.display().to_string(),
116            })
117        );
118    } else {
119        output::success(&format!(
120            "Built: {} docs, {} skills → {}",
121            result.docs_count,
122            result.skills_count,
123            output_dir.display()
124        ));
125    }
126
127    Ok(())
128}