use crate::manifest::Manifest;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveredService {
pub name: String,
pub path: String,
pub markers_found: Vec<String>,
}
pub fn discover_services(root: &Path, manifest: &Manifest) -> Vec<DiscoveredService> {
discover_services_with_ignore(root, manifest, &[])
}
pub fn discover_services_with_ignore(
root: &Path,
manifest: &Manifest,
extra_ignore: &[String],
) -> Vec<DiscoveredService> {
let patterns = manifest.effective_discovery_paths();
let markers = &manifest.discovery.markers;
let effective_markers: Vec<String> = if markers.is_empty() {
default_markers()
} else {
markers.clone()
};
let ignore_patterns: Vec<glob::Pattern> = manifest
.discovery
.ignore
.iter()
.chain(extra_ignore.iter())
.filter_map(|p| glob::Pattern::new(p).ok())
.collect();
let mut discovered: Vec<DiscoveredService> = Vec::new();
for pattern in &patterns {
let full_pattern = root.join(pattern);
let pattern_str = full_pattern.to_string_lossy();
let entries = match glob::glob(&pattern_str) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
if !entry.is_dir() {
continue;
}
let rel_path = entry
.strip_prefix(root)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| entry.to_string_lossy().to_string());
if ignore_patterns.iter().any(|p| p.matches(&rel_path)) {
continue;
}
let found_markers = markers_in_dir(&entry, &effective_markers);
if found_markers.is_empty() {
continue;
}
let name = entry
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
if discovered.iter().any(|d| d.path == rel_path) {
continue;
}
discovered.push(DiscoveredService {
name,
path: rel_path,
markers_found: found_markers,
});
}
}
discovered
}
pub fn dir_has_markers(root: &Path, rel_path: &str, markers: &[String]) -> bool {
let full = root.join(rel_path);
if !full.is_dir() {
return false;
}
!markers_in_dir(&full, markers).is_empty()
}
fn markers_in_dir(dir: &Path, markers: &[String]) -> Vec<String> {
markers
.iter()
.filter(|m| dir.join(m.as_str()).exists())
.cloned()
.collect()
}
fn default_markers() -> Vec<String> {
[
"Cargo.toml",
"Dockerfile",
"go.mod",
"package.json",
"pyproject.toml",
"requirements.txt",
]
.iter()
.map(|s| s.to_string())
.collect()
}