use std::fs;
use std::path::{Path, PathBuf};
fn repo_root() -> Option<PathBuf> {
Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.find(|d| d.join("crates/umbral-core/Cargo.toml").is_file())
.map(Path::to_path_buf)
}
fn files_with_extension(dir: &Path, ext: &str, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if path.file_name().is_some_and(|n| n == "node_modules") {
continue;
}
files_with_extension(&path, ext, out);
} else if path.extension().is_some_and(|e| e == ext) {
out.push(path);
}
}
}
fn version_pin(line: &str) -> Option<(&str, &str)> {
let line = line.trim();
let crate_name = line.strip_prefix("umbral")?;
let (name, rest) = line.split_once(" = ")?;
if !crate_name
.split(" = ")
.next()?
.chars()
.all(|c| c.is_ascii_lowercase() || c == '-')
{
return None;
}
let pin = rest.trim().trim_matches('"');
let is_semver = !pin.is_empty()
&& pin
.chars()
.all(|c| c.is_ascii_digit() || c == '.' || c == '-' || c.is_ascii_alphabetic());
(is_semver && pin.chars().next()?.is_ascii_digit()).then_some((name, pin))
}
#[test]
fn readmes_use_cargo_add_and_never_pin_a_version() {
let Some(root) = repo_root() else { return };
let mut readmes = vec![root.join("README.md")];
for dir in ["crates", "plugins"] {
files_with_extension(&root.join(dir), "md", &mut readmes);
}
let mut offenders = Vec::new();
for path in readmes.iter().filter(|p| p.ends_with("README.md")) {
let Ok(text) = fs::read_to_string(path) else {
continue;
};
for (i, line) in text.lines().enumerate() {
if let Some((name, pin)) = version_pin(line) {
offenders.push(format!(
"{}:{}: `{name} = \"{pin}\"` — use `cargo add {name}` instead",
path.strip_prefix(&root).unwrap_or(path).display(),
i + 1,
));
}
}
}
assert!(
offenders.is_empty(),
"READMEs must not pin an umbral version — a 0.0.z pin resolves to that exact \
release forever, so the snippet rots the moment we ship again:\n {}",
offenders.join("\n "),
);
}
#[test]
fn docs_manifests_pin_the_current_version() {
let Some(root) = repo_root() else { return };
let docs = root.join("documentation/docs");
if !docs.is_dir() {
return;
}
let current = env!("CARGO_PKG_VERSION");
let mut pages = Vec::new();
files_with_extension(&docs, "mdx", &mut pages);
let mut stale = Vec::new();
for path in &pages {
let Ok(text) = fs::read_to_string(path) else {
continue;
};
for (i, line) in text.lines().enumerate() {
if let Some((name, pin)) = version_pin(line)
&& pin != current
{
stale.push(format!(
"{}:{}: `{name} = \"{pin}\"` but the crates are at {current}",
path.strip_prefix(&root).unwrap_or(path).display(),
i + 1,
));
}
}
}
assert!(
stale.is_empty(),
"documentation shows a Cargo.toml pinned to a version we no longer ship. A manifest \
needs a literal version, so these have to be bumped by hand on release:\n {}",
stale.join("\n "),
);
}