systemprompt-generator 0.2.1

Static site generation, theme rendering, and asset bundling for systemprompt.io AI governance dashboards. Handlebars and Markdown pipeline for the MCP governance platform.
Documentation
use indicatif::{ProgressBar, ProgressStyle};
use std::path::PathBuf;
use std::time::Instant;
use thiserror::Error;

use super::steps::organize_css;
use super::validation::validate_build;

pub type Result<T> = std::result::Result<T, BuildError>;

#[derive(Error, Debug)]
pub enum BuildError {
    #[error("CSS organization failed: {0}")]
    CssOrganizationFailed(String),

    #[error("Validation failed: {0}")]
    ValidationFailed(String),

    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Process execution error: {0}")]
    ProcessError(String),

    #[error("Configuration error: {0}")]
    ConfigError(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildMode {
    Development,
    Production,
    Docker,
}

impl BuildMode {
    #[must_use]
    pub fn parse(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "development" | "dev" => Some(Self::Development),
            "production" | "prod" => Some(Self::Production),
            "docker" => Some(Self::Docker),
            _ => None,
        }
    }

    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::Development => "development",
            Self::Production => "production",
            Self::Docker => "docker",
        }
    }
}

#[derive(Debug)]
pub struct BuildOrchestrator {
    web_dir: PathBuf,
    mode: BuildMode,
}

impl BuildOrchestrator {
    #[must_use]
    pub const fn new(web_dir: PathBuf, mode: BuildMode) -> Self {
        Self { web_dir, mode }
    }

    #[must_use]
    pub const fn mode(&self) -> BuildMode {
        self.mode
    }

    pub async fn build(&self) -> Result<()> {
        let start = Instant::now();
        let pb = create_progress_bar();
        self.execute_build_steps(&pb).await?;
        finish_build(&pb, start);
        Ok(())
    }

    async fn execute_build_steps(&self, pb: &ProgressBar) -> Result<()> {
        pb.set_message("CSS Organization");
        organize_css(&self.web_dir).await?;
        pb.inc(1);

        pb.set_message("Validation");
        validate_build(&self.web_dir).await?;
        pb.inc(1);

        Ok(())
    }

    pub async fn validate_only(&self) -> Result<()> {
        tracing::info!("Validating build");
        validate_build(&self.web_dir).await
    }
}

fn create_progress_bar() -> ProgressBar {
    let pb = ProgressBar::new(2);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{msg} [{bar:40.cyan/blue}] {pos}/{len}")
            .unwrap_or_else(|_| ProgressStyle::default_bar())
            .progress_chars("=>-"),
    );
    pb
}

fn finish_build(pb: &ProgressBar, start: Instant) {
    pb.finish_with_message("Build complete");
    tracing::info!(
        duration_secs = start.elapsed().as_secs_f64(),
        "Build successful"
    );
}