use anyhow::{bail, Result};
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use crate::commands::dev::host::monorepo::{
burger_dev_checkouts, dev_burger_binary, git_common_dir, BURGER_DIR_ENV,
};
pub const BURGER_BIN_ENV: &str = "NODE_BURGER_BIN";
const BINARY: &str = "node-app-burger";
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct BurgerBinSearch {
pub explicit: Option<PathBuf>,
pub checkouts: Vec<PathBuf>,
pub path_var: Option<OsString>,
}
impl BurgerBinSearch {
pub fn for_project(project: &Path) -> Self {
Self {
explicit: std::env::var_os(BURGER_BIN_ENV)
.filter(|value| !value.is_empty())
.map(PathBuf::from),
checkouts: burger_dev_checkouts(
project,
std::env::var_os(BURGER_DIR_ENV),
git_common_dir(project).as_deref(),
),
path_var: std::env::var_os("PATH"),
}
}
pub fn resolve(&self) -> Result<PathBuf> {
if let Some(explicit) = &self.explicit {
if explicit.is_file() {
return Ok(explicit.clone());
}
bail!("{BURGER_BIN_ENV}={} does not exist", explicit.display());
}
if let Some(built) = dev_burger_binary(&self.checkouts) {
return Ok(built);
}
let on_path: Vec<PathBuf> = self
.path_var
.iter()
.flat_map(std::env::split_paths)
.map(|dir| dir.join(BINARY))
.collect();
if let Some(found) = on_path.iter().find(|candidate| candidate.is_file()) {
return Ok(found.clone());
}
let checkouts: Vec<String> = self
.checkouts
.iter()
.map(|checkout| format!(" {} (debug build)", checkout.display()))
.collect();
let path_entries: Vec<String> = on_path
.iter()
.map(|candidate| format!(" {}", candidate.display()))
.collect();
bail!(
"node-app-burger not found. Build a node-app-burger checkout (`cargo build`), install \
the node-app-burger apt package from https://apt.economy1.cloud (stable main), or \
set {BURGER_BIN_ENV}. Looked in:\n{}\n{}",
checkouts.join("\n"),
path_entries.join("\n")
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn touch(path: &Path) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, b"").unwrap();
}
fn built_checkout(dir: &Path) -> PathBuf {
touch(&dir.join("Cargo.toml"));
let binary = dir.join("target/debug/node-app-burger");
touch(&binary);
binary
}
#[test]
fn explicit_env_wins_and_must_exist() {
let tmp = tempfile::tempdir().unwrap();
let checkout = tmp.path().join("node-app-burger");
built_checkout(&checkout);
let explicit = tmp.path().join("custom/node-app-burger");
let search = BurgerBinSearch {
explicit: Some(explicit.clone()),
checkouts: vec![checkout],
path_var: None,
};
let err = search.resolve().unwrap_err().to_string();
assert!(err.contains("NODE_BURGER_BIN="), "{err}");
touch(&explicit);
assert_eq!(search.resolve().unwrap(), explicit);
}
#[test]
fn a_built_checkout_wins_over_path() {
let tmp = tempfile::tempdir().unwrap();
let binary = built_checkout(&tmp.path().join("node-app-burger"));
let bin_dir = tmp.path().join("bin");
touch(&bin_dir.join("node-app-burger"));
let search = BurgerBinSearch {
explicit: None,
checkouts: vec![tmp.path().join("node-app-burger")],
path_var: Some(bin_dir.into_os_string()),
};
assert_eq!(search.resolve().unwrap(), binary);
}
#[test]
fn path_is_the_fallback() {
let tmp = tempfile::tempdir().unwrap();
let bin_dir = tmp.path().join("bin");
touch(&bin_dir.join("node-app-burger"));
let search = BurgerBinSearch {
explicit: None,
checkouts: vec![tmp.path().join("no-checkout-here")],
path_var: Some(bin_dir.clone().into_os_string()),
};
assert_eq!(search.resolve().unwrap(), bin_dir.join("node-app-burger"));
}
#[test]
fn not_found_names_every_place_it_looked() {
let tmp = tempfile::tempdir().unwrap();
let search = BurgerBinSearch {
explicit: None,
checkouts: vec![tmp.path().join("node-app-burger")],
path_var: Some(tmp.path().join("bin").into_os_string()),
};
let err = search.resolve().unwrap_err().to_string();
assert!(err.contains("node-app-burger not found"), "{err}");
assert!(
err.contains(&tmp.path().join("node-app-burger").display().to_string()),
"{err}"
);
assert!(
err.contains(&tmp.path().join("bin/node-app-burger").display().to_string()),
"{err}"
);
}
#[test]
fn for_project_uses_the_c1_dev_checkout_order() {
let tmp = tempfile::tempdir().unwrap();
let search = BurgerBinSearch::for_project(tmp.path());
assert_eq!(
search.checkouts.last(),
Some(&tmp.path().join("modules").join("node-app-burger"))
);
}
}