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