use std::path::{Component, Path, PathBuf};
fn repo_root() -> PathBuf {
std::env::var("YANA_REPO_ROOT")
.or_else(|_| std::env::var("PWD"))
.map(PathBuf::from)
.ok()
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| PathBuf::from("."))
}
fn repo_relative(raw: &str) -> String {
let folded = lexical_fold(Path::new(raw));
if folded.is_absolute() {
let root = lexical_fold(&repo_root());
if let Ok(stripped) = folded.strip_prefix(&root) {
return stripped.to_string_lossy().into_owned();
}
return folded.to_string_lossy().into_owned();
}
folded.to_string_lossy().into_owned()
}
fn lexical_fold(p: &Path) -> PathBuf {
let mut out = PathBuf::new();
for c in p.components() {
match c {
Component::ParentDir => {
out.pop();
}
Component::CurDir => {}
other => out.push(other.as_os_str()),
}
}
out
}
pub fn protected_hit(raw: &str, protected: &[String]) -> Option<String> {
let rel = repo_relative(raw);
for p in protected {
if matches_prefix(&rel, p) || matches_prefix(raw, p) {
return Some(p.clone());
}
}
None
}
fn matches_prefix(candidate: &str, prefix: &str) -> bool {
candidate == prefix || candidate.starts_with(&format!("{prefix}/"))
}
pub fn entry_point_prefixes() -> Vec<String> {
let mut v = vec![
"scripts/yana-rt-wrapper.js".to_string(),
"bin/yana".to_string(),
"scripts/npm-install.js".to_string(),
];
if let Ok(extra) = std::env::var("YANA_ENTRY_POINT_PATHS") {
v.extend(extra.split(':').filter(|s| !s.is_empty()).map(String::from));
}
v
}
pub fn entry_point_hit(raw: &str, entry_points: &[String]) -> Option<String> {
let rel = repo_relative(raw);
for p in entry_points {
if matches_prefix(&rel, p) || matches_prefix(raw, p) {
return Some(p.clone());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn prot() -> Vec<String> {
vec!["core/rules".to_string(), ".git".to_string()]
}
#[test]
fn relative_path_is_protected() {
assert!(protected_hit("core/rules/00-meta.md", &prot()).is_some());
}
#[test]
fn dotdot_path_is_protected() {
assert!(protected_hit("core/../core/rules/x.md", &prot()).is_some());
}
#[test]
fn absolute_path_under_repo_root_is_protected() {
std::env::set_var("YANA_REPO_ROOT", "/workspaces/Yana-AI");
let hit = protected_hit("/workspaces/Yana-AI/core/rules/00-meta.md", &prot());
std::env::remove_var("YANA_REPO_ROOT");
assert!(hit.is_some(), "absolute path under repo root must be blocked");
}
#[test]
fn absolute_path_via_pwd_fallback_with_symlinked_cwd_is_protected() {
std::env::remove_var("YANA_REPO_ROOT");
let prior_pwd = std::env::var("PWD").ok();
std::env::set_var("PWD", "/var/folders/fake/tmp.abc123");
let hit = protected_hit("/var/folders/fake/tmp.abc123/core/rules/00-meta.md", &prot());
match prior_pwd {
Some(v) => std::env::set_var("PWD", v),
None => std::env::remove_var("PWD"),
}
assert!(hit.is_some(), "absolute path resolved via $PWD fallback must be blocked");
}
#[test]
fn sibling_dir_is_not_protected() {
assert!(protected_hit("core/rulesets-public/x", &prot()).is_none());
}
#[test]
fn unrelated_absolute_path_not_treated_as_repo_relative() {
std::env::set_var("YANA_REPO_ROOT", "/workspaces/Yana-AI");
let hit = protected_hit("/etc/passwd", &prot());
std::env::remove_var("YANA_REPO_ROOT");
assert!(hit.is_none());
}
fn entry_points() -> Vec<String> {
vec!["scripts/yana-rt-wrapper.js".to_string()]
}
#[test]
fn registered_entry_point_is_hit() {
assert!(entry_point_hit("scripts/yana-rt-wrapper.js", &entry_points()).is_some());
}
#[test]
fn dotdot_path_to_entry_point_is_hit() {
assert!(entry_point_hit("scripts/../scripts/yana-rt-wrapper.js", &entry_points()).is_some());
}
#[test]
fn absolute_path_to_entry_point_is_hit() {
std::env::set_var("YANA_REPO_ROOT", "/workspaces/Yana-AI");
let hit = entry_point_hit(
"/workspaces/Yana-AI/scripts/yana-rt-wrapper.js",
&entry_points(),
);
std::env::remove_var("YANA_REPO_ROOT");
assert!(hit.is_some());
}
#[test]
fn sibling_script_is_not_hit() {
assert!(entry_point_hit("scripts/other-wrapper.js", &entry_points()).is_none());
}
#[test]
fn bin_yana_is_registered_entry_point() {
assert!(entry_point_hit("bin/yana", &entry_point_prefixes()).is_some());
}
#[test]
fn npm_install_js_is_registered_entry_point() {
assert!(entry_point_hit("scripts/npm-install.js", &entry_point_prefixes()).is_some());
}
#[test]
fn unrelated_file_is_not_hit() {
assert!(entry_point_hit("src/main.rs", &entry_points()).is_none());
}
#[test]
fn env_extension_adds_entry_point() {
std::env::set_var("YANA_ENTRY_POINT_PATHS", "scripts/other-wrapper.sh:bin/cli.js");
let points = entry_point_prefixes();
std::env::remove_var("YANA_ENTRY_POINT_PATHS");
assert!(points.iter().any(|p| p == "scripts/yana-rt-wrapper.js"));
assert!(points.iter().any(|p| p == "scripts/other-wrapper.sh"));
assert!(points.iter().any(|p| p == "bin/cli.js"));
}
}