canonrs-server 0.1.0

CanonRS server-side rendering support
use std::path::Path;
use std::fs;

fn main() {
    let generated = Path::new("styles/.generated/primitives.css");

    if !generated.exists() {
        println!("cargo:warning=CSS not generated by CanonRS CLI.");
        panic!(
            "\n❌ CanonRS requires CSS generation via CLI.\n\
             Run one of:\n\
               canonrs dev\n\
               canonrs build\n"
        );
    }

    println!("cargo:rerun-if-changed=styles/.generated/");
    println!("cargo:rerun-if-changed=styles/");
    println!("cargo:rerun-if-changed=src/ui/");

    gen_boundary_exports();
}

fn gen_boundary_exports() {
    let ui_dir  = Path::new("src/ui");
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let out_path = std::path::PathBuf::from(&out_dir).join("_boundary_exports.rs");

    let mut exports = String::new();
    exports.push_str("// AUTO-GENERATED by build.rs — do not edit manually.\n");
    exports.push_str("// Re-exports all boundary types from canonrs-server::ui\n\n");

    let mut seen_types: std::collections::HashSet<String> = std::collections::HashSet::new();
    let global_types = ["DisabledState", "LoadingState", "VisibilityState", "ToggleState"];
    for t in &global_types { seen_types.insert(t.to_string()); }

    let mut entries: Vec<_> = fs::read_dir(ui_dir)
        .expect("src/ui not found")
        .filter_map(|e| e.ok())
        .filter(|e| e.path().is_dir())
        .collect();
    entries.sort_by_key(|e| e.file_name());

    for entry in entries {
        let component = entry.file_name().to_string_lossy().to_string();
        let boundary  = entry.path().join(format!("{}_boundary.rs", component));
        if !boundary.exists() { continue; }

        let content = fs::read_to_string(&boundary).unwrap_or_default();
        let mut types: Vec<String> = Vec::new();

        for line in content.lines() {
            let t = line.trim();

            // pub use canonrs_core::primitives/meta
            if (t.starts_with("pub use canonrs_core::primitives") ||
                t.starts_with("pub use canonrs_core::meta")) && t.ends_with(';') {
                let types_part = t
                    .trim_start_matches("pub use canonrs_core::primitives::")
                    .trim_start_matches("pub use canonrs_core::meta::")
                    .trim_end_matches(';');
                let types_str = types_part.trim_matches('{').trim_matches('}');
                for s in types_str.split(',') {
                    let s = s.trim().to_string();
                    if s.is_empty() { continue; }
                    // se tem "as Alias", usar so o alias no export
                    let export_name = if s.contains(" as ") {
                        s.split(" as ").last().unwrap_or(&s).trim().to_string()
                    } else {
                        s.clone()
                    };
                    if !export_name.is_empty() && seen_types.insert(export_name.clone()) {
                        types.push(export_name);
                    }
                }
                continue;
            }

            // pub fn ComponentName(
            if t.starts_with("pub fn ") && t.contains('(') {
                let name = t
                    .trim_start_matches("pub fn ")
                    .split('(')
                    .next()
                    .unwrap_or("")
                    .trim()
                    .to_string();
                if !name.is_empty() && !name.contains('<') && seen_types.insert(name.clone()) {
                    types.push(name);
                }
                continue;
            }

            // pub enum EnumName
            if t.starts_with("pub enum ") {
                let name = t
                    .trim_start_matches("pub enum ")
                    .split_whitespace()
                    .next()
                    .unwrap_or("")
                    .to_string();
                if !name.is_empty() && !name.contains('<') && seen_types.insert(name.clone()) {
                    types.push(name);
                }
                continue;
            }

            // pub struct StructName
            if t.starts_with("pub struct ") {
                let name = t
                    .trim_start_matches("pub struct ")
                    .split_whitespace()
                    .next()
                    .unwrap_or("")
                    .to_string();
                if !name.is_empty() && !name.contains('<') && seen_types.insert(name.clone()) {
                    types.push(name);
                }
            }
        }

        if !types.is_empty() {
            exports.push_str(&format!(
                "pub use super::{}::{}::{{{}}};
",
                component,
                format!("{}_boundary", component),
                types.join(", ")
            ));
        }
    }

    fs::write(&out_path, &exports).expect("failed to write _boundary_exports.rs");
    println!("cargo:warning=[canon] ui prelude ready");
}