use anyhow::{Context, Result};
use std::path::Path;
use yah_almanac::OnChangeConfig;
use crate::config::CloudConfig;
use crate::reconciler::{MesofactStaticReconciler, ReconcileCtx};
pub async fn dispatch_on_change(
on_change: &OnChangeConfig,
workspace_root: &Path,
env: &str,
) -> Result<()> {
match on_change {
OnChangeConfig::MesofactRebuild { service, route } => {
revalidate_mesofact(service, route, workspace_root, env).await
}
OnChangeConfig::Reload { service } => {
tracing::info!(
service,
env,
"almanac on_change: reload — the artifact write is the update, \
nothing to rebuild"
);
Ok(())
}
}
}
async fn revalidate_mesofact(
service_name: &str,
route: &str,
workspace_root: &Path,
env: &str,
) -> Result<()> {
let config = CloudConfig::load(workspace_root)
.with_context(|| format!("loading cloud config from {}", workspace_root.display()))?;
let svc = config
.service(service_name)
.with_context(|| format!("service {service_name:?} not found in .yah/services/"))?;
let component = svc
.service
.components
.iter()
.find(|c| c.kind == "mesofact-static")
.with_context(|| format!("service {service_name:?} has no mesofact-static component"))?;
let mirror = svc
.mirrors
.get(env)
.with_context(|| format!("service {service_name:?} has no mirror for env {env:?}"))?;
let ctx = ReconcileCtx {
workspace_root,
service: &svc.service,
component,
mirror,
env,
scope: crate::reconciler::ProviderScope::singleton(),
};
let reconciler = MesofactStaticReconciler::new();
let result = reconciler.revalidate_static(ctx, route).await?;
tracing::info!(
service = service_name,
env,
public_url = ?result.public_url,
"almanac on_change: mesofact revalidate complete"
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn write_toml(path: &std::path::Path, content: &str) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(path, content).unwrap();
}
#[tokio::test]
async fn missing_service_returns_error() {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
std::fs::create_dir_all(root.join(".yah")).unwrap();
let on_change = OnChangeConfig::MesofactRebuild {
service: "no-such-svc".to_string(),
route: "/releases".to_string(),
};
let err = dispatch_on_change(&on_change, root, "prod")
.await
.unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("no-such-svc"), "got: {msg}");
}
#[tokio::test]
async fn missing_mirror_env_returns_error() {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
write_toml(
&root.join(".yah/services/dev-yah/service.toml"),
r#"schema_version = 1
name = "dev-yah"
domain = "yah.dev"
[[components]]
id = "site"
kind = "mesofact-static"
path = "app/yah/web"
role = "static"
wave = 0
"#,
);
let on_change = OnChangeConfig::MesofactRebuild {
service: "dev-yah".to_string(),
route: "/releases".to_string(),
};
let err = dispatch_on_change(&on_change, root, "prod")
.await
.unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("no mirror"), "got: {msg}");
}
#[tokio::test]
async fn reload_dispatches_nothing_and_needs_no_service_config() {
let tmp = TempDir::new().unwrap();
let on_change = OnChangeConfig::Reload {
service: "yah-cloud-admin".to_string(),
};
dispatch_on_change(&on_change, tmp.path(), "prod")
.await
.expect("a reload must succeed against an empty workspace");
}
}