use std::fmt::Write as _; use std::fs; use std::path::Path; /// 前端构建产物目录(`pnpm build` 的输出)。 const WEB_DIST: &str = "../../../view/dist"; fn main() { let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR")); build_web_assets(manifest_dir, &out_dir); } /// 把根目录 `view/dist` 下的前端产物编进二进制:认证中心自带登录页,不依赖外部静态站点。 fn build_web_assets(manifest_dir: &Path, out_dir: &Path) { let root = manifest_dir.join(WEB_DIST); println!("cargo:rerun-if-changed={}", root.display()); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=KENOAUTH_SKIP_WEB_ASSETS"); if std::env::var_os("KENOAUTH_SKIP_WEB_ASSETS").is_some() { // 纯后端编译(未装 pnpm 的环境)跳过前端资产,服务启动后退化为 501 页面。 let source = "// web assets skipped via KENOAUTH_SKIP_WEB_ASSETS\npub static ASSETS: &[Asset] = &[];\n"; fs::write(out_dir.join("assets.rs"), source).expect("writing generated asset table"); return; } let mut files = Vec::new(); collect_assets(&root, &root, &mut files); files.sort(); let mut source = String::from(concat!( "// generated by build.rs from ", "../../../view/dist\n", "pub static ASSETS: &[Asset] = &[\n" )); for file in &files { let absolute = root.join(file); let absolute = absolute .canonicalize() .unwrap_or(absolute) .to_string_lossy() .replace('\\', "/"); let _ = writeln!( source, " Asset {{ path: {file:?}, content_type: {:?}, bytes: include_bytes!({absolute:?}) }},", content_type(file) ); } source.push_str("];\n"); fs::write(out_dir.join("assets.rs"), source).expect("writing generated asset table"); } fn collect_assets(root: &Path, dir: &Path, files: &mut Vec) { let Ok(entries) = fs::read_dir(dir) else { return; }; for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { collect_assets(root, &path, files); } else if let Ok(relative) = path.strip_prefix(root) { files.push(relative.to_string_lossy().replace('\\', "/")); } } } fn content_type(file: &str) -> &'static str { match file.rsplit_once('.').map(|(_, extension)| extension) { Some("html" | "htm") => "text/html; charset=utf-8", Some("js" | "mjs") => "text/javascript; charset=utf-8", Some("css") => "text/css; charset=utf-8", Some("json" | "map") => "application/json", Some("svg") => "image/svg+xml", Some("png") => "image/png", Some("jpg" | "jpeg") => "image/jpeg", Some("webp") => "image/webp", Some("gif") => "image/gif", Some("ico") => "image/x-icon", Some("txt") => "text/plain; charset=utf-8", Some("woff") => "font/woff", Some("woff2") => "font/woff2", Some("ttf") => "font/ttf", Some("otf") => "font/otf", Some("wasm") => "application/wasm", Some("xml") => "application/xml", Some("pdf") => "application/pdf", _ => "application/octet-stream", } }