1#![forbid(unsafe_code)]
9
10use std::path::{Path, PathBuf};
11use std::process::{Command, ExitCode};
12use vanta_core::StoreKey;
13use vanta_state::State;
14
15#[must_use]
18pub fn run() -> ExitCode {
19 let invoked = std::env::args()
20 .next()
21 .and_then(|p| {
22 Path::new(&p)
23 .file_name()
24 .map(|s| s.to_string_lossy().into_owned())
25 })
26 .unwrap_or_default();
27 let name = invoked.strip_suffix(".exe").unwrap_or(&invoked).to_string();
28 let args: Vec<String> = std::env::args().skip(1).collect();
29
30 match dispatch(&name, &args) {
31 Ok(code) => code,
32 Err(msg) => {
33 eprintln!("vanta-shim: {msg}");
34 ExitCode::from(1)
35 }
36 }
37}
38
39pub fn dispatch(name: &str, args: &[String]) -> Result<ExitCode, String> {
43 let home = home().ok_or("cannot determine VANTA_HOME")?;
44 let state = State::open(&home.join("state.db")).map_err(|e| e.to_string())?;
45 let id = state
46 .current()
47 .map_err(|e| e.to_string())?
48 .ok_or("no active generation")?;
49 let generation = state
50 .get_generation(id)
51 .map_err(|e| e.to_string())?
52 .ok_or("active generation is missing")?;
53 let (_, key) = generation
54 .tools
55 .iter()
56 .find(|(tool, _)| tool == name)
57 .ok_or_else(|| format!("`{name}` is not managed by vanta"))?;
58 let key = StoreKey::new(key.clone()).map_err(|e| e.to_string())?;
61 let entry = home.join("store").join(key.as_str());
62 let bin = find_bin(&entry, name)
63 .ok_or_else(|| format!("executable for `{name}` not found in {}", entry.display()))?;
64 exec(&bin, args)
65}
66
67fn find_bin(entry: &Path, name: &str) -> Option<PathBuf> {
68 let candidates = [
69 entry.join("bin").join(name),
70 entry.join(name),
71 entry.join("bin").join(format!("{name}.exe")),
72 entry.join(format!("{name}.exe")),
73 ];
74 candidates.into_iter().find(|c| c.is_file())
75}
76
77#[cfg(unix)]
78fn exec(bin: &Path, args: &[String]) -> Result<ExitCode, String> {
79 use std::os::unix::process::CommandExt;
80 let err = Command::new(bin).args(args).exec();
82 Err(format!("exec {}: {err}", bin.display()))
83}
84
85#[cfg(not(unix))]
86fn exec(bin: &Path, args: &[String]) -> Result<ExitCode, String> {
87 let status = Command::new(bin)
88 .args(args)
89 .status()
90 .map_err(|e| format!("running {}: {e}", bin.display()))?;
91 Ok(ExitCode::from(status.code().unwrap_or(1) as u8))
92}
93
94fn home() -> Option<PathBuf> {
95 if let Ok(h) = std::env::var("VANTA_HOME") {
96 return Some(PathBuf::from(h));
97 }
98 std::env::var("HOME")
99 .or_else(|_| std::env::var("USERPROFILE"))
100 .ok()
101 .map(|base| PathBuf::from(base).join(".vanta"))
102}