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();
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; }
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;
}
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;
}
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;
}
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");
}