use std::path::{Path, PathBuf};
use std::process::{Command, Output};
pub type TestError = Box<dyn std::error::Error>;
pub fn run_cli(current_dir: &Path, args: &[&str]) -> Result<Output, TestError> {
Ok(Command::new(env!("CARGO_BIN_EXE_aion"))
.args(args)
.current_dir(current_dir)
.output()?)
}
pub fn success_json(output: &Output) -> Result<serde_json::Value, TestError> {
if output.status.code() != Some(0) {
return Err(format!(
"expected success, got {:?}; stdout: {} stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
.into());
}
Ok(serde_json::from_slice(&output.stdout)?)
}
pub fn scaffold_project(
parent: &Path,
name: &str,
extra: &[&str],
) -> Result<(PathBuf, serde_json::Value), TestError> {
let mut args = vec!["new", name];
args.extend_from_slice(extra);
let output = run_cli(parent, &args)?;
let report = success_json(&output)?;
Ok((parent.join(name), report))
}
pub fn patch_aion_flow_to_workspace(project: &Path) -> Result<(), TestError> {
let manifest_path = project.join("gleam.toml");
let manifest = std::fs::read_to_string(&manifest_path)?;
let published = "aion_flow = \">= 0.4.0 and < 0.5.0\"";
if !manifest.contains(published) {
return Err(format!(
"emitted gleam.toml must require the published aion_flow range; got:\n{manifest}"
)
.into());
}
let sdk = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../gleam/aion_flow")
.canonicalize()?;
let patched = manifest.replace(
published,
&format!("aion_flow = {{ path = \"{}\" }}", sdk.display()),
);
std::fs::write(manifest_path, patched)?;
Ok(())
}
pub fn gleam_build(project: &Path) -> Result<(), TestError> {
let status = Command::new("gleam")
.arg("build")
.current_dir(project)
.status()
.map_err(|error| {
format!(
"the scaffold gate requires the `gleam` CLI on PATH (failed to \
spawn `gleam build` in {}: {error}); this gate fails loudly by \
design — never reintroduce a skip",
project.display()
)
})?;
if !status.success() {
return Err(format!(
"`gleam build` failed in {} with {status}: the scaffolded project must compile",
project.display()
)
.into());
}
Ok(())
}
pub fn package_project(project: &Path, name: &str) -> Result<PathBuf, TestError> {
let output = run_cli(project, &["package", "."])?;
let report = success_json(&output)?;
let packaged = report["packages"]
.as_array()
.and_then(|packages| packages.first())
.ok_or("aion package must report one packaged workflow")?;
if packaged["workflow_type"] != name {
return Err(format!("packaged workflow type must be {name}; report: {report}").into());
}
let archive = project.join(format!("{name}.aion"));
if !archive.is_file() {
return Err(format!(
"aion package did not produce the declared archive {}",
archive.display()
)
.into());
}
Ok(archive)
}