合并 stash-cli / stash-core 为根目录单 crate(lib + bin),新增 React + Vite + TS 控制台替换 Askama 模板。 build.rs 用 walkdir 把 stash-web/dist 同步到 $OUT_DIR/web,由 include_dir! 编译期内嵌,产出单文件二进制。 路由改为 / 与 /assets/** 走内嵌前端,其余交给 ServeDir,缺失路径返回内置 404。同步更新依赖、.gitignore 与 README。
89 lines
2.7 KiB
Rust
89 lines
2.7 KiB
Rust
// build.rs
|
||
//
|
||
// 把前端产物 stash-web/dist 同步到 $OUT_DIR/web,
|
||
// 供 include_dir!("$OUT_DIR/web") 在编译期内嵌进二进制。
|
||
//
|
||
// 这里只做「校验 + 搬运」,不在这里跑 npm:
|
||
// 那样会把 Node 和网络依赖带进 cargo build。
|
||
// 编译 Rust 之前请先执行:cd stash-web && npm install && npm run build
|
||
|
||
use std::{
|
||
env, fs,
|
||
path::{Path, PathBuf},
|
||
};
|
||
|
||
use walkdir::WalkDir;
|
||
|
||
/// 前端工程目录,相对 crate 根目录。
|
||
const WEB_PROJECT_DIR: &str = "stash-web";
|
||
/// 前端构建产物目录,相对前端工程目录。
|
||
const WEB_DIST_DIR: &str = "dist";
|
||
/// 同步到 $OUT_DIR 下的子目录名,需与 include_dir! 中的路径保持一致。
|
||
const EMBED_DIR: &str = "web";
|
||
|
||
fn main() {
|
||
// 前端产物变了要重新嵌入(Cargo 对目录会递归扫描)
|
||
println!("cargo:rerun-if-changed={}/{}", WEB_PROJECT_DIR, WEB_DIST_DIR);
|
||
println!("cargo:rerun-if-changed={}/package.json", WEB_PROJECT_DIR);
|
||
|
||
let dist = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||
.join(WEB_PROJECT_DIR)
|
||
.join(WEB_DIST_DIR);
|
||
let out = PathBuf::from(env::var("OUT_DIR").expect("构建环境缺少 OUT_DIR")).join(EMBED_DIR);
|
||
|
||
// 缺产物就给出人能看懂的报错,而不是 proc-macro 的 "No such file or directory"
|
||
assert!(
|
||
dist.join("index.html").is_file(),
|
||
"未找到前端产物:{}\n请先执行: cd {} && npm install && npm run build",
|
||
dist.display(),
|
||
WEB_PROJECT_DIR
|
||
);
|
||
|
||
// 必须整体重建,否则 Vite 换 hash 后旧 chunk 会残留在二进制里
|
||
let _ = fs::remove_dir_all(&out);
|
||
fs::create_dir_all(&out).expect("创建内嵌目录失败");
|
||
|
||
copy_dir(&dist, &out).expect("复制前端产物失败");
|
||
}
|
||
|
||
/// 用 walkdir 递归复制 `from` 到 `to`,并保留目录结构。
|
||
fn copy_dir(from: &Path, to: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||
let mut copied = 0usize;
|
||
|
||
for entry in WalkDir::new(from) {
|
||
let entry = entry?;
|
||
|
||
let relative = entry.path().strip_prefix(from)?;
|
||
|
||
// 跳过 dist 目录自身
|
||
if relative.as_os_str().is_empty() {
|
||
continue;
|
||
}
|
||
|
||
let destination = to.join(relative);
|
||
|
||
if entry.file_type().is_dir() {
|
||
fs::create_dir_all(&destination)?;
|
||
continue;
|
||
}
|
||
|
||
// 符号链接等非普通文件不参与内嵌
|
||
if !entry.file_type().is_file() {
|
||
continue;
|
||
}
|
||
|
||
fs::copy(entry.path(), &destination)?;
|
||
copied += 1;
|
||
}
|
||
|
||
if copied == 0 {
|
||
return Err(std::io::Error::other(format!(
|
||
"{} 中没有任何文件可内嵌",
|
||
from.display()
|
||
))
|
||
.into());
|
||
}
|
||
|
||
Ok(())
|
||
}
|