use std::path::{Path, PathBuf};
use thiserror::Error;
use crate::config::FleetEntry;
#[derive(Debug, Error)]
pub enum EnrollError {
#[error("target path does not exist or is not a directory: {0}")]
BadTarget(String),
#[error("template not found: {template} (looked in {dir})")]
MissingTemplate { template: String, dir: String },
#[error("io error at {path}: {source}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
}
fn walk(root: &Path) -> Result<Vec<PathBuf>, EnrollError> {
let mut out = Vec::new();
visit(root, &mut out)?;
out.sort();
Ok(out)
}
fn visit(dir: &Path, out: &mut Vec<PathBuf>) -> Result<(), EnrollError> {
let entries = std::fs::read_dir(dir).map_err(|e| EnrollError::Io {
path: dir.display().to_string(),
source: e,
})?;
for entry in entries {
let entry = entry.map_err(|e| EnrollError::Io {
path: dir.display().to_string(),
source: e,
})?;
let p = entry.path();
let ft = entry.file_type().map_err(|e| EnrollError::Io {
path: p.display().to_string(),
source: e,
})?;
if ft.is_dir() {
visit(&p, out)?;
} else {
out.push(p);
}
}
Ok(())
}
pub fn enroll(entry: &FleetEntry, templates_root: &Path) -> Result<Vec<String>, EnrollError> {
let target = Path::new(&entry.path);
let meta = std::fs::metadata(target).ok();
let is_dir = meta.as_ref().map(|m| m.is_dir()).unwrap_or(false);
if !is_dir {
return Err(EnrollError::BadTarget(entry.path.clone()));
}
let tpl_dir = templates_root.join(&entry.template);
if !tpl_dir.exists() {
return Err(EnrollError::MissingTemplate {
template: entry.template.clone(),
dir: tpl_dir.display().to_string(),
});
}
let vars = [("name", entry.name.as_str()), ("repo", entry.repo.as_str())];
let mut written = Vec::new();
for src in walk(&tpl_dir)? {
let rel = src.strip_prefix(&tpl_dir).expect("walk under tpl_dir");
let dest = target.join(rel);
let raw = std::fs::read_to_string(&src).map_err(|e| EnrollError::Io {
path: src.display().to_string(),
source: e,
})?;
let mut rendered = raw;
for (k, v) in &vars {
let key = format!("{{{{{}}}}}", k);
rendered = rendered.replace(&key, v);
}
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent).map_err(|e| EnrollError::Io {
path: parent.display().to_string(),
source: e,
})?;
}
std::fs::write(&dest, &rendered).map_err(|e| EnrollError::Io {
path: dest.display().to_string(),
source: e,
})?;
written.push(rel.to_string_lossy().replace('\\', "/"));
}
let pkg_path = target.join("package.json");
if pkg_path.exists() {
if let Ok(text) = std::fs::read_to_string(&pkg_path) {
if let Ok(pkg) = serde_json::from_str::<serde_json::Value>(&text) {
let version = pkg
.get("version")
.and_then(|v| v.as_str())
.unwrap_or("0.0.0")
.to_string();
let manifest_path = target.join(".release-please-manifest.json");
let manifest = serde_json::json!({ ".": version });
let body = serde_json::to_string_pretty(&manifest).unwrap_or_else(|_| "{}".into());
let _ = std::fs::write(&manifest_path, format!("{}\n", body));
written.push(".release-please-manifest.json".to_string());
}
}
}
Ok(written)
}