use std::{env, fs};
const MANIFEST_PREFIX: &str = "trillium-frontend-";
const MANIFEST_SUFFIX: &str = ".paths";
pub fn track_frontend_sources() {
for path in rerun_paths(env::var("OUT_DIR").ok().as_deref()) {
println!("cargo::rerun-if-changed={path}");
}
}
fn rerun_paths(out_dir: Option<&str>) -> Vec<String> {
let Some(out_dir) = out_dir else {
return Vec::new();
};
let Ok(entries) = fs::read_dir(out_dir) else {
return Vec::new();
};
let mut paths = Vec::new();
for entry in entries.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if !(name.starts_with(MANIFEST_PREFIX) && name.ends_with(MANIFEST_SUFFIX)) {
continue;
}
if let Ok(contents) = fs::read_to_string(entry.path()) {
paths.extend(
contents
.lines()
.filter(|line| !line.is_empty())
.map(String::from),
);
}
}
paths
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
struct FakeOutDir(PathBuf);
impl FakeOutDir {
fn new(tag: &str) -> Self {
let dir = env::temp_dir().join(format!("tf-shim-{}-{tag}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
FakeOutDir(dir)
}
fn write(&self, name: &str, contents: &str) {
fs::write(self.0.join(name), contents).unwrap();
}
fn as_str(&self) -> &str {
self.0.to_str().unwrap()
}
}
impl Drop for FakeOutDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[test]
fn no_out_dir_yields_nothing() {
assert!(rerun_paths(None).is_empty());
}
#[test]
fn empty_out_dir_yields_nothing() {
let out = FakeOutDir::new("empty");
assert!(rerun_paths(Some(out.as_str())).is_empty());
}
#[test]
fn reads_manifest_paths() {
let out = FakeOutDir::new("manifest");
out.write(
"trillium-frontend-00ff.paths",
"/client/src\n/client/src/main.js\n",
);
let mut got = rerun_paths(Some(out.as_str()));
got.sort();
assert_eq!(got, ["/client/src", "/client/src/main.js"]);
}
#[test]
fn unions_multiple_manifests_and_skips_blank_lines() {
let out = FakeOutDir::new("multi");
out.write("trillium-frontend-aaaa.paths", "/a/one\n\n/a/two\n");
out.write("trillium-frontend-bbbb.paths", "/b/three\n");
out.write("unrelated.txt", "/should/not/appear\n");
let mut got = rerun_paths(Some(out.as_str()));
got.sort();
assert_eq!(got, ["/a/one", "/a/two", "/b/three"]);
}
#[test]
fn emits_manifest_paths_regardless_of_other_out_dir_files() {
let out = FakeOutDir::new("coexist");
out.write("trillium-frontend-aaaa.paths", "/client/src\n");
out.write("trillium-frontend.tracked", "");
assert_eq!(
rerun_paths(Some(out.as_str())),
["/client/src"],
"the shim must always declare the recorded source paths"
);
}
}