use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MonorepoConfig {
pub tag_prefix: Option<String>,
pub dir: Option<String>,
}
pub fn prepend_monorepo_dir(path: &str, dir: &str) -> Option<String> {
if dir.is_empty() || path.is_empty() {
return None;
}
if path.trim_start().starts_with("{{") {
return None;
}
if Path::new(path).is_absolute() {
return None;
}
if path.starts_with('/') || path.starts_with('\\') {
return None;
}
let bytes = path.as_bytes();
if bytes.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_alphabetic() {
return None;
}
let trimmed_dir = dir.trim_end_matches(['/', '\\']);
if path == trimmed_dir {
return None;
}
if let Some(rest) = path.strip_prefix(trimmed_dir)
&& (rest.starts_with('/') || rest.starts_with('\\'))
{
return None;
}
Some(format!("{}/{}", trimmed_dir, path))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prepends_relative_path() {
assert_eq!(
prepend_monorepo_dir("LICENSE", "sub1").as_deref(),
Some("sub1/LICENSE")
);
}
#[test]
fn leaves_absolute_path() {
assert_eq!(prepend_monorepo_dir("/etc/passwd", "sub1"), None);
}
#[test]
fn leaves_leading_separator_path_on_every_host() {
assert_eq!(prepend_monorepo_dir("/etc/passwd", "sub1"), None);
assert_eq!(prepend_monorepo_dir("\\rooted\\x", "sub1"), None);
}
#[test]
fn leaves_windows_drive_path() {
assert_eq!(prepend_monorepo_dir("C:\\Users\\me", "sub1"), None);
}
#[test]
fn leaves_already_prefixed() {
assert_eq!(prepend_monorepo_dir("sub1/LICENSE", "sub1"), None);
assert_eq!(prepend_monorepo_dir("sub1/", "sub1"), None);
assert_eq!(prepend_monorepo_dir("sub1", "sub1"), None);
}
#[test]
fn dir_with_trailing_slash_normalised() {
assert_eq!(
prepend_monorepo_dir("LICENSE", "sub1/").as_deref(),
Some("sub1/LICENSE")
);
}
#[test]
fn leaves_template_paths() {
assert_eq!(prepend_monorepo_dir("{{ .ProjectDir }}/x", "sub1"), None);
assert_eq!(prepend_monorepo_dir(" {{ .X }}", "sub1"), None);
}
#[test]
fn handles_glob_patterns() {
assert_eq!(
prepend_monorepo_dir("*.txt", "sub1").as_deref(),
Some("sub1/*.txt")
);
assert_eq!(
prepend_monorepo_dir("dist/**/*.so", "sub1").as_deref(),
Some("sub1/dist/**/*.so")
);
}
#[test]
fn no_change_for_empty_inputs() {
assert_eq!(prepend_monorepo_dir("", "sub1"), None);
assert_eq!(prepend_monorepo_dir("LICENSE", ""), None);
}
}