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 std::path::Path;

use anyhow::{Context, Result};
use systemprompt_database::DbPool;
use systemprompt_models::{ContentConfigRaw, ParentRoute, WebConfig};
use systemprompt_template_provider::{ComponentContext, PageContext};
use systemprompt_templates::TemplateRegistry;
use tokio::fs;

use crate::prerender::utils::{merge_json_data, render_components};

pub struct RenderListParams<'a> {
    pub items: &'a [serde_json::Value],
    pub config: &'a ContentConfigRaw,
    pub web_config: &'a WebConfig,
    pub list_config: &'a ParentRoute,
    pub source_name: &'a str,
    pub template_registry: &'a TemplateRegistry,
    pub dist_dir: &'a Path,
    pub index_content: Option<&'a serde_json::Value>,
    pub db_pool: &'a DbPool,
}

impl std::fmt::Debug for RenderListParams<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RenderListParams")
            .field("source_name", &self.source_name)
            .field("items_count", &self.items.len())
            .field("has_index_content", &self.index_content.is_some())
            .finish_non_exhaustive()
    }
}

pub async fn render_list_route(params: RenderListParams<'_>) -> Result<()> {
    let RenderListParams {
        items,
        config,
        web_config,
        list_config,
        source_name,
        template_registry,
        dist_dir,
        index_content,
        db_pool,
    } = params;

    let list_content_type = format!("{source_name}-list");

    let template_name = template_registry
        .find_template_for_content_type(&list_content_type)
        .ok_or_else(|| anyhow::anyhow!("No template for: {list_content_type}"))?;

    let mut list_data = serde_json::json!({
        "HAS_INDEX_CONTENT": index_content.is_some(),
    });

    let mut page_ctx =
        PageContext::new(&list_content_type, web_config, config, db_pool).with_all_items(items);

    if let Some(item) = index_content {
        page_ctx = page_ctx.with_content_item(item);
    }

    for provider in template_registry.page_providers_for(&list_content_type) {
        let data = provider
            .provide_page_data(&page_ctx)
            .await
            .map_err(|e| anyhow::anyhow!("Provider {} failed: {e}", provider.provider_id()))?;
        merge_json_data(&mut list_data, &data);
    }

    let component_ctx = ComponentContext::for_list(web_config, items);
    render_components(
        template_registry,
        &list_content_type,
        &component_ctx,
        &mut list_data,
    )
    .await;

    let list_html = template_registry
        .render(template_name, &list_data)
        .context("Failed to render list route")?;

    let list_dir = dist_dir.join(list_config.url.trim_start_matches('/'));
    fs::create_dir_all(&list_dir).await?;
    fs::write(list_dir.join("index.html"), &list_html).await?;

    tracing::debug!(path = %list_config.url, "Generated list route");
    Ok(())
}