Skip to main content

systemprompt_generator/build/
orchestrator.rs

1//! Top-level build orchestrator: organises CSS, runs validation, reports
2//! progress, and returns a typed [`BuildError`] on failure.
3
4use indicatif::{ProgressBar, ProgressStyle};
5use std::path::PathBuf;
6use std::time::Instant;
7use thiserror::Error;
8
9use super::steps::organize_css;
10use super::validation::validate_build;
11
12pub(super) type Result<T> = std::result::Result<T, BuildError>;
13
14#[derive(Error, Debug)]
15pub enum BuildError {
16    #[error("CSS organization failed: {0}")]
17    CssOrganizationFailed(String),
18
19    #[error("Validation failed: {0}")]
20    ValidationFailed(String),
21
22    #[error("I/O error: {0}")]
23    Io(#[from] std::io::Error),
24
25    #[error("Process execution error: {0}")]
26    ProcessError(String),
27
28    #[error("Configuration error: {0}")]
29    ConfigError(String),
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum BuildMode {
34    Development,
35    Production,
36    Docker,
37}
38
39impl BuildMode {
40    #[must_use]
41    pub fn parse(s: &str) -> Option<Self> {
42        match s.to_lowercase().as_str() {
43            "development" | "dev" => Some(Self::Development),
44            "production" | "prod" => Some(Self::Production),
45            "docker" => Some(Self::Docker),
46            _ => None,
47        }
48    }
49
50    #[must_use]
51    pub const fn as_str(&self) -> &'static str {
52        match self {
53            Self::Development => "development",
54            Self::Production => "production",
55            Self::Docker => "docker",
56        }
57    }
58}
59
60#[derive(Debug)]
61pub struct BuildOrchestrator {
62    web_dir: PathBuf,
63    mode: BuildMode,
64}
65
66impl BuildOrchestrator {
67    #[must_use]
68    pub const fn new(web_dir: PathBuf, mode: BuildMode) -> Self {
69        Self { web_dir, mode }
70    }
71
72    #[must_use]
73    pub const fn mode(&self) -> BuildMode {
74        self.mode
75    }
76
77    pub async fn build(&self) -> Result<()> {
78        let start = Instant::now();
79        let pb = create_progress_bar();
80        self.execute_build_steps(&pb).await?;
81        finish_build(&pb, start);
82        Ok(())
83    }
84
85    async fn execute_build_steps(&self, pb: &ProgressBar) -> Result<()> {
86        pb.set_message("CSS Organization");
87        organize_css(&self.web_dir).await?;
88        pb.inc(1);
89
90        pb.set_message("Validation");
91        validate_build(&self.web_dir).await?;
92        pb.inc(1);
93
94        Ok(())
95    }
96
97    pub async fn validate_only(&self) -> Result<()> {
98        tracing::info!("Validating build");
99        validate_build(&self.web_dir).await
100    }
101}
102
103fn create_progress_bar() -> ProgressBar {
104    let pb = ProgressBar::new(2);
105    pb.set_style(
106        ProgressStyle::default_bar()
107            .template("{msg} [{bar:40.cyan/blue}] {pos}/{len}")
108            .unwrap_or_else(|_| ProgressStyle::default_bar())
109            .progress_chars("=>-"),
110    );
111    pb
112}
113
114fn finish_build(pb: &ProgressBar, start: Instant) {
115    pb.finish_with_message("Build complete");
116    tracing::info!(
117        duration_secs = start.elapsed().as_secs_f64(),
118        "Build successful"
119    );
120}