1use crate::config::Config;
2use crate::os_detector::detect_os;
3use cmdhub_shared::AciCommandContract;
4use serde::Serialize;
5
6#[derive(Serialize)]
7pub struct UsageDto {
8 pub cmd_path: String,
9 pub example_template: Option<String>,
10}
11
12#[derive(Serialize)]
13pub struct MinimalDto {
14 pub cmd_path: String,
15}
16
17#[derive(Serialize)]
18pub struct FullDto {
19 pub app_id: String,
20 pub name: String,
21 pub cmd_path: String,
22 pub node_type: String,
23 pub description: String,
24 pub risk_level: String,
25 pub example_template: Option<String>,
26 pub install_command: Option<String>,
27}
28
29pub fn resolve_install_command(contract: &AciCommandContract, config: &Config) -> Option<String> {
30 let instructions = contract.install_instructions.as_ref()?;
31
32 let os = config.install.os.clone().or_else(detect_os);
33 let sys_installer = os.as_ref().map(|o| map_os_to_package_manager(o));
34
35 let mut resolved = None;
36 if let Some(installer) = sys_installer {
37 if let Some(cmd) = instructions.get_command(installer) {
38 resolved = Some((cmd.clone(), is_system_installer(installer)));
39 }
40 }
41
42 if resolved.is_none() {
43 for pm in &config.install.package_managers {
44 if let Some(cmd) = instructions.get_command(pm) {
45 resolved = Some((cmd.clone(), is_system_installer(pm)));
46 break;
47 }
48 }
49 }
50
51 if let Some((cmd, is_sys)) = resolved {
52 if is_sys && !is_root_user() {
53 Some(format!("sudo {}", cmd))
54 } else {
55 Some(cmd)
56 }
57 } else {
58 None
59 }
60}
61
62#[cfg(unix)]
63fn is_root_user() -> bool {
64 unsafe { libc::getuid() == 0 }
65}
66
67#[cfg(not(unix))]
68fn is_root_user() -> bool {
69 false
70}
71
72fn is_system_installer(installer: &str) -> bool {
73 matches!(
74 installer,
75 "apt" | "pacman" | "dnf" | "apk" | "emerge" | "zypper" | "yum"
76 )
77}
78
79fn map_os_to_package_manager(os: &str) -> &str {
80 match os {
81 "macos" => "brew",
82 "arch" => "pacman",
83 "ubuntu" | "debian" => "apt",
84 "fedora" => "dnf",
85 "centos" | "rhel" => "yum",
86 "gentoo" => "emerge",
87 "alpine" => "apk",
88 "opensuse" => "zypper",
89 "nixos" => "nix-env",
90 other => other,
91 }
92}
93
94pub fn format_results(
95 contracts: Vec<AciCommandContract>,
96 mode: &str,
97 config: &Config,
98) -> serde_json::Value {
99 match mode {
100 "usage" => {
101 let dtos: Vec<UsageDto> = contracts
102 .into_iter()
103 .map(|c| UsageDto {
104 cmd_path: c.cmd_path,
105 example_template: c.example_template,
106 })
107 .collect();
108 serde_json::to_value(&dtos).unwrap()
109 }
110 "minimal" => {
111 let dtos: Vec<MinimalDto> = contracts
112 .into_iter()
113 .map(|c| MinimalDto {
114 cmd_path: c.cmd_path,
115 })
116 .collect();
117 serde_json::to_value(&dtos).unwrap()
118 }
119 _ => {
120 let dtos: Vec<FullDto> = contracts
121 .into_iter()
122 .map(|c| {
123 let install_command = resolve_install_command(&c, config);
124 FullDto {
125 app_id: c.app_id,
126 name: c.name,
127 cmd_path: c.cmd_path,
128 node_type: format!("{:?}", c.node_type).to_lowercase(),
129 description: c.description,
130 risk_level: format!("{:?}", c.risk_level).to_lowercase(),
131 example_template: c.example_template,
132 install_command,
133 }
134 })
135 .collect();
136 serde_json::to_value(&dtos).unwrap()
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use cmdhub_shared::InstallInstructions;
145
146 #[test]
147 fn test_resolve_install_command_sudo() {
148 let contract = AciCommandContract {
149 app_id: "test".to_string(),
150 name: "test".to_string(),
151 cmd_path: "test".to_string(),
152 node_type: cmdhub_shared::NodeType::Root,
153 description: "test".to_string(),
154 risk_level: cmdhub_shared::RiskLevel::Safe,
155 example_template: None,
156 install_instructions: Some(InstallInstructions {
157 brew: Some("brew install test".to_string()),
158 apt: Some("apt-get install test".to_string()),
159 pacman: None,
160 cargo: None,
161 others: std::collections::HashMap::new(),
162 }),
163 };
164
165 let mut config = Config::default();
166 config.install.os = Some("debian".to_string());
167
168 let cmd = resolve_install_command(&contract, &config).unwrap();
169 if is_root_user() {
170 assert_eq!(cmd, "apt-get install test");
171 } else {
172 assert_eq!(cmd, "sudo apt-get install test");
173 }
174
175 config.install.os = Some("macos".to_string());
176 let cmd_brew = resolve_install_command(&contract, &config).unwrap();
177 assert_eq!(cmd_brew, "brew install test");
178 }
179}