use super::{FunctionError, Result};
const COMPONENTIZE_PY_VERSION: &str = "0.25.0";
const JCO_VERSION: &str = "1.25.2";
pub(super) async fn build_project(dir: Option<std::path::PathBuf>) -> Result<()> {
let dir = dir.unwrap_or_else(|| std::path::PathBuf::from("."));
let component = if dir.join("Cargo.toml").exists() {
build_rust(&dir).await?
} else if dir.join("package.json").exists() {
build_js(&dir).await?
} else if dir.join("pyproject.toml").exists() {
build_python(&dir).await?
} else {
return Err(FunctionError::NoComponent);
};
println!("built {}", component.display());
println!(
" deploy: boatramp function deploy <name> --component {}",
component.display()
);
Ok(())
}
async fn build_rust(dir: &std::path::Path) -> Result<std::path::PathBuf> {
let status = tokio::process::Command::new("cargo")
.args(["build", "--release", "--target", "wasm32-wasip2"])
.current_dir(dir)
.status()
.await?;
if !status.success() {
return Err(FunctionError::BuildFailed);
}
let release = dir.join("target/wasm32-wasip2/release");
std::fs::read_dir(&release)
.ok()
.and_then(|entries| {
entries
.flatten()
.map(|e| e.path())
.find(|p| p.extension().and_then(|e| e.to_str()) == Some("wasm"))
})
.ok_or(FunctionError::NoComponent)
}
async fn build_js(dir: &std::path::Path) -> Result<std::path::PathBuf> {
let name = dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("function");
let out = format!("{name}.wasm");
let status = tokio::process::Command::new("npx")
.args([
"--yes",
&format!("@bytecodealliance/jco@{JCO_VERSION}"),
"componentize",
"handler.js",
"--wit",
"wit",
"-n",
"wasi:http/proxy",
"-o",
&out,
])
.current_dir(dir)
.status()
.await?;
if !status.success() {
return Err(FunctionError::BuildFailed);
}
let component = dir.join(&out);
if !component.exists() {
return Err(FunctionError::NoComponent);
}
Ok(component)
}
async fn build_python(dir: &std::path::Path) -> Result<std::path::PathBuf> {
let name = dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("function");
let out = format!("{name}.wasm");
let build_args = [
"-d",
"wit",
"-w",
"wasi:http/proxy",
"componentize",
"app",
"-o",
&out,
];
let status = if which("componentize-py") {
tokio::process::Command::new("componentize-py")
.args(build_args)
.current_dir(dir)
.status()
.await?
} else {
tokio::process::Command::new("uvx")
.arg("--from")
.arg(format!("componentize-py=={COMPONENTIZE_PY_VERSION}"))
.arg("componentize-py")
.args(build_args)
.current_dir(dir)
.status()
.await?
};
if !status.success() {
return Err(FunctionError::BuildFailed);
}
let component = dir.join(&out);
if !component.exists() {
return Err(FunctionError::NoComponent);
}
Ok(component)
}
fn which(cmd: &str) -> bool {
std::env::var_os("PATH")
.map(|paths| {
std::env::split_paths(&paths).any(|dir| {
let p = dir.join(cmd);
p.is_file()
})
})
.unwrap_or(false)
}