use serde::Deserialize;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;
#[derive(Deserialize, Debug)]
struct ManifestEntry {
file: String,
css: Option<Vec<String>>,
}
fn main() {
if let Err(e) = vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit() {
println!("cargo:warning=Failed to generate version info: {}", e);
}
println!("cargo:rerun-if-changed=ui/build.rs");
println!("cargo:rerun-if-changed=ui/src/");
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let ui_dist_path = Path::new(&crate_dir).join("ui/dist");
let ui_public_path = Path::new(&crate_dir).join("ui/public");
if !ui_dist_path.exists() {
fs::create_dir_all(&ui_dist_path).expect("Failed to create ui/dist directory");
}
let pwa_manifest_source = ui_public_path.join("manifest.json");
let pwa_manifest_dest = ui_dist_path.join("pwa-manifest.json");
let sw_source = ui_public_path.join("sw.js");
let sw_dest = ui_dist_path.join("sw.js");
if pwa_manifest_source.exists() && !pwa_manifest_dest.exists() {
fs::copy(&pwa_manifest_source, &pwa_manifest_dest)
.expect("Failed to copy PWA manifest.json from public to dist");
}
if sw_source.exists() && !sw_dest.exists() {
fs::copy(&sw_source, &sw_dest).expect("Failed to copy sw.js from public to dist");
}
let ui_build_script = Path::new(&crate_dir).join("build_ui.sh");
if ui_build_script.exists() {
let status = Command::new("bash").arg(&ui_build_script).status();
if let Ok(status) = status {
if !status.success() {
println!(
"cargo:warning=UI build script failed, but continuing with fallback files"
);
}
} else {
println!(
"cargo:warning=Failed to run UI build script, but continuing with fallback files"
);
}
} else {
println!("cargo:warning=UI build script not found, using fallback files from public/");
}
println!("cargo:rerun-if-changed={}", ui_dist_path.join("manifest.json").display());
println!("cargo:rerun-if-changed={}", ui_dist_path.join("pwa-manifest.json").display());
println!("cargo:rerun-if-changed={}", ui_dist_path.join("sw.js").display());
println!("cargo:rerun-if-changed={}", ui_dist_path.join("assets").display());
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("asset_paths.rs");
let vite_manifest_path = ui_dist_path.join("manifest.json");
let assets_dir = ui_dist_path.join("assets");
if vite_manifest_path.exists() {
if let Ok(manifest_content) = fs::read_to_string(&vite_manifest_path) {
if let Ok(manifest) =
serde_json::from_str::<HashMap<String, ManifestEntry>>(&manifest_content)
{
let entry = manifest
.get("index.html")
.expect("Could not find index.html entry in Vite manifest.json");
let js_path = ui_dist_path.join(&entry.file);
let css_path = entry
.css
.as_ref()
.and_then(|files| files.first())
.map(|file| ui_dist_path.join(file));
let css_content = if let Some(path) = css_path {
format!(
"pub fn get_admin_css() -> &'static str {{ include_str!(r\"{}\")\n}}",
path.display()
)
} else {
"pub fn get_admin_css() -> &'static str { \"\" }\n".to_string()
};
let js_content = format!(
"pub fn get_admin_js() -> &'static str {{ include_str!(r\"{}\")\n}}",
js_path.display()
);
fs::write(&dest_path, format!("{}\n\n{}", css_content, js_content)).unwrap();
return; }
}
}
if assets_dir.exists() {
let mut asset_map = String::from(
"pub fn get_asset_map() -> std::collections::HashMap<&'static str, &'static str> {\n",
);
asset_map.push_str(" let mut map = std::collections::HashMap::new();\n");
if let Ok(entries) = fs::read_dir(&assets_dir) {
for entry in entries.flatten() {
if let Some(filename) = entry.path().file_name().and_then(|n| n.to_str()) {
if filename.ends_with(".js") || filename.ends_with(".css") {
let asset_path = entry.path();
asset_map.push_str(&format!(
" map.insert(\"{}\", include_str!(r\"{}\"));\n",
filename,
asset_path.display()
));
}
}
}
}
asset_map.push_str(" map\n");
asset_map.push_str("}\n");
let css_content = format!(
"pub fn get_admin_css() -> &'static str {{ include_str!(r\"{}\")\n}}",
ui_dist_path.join("assets/index.css").display()
);
let js_content = format!(
"pub fn get_admin_js() -> &'static str {{ include_str!(r\"{}\")\n}}",
ui_dist_path.join("assets/index.js").display()
);
fs::write(&dest_path, format!("{}\n\n{}\n\n{}", css_content, js_content, asset_map))
.unwrap();
}
let icon_assets_path = Path::new(&out_dir).join("icon_assets.rs");
let mut icon_assets = String::from("// Embedded icon/logo assets\n");
let icon_files = vec![
("ICON_DEFAULT", "mockforge-icon.png"),
("ICON_32", "mockforge-icon-32.png"),
("ICON_48", "mockforge-icon-48.png"),
("LOGO_40", "mockforge-logo-40.png"),
("LOGO_80", "mockforge-logo-80.png"),
];
for (const_name, filename) in icon_files {
let icon_path = ui_public_path.join(filename);
if icon_path.exists() {
match fs::read(&icon_path) {
Ok(bytes) => {
let mut byte_array = String::from("&[");
for (i, byte) in bytes.iter().enumerate() {
if i > 0 {
byte_array.push_str(", ");
}
if i % 20 == 0 && i > 0 {
byte_array.push_str("\n ");
}
byte_array.push_str(&format!("0x{:02X}", byte));
}
byte_array.push_str("]");
icon_assets
.push_str(&format!("pub const {}: &[u8] = {};\n", const_name, byte_array));
}
Err(_) => {
icon_assets.push_str(&format!("pub const {}: &[u8] = &[];\n", const_name));
}
}
} else {
icon_assets.push_str(&format!("pub const {}: &[u8] = &[];\n", const_name));
}
}
fs::write(&icon_assets_path, icon_assets).unwrap();
if !assets_dir.exists() {
let content = "
pub fn get_admin_css() -> &'static str { \"\" }
pub fn get_admin_js() -> &'static str { \"\" }
pub fn get_asset_map() -> std::collections::HashMap<&'static str, &'static str> {
std::collections::HashMap::new()
}
";
fs::write(&dest_path, content).unwrap();
}
}