use std::path::{Path, PathBuf};
use aion_package::{PackageOptions, package_project};
use aion_toolchain::build_project;
use anyhow::{Context, Result, bail};
use serde_json::Value;
use crate::deploy::DeployTarget;
#[derive(Clone, Debug)]
pub struct ReloadOutcome {
pub workflow_type: String,
pub content_hash: String,
pub freshly_loaded: bool,
pub route_changed: bool,
pub archive_path: PathBuf,
}
pub async fn rebuild_repackage_reload(
project_root: &Path,
gleam_path: &Path,
target: &DeployTarget,
) -> Result<ReloadOutcome> {
build_project(project_root, gleam_path)
.with_context(|| format!("failed to rebuild project at {}", project_root.display()))?;
let report = package_project(project_root, &PackageOptions::default())
.with_context(|| format!("failed to package project at {}", project_root.display()))?;
let packaged = match report.packages.len() {
1 => report
.packages
.into_iter()
.next()
.context("packaged workflow vanished after length check")?,
count => bail!(
"`aion dev` watches a single workflow, but the project at {} packaged {count} workflows; \
split them into separate projects or run the server-side deploy surface",
project_root.display()
),
};
let archive_path = packaged.output_path.clone();
let response = crate::deploy::deploy(target, &archive_path)
.await
.context("failed to hot-load the rebuilt package into the running server")?;
reload_outcome_from_response(&response, archive_path)
}
fn reload_outcome_from_response(response: &Value, archive_path: PathBuf) -> Result<ReloadOutcome> {
let string_field = |key: &str| -> Result<String> {
response
.get(key)
.and_then(Value::as_str)
.map(str::to_owned)
.with_context(|| format!("deploy response missing string field `{key}`"))
};
let bool_field = |key: &str| -> Result<bool> {
response
.get(key)
.and_then(Value::as_bool)
.with_context(|| format!("deploy response missing boolean field `{key}`"))
};
Ok(ReloadOutcome {
workflow_type: string_field("workflow_type")?,
content_hash: string_field("content_hash")?,
freshly_loaded: bool_field("freshly_loaded")?,
route_changed: bool_field("route_changed")?,
archive_path,
})
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use serde_json::json;
use super::reload_outcome_from_response;
#[test]
fn reload_outcome_reads_the_deploy_response_document() -> anyhow::Result<()> {
let response = json!({
"workflow_type": "order",
"content_hash": "abc123",
"deployed_entry_module": "order$abc123",
"entry_function": "run",
"freshly_loaded": true,
"route_changed": true,
});
let outcome = reload_outcome_from_response(&response, PathBuf::from("dist/order.aion"))?;
assert_eq!(outcome.workflow_type, "order");
assert_eq!(outcome.content_hash, "abc123");
assert!(outcome.freshly_loaded);
assert!(outcome.route_changed);
assert_eq!(outcome.archive_path, PathBuf::from("dist/order.aion"));
Ok(())
}
#[test]
fn reload_outcome_rejects_a_response_missing_a_field() {
let response = json!({ "workflow_type": "order" });
assert!(reload_outcome_from_response(&response, PathBuf::from("x.aion")).is_err());
}
}