use crate::commands::validate;
use crate::manifest::AppType;
use anyhow::{bail, Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn run(
project_path: &Path,
target: &str,
version: Option<&str>,
out: Option<&Path>,
) -> Result<()> {
let manifest = validate::run(project_path)?;
match (target, manifest.app_type) {
("all", AppType::Native) => {
bail!("native (cdylib) apps cannot ship as Architecture: all; use --target arm64 or amd64")
}
("amd64" | "arm64", AppType::Bun) => {
bail!("bun apps must ship as Architecture: all; use --target all")
}
("amd64" | "arm64" | "all", _) => {}
(other, _) => bail!("unsupported --target '{}': use amd64, arm64, or all", other),
}
let project_path = project_path.canonicalize()?;
let script = locate_deb_script(&project_path)?;
match manifest.app_type {
AppType::Bun => {
run_step(
"bun install",
Command::new("bun")
.arg("install")
.current_dir(&project_path),
)?;
run_step(
"bun run build",
Command::new("bun")
.args(["run", "build"])
.current_dir(&project_path),
)
.ok();
if manifest.has_ui {
let ui_dir = project_path.join(
manifest
.ui_path
.as_deref()
.unwrap_or("ui/dist")
.split('/')
.next()
.unwrap_or("ui"),
);
if ui_dir.is_dir() {
run_step(
"bun install (ui)",
Command::new("bun").arg("install").current_dir(&ui_dir),
)
.ok();
run_step(
"bun run build (ui)",
Command::new("bun")
.args(["run", "build"])
.current_dir(&ui_dir),
)
.ok();
}
}
}
AppType::Native => {
let rust_target = match target {
"amd64" => "x86_64-unknown-linux-gnu",
"arm64" => "aarch64-unknown-linux-gnu",
_ => unreachable!("validated above"),
};
run_step(
"cargo build --release",
Command::new("cargo")
.args(["build", "--release", "--target", rust_target])
.current_dir(&project_path),
)?;
let crate_underscored = manifest.name.replace('-', "_");
let so_src = project_path
.join("target")
.join(rust_target)
.join("release")
.join(format!("lib{}.so", crate_underscored));
if !so_src.exists() {
bail!(
"expected cdylib output not found: {} — does Cargo.toml set crate-type = [\"cdylib\"]?",
so_src.display()
);
}
let stage_dir = project_path
.join("build")
.join(target);
std::fs::create_dir_all(&stage_dir)?;
std::fs::copy(&so_src, stage_dir.join("app.so"))?;
}
AppType::Standalone => {
let rust_target = match target {
"amd64" => "x86_64-unknown-linux-gnu",
"arm64" => "aarch64-unknown-linux-gnu",
_ => unreachable!("validated above"),
};
run_step(
"cargo build --release",
Command::new("cargo")
.args(["build", "--release", "--target", rust_target])
.current_dir(&project_path),
)?;
}
}
let module_name = &manifest.name;
let mut cmd = Command::new(&script);
cmd.arg("--module").arg(module_name);
cmd.arg("--target").arg(target);
if let Some(v) = version {
cmd.arg("--version").arg(v);
}
if let Some(out) = out {
cmd.arg("--out").arg(out);
}
cmd.current_dir(script.parent().unwrap().parent().unwrap().parent().unwrap());
let status = cmd.status().with_context(|| "spawning deb build script")?;
if !status.success() {
bail!("create_app_deb_package.sh exited with status {}", status);
}
Ok(())
}
fn locate_deb_script(start: &Path) -> Result<PathBuf> {
let mut cur = Some(start);
while let Some(dir) = cur {
let candidate = dir.join("infra/scripts/create_app_deb_package.sh");
if candidate.is_file() {
return Ok(candidate);
}
cur = dir.parent();
}
bail!(
"could not find infra/scripts/create_app_deb_package.sh by walking upwards from {} — \
is the project under a Node Lightning OS workspace?",
start.display()
)
}
fn run_step(label: &str, cmd: &mut Command) -> Result<()> {
println!("» {}", label);
let status = cmd.status().with_context(|| format!("spawning {}", label))?;
if !status.success() {
bail!("{} failed (exit {})", label, status);
}
Ok(())
}