Skip to main content

ferro_cli/deploy/
bin_detect.rs

1//! Shared web-bin detection for `docker:init` and `do:init` (D-02).
2//!
3//! Resolves the "web bin" — the binary that should be the container
4//! ENTRYPOINT and the DigitalOcean `web` service target — using a stable
5//! 4-step order so both scaffolders stay in sync by construction.
6
7use std::path::Path;
8
9use crate::project::{package_name, read_bins, read_deploy_metadata};
10
11/// Resolve the web bin name using the D-02 4-step order:
12/// 1. `[package.metadata.ferro.deploy].web_bin` explicit override
13/// 2. The bin matching `package.name`
14/// 3. The first declared `[[bin]]`
15/// 4. Fall back to `package.name` if no `[[bin]]` is declared
16pub fn detect_web_bin(project_root: &Path) -> anyhow::Result<String> {
17    if let Ok(meta) = read_deploy_metadata(project_root) {
18        if let Some(explicit) = meta.web_bin {
19            return Ok(explicit);
20        }
21    }
22    let pkg = package_name(project_root);
23    let bins = read_bins(project_root);
24    if bins.iter().any(|b| b.name == pkg) {
25        return Ok(pkg);
26    }
27    if let Some(first) = bins.first() {
28        return Ok(first.name.clone());
29    }
30    Ok(pkg)
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use std::fs;
37    use tempfile::TempDir;
38
39    fn write_cargo(body: &str) -> TempDir {
40        let tmp = TempDir::new().unwrap();
41        fs::write(tmp.path().join("Cargo.toml"), body).unwrap();
42        tmp
43    }
44
45    #[test]
46    fn bin_detect_explicit_override() {
47        let tmp = write_cargo(
48            r#"
49[package]
50name = "myapp"
51version = "0.1.0"
52
53[[bin]]
54name = "alpha"
55
56[[bin]]
57name = "beta"
58
59[package.metadata.ferro.deploy]
60web_bin = "foo"
61"#,
62        );
63        assert_eq!(detect_web_bin(tmp.path()).unwrap(), "foo");
64    }
65
66    #[test]
67    fn bin_detect_package_name_match() {
68        let tmp = write_cargo(
69            r#"
70[package]
71name = "api"
72version = "0.1.0"
73
74[[bin]]
75name = "api"
76
77[[bin]]
78name = "worker"
79"#,
80        );
81        assert_eq!(detect_web_bin(tmp.path()).unwrap(), "api");
82    }
83
84    #[test]
85    fn bin_detect_first_bin_fallback() {
86        let tmp = write_cargo(
87            r#"
88[package]
89name = "other"
90version = "0.1.0"
91
92[[bin]]
93name = "alpha"
94
95[[bin]]
96name = "beta"
97"#,
98        );
99        assert_eq!(detect_web_bin(tmp.path()).unwrap(), "alpha");
100    }
101
102    #[test]
103    fn bin_detect_no_bins_uses_package_name() {
104        let tmp = write_cargo(
105            r#"
106[package]
107name = "myapp"
108version = "0.1.0"
109"#,
110        );
111        assert_eq!(detect_web_bin(tmp.path()).unwrap(), "myapp");
112    }
113}