refactor: 合并为单 crate,前端改为内嵌 React 控制台
合并 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。
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user