1#![forbid(unsafe_code)]
9
10use std::path::{Path, PathBuf};
11use std::process::{Command, ExitCode};
12use vanta_core::{Platform, StoreKey};
13use vanta_lock::Lock;
14use vanta_state::State;
15
16enum LockResolution {
18 Bin(PathBuf),
20 PinnedButMissing(String),
22 NotManaged,
24}
25
26fn basename(p: &str) -> &str {
27 p.rsplit(['/', '\\']).next().unwrap_or(p)
28}
29
30fn resolve_from_lock(home: &Path, name: &str) -> LockResolution {
34 let plat = Platform::current().token();
35 let mut dir = std::env::current_dir().ok();
36 while let Some(d) = dir {
37 let lock_path = d.join("vanta.lock");
38 if lock_path.is_file() {
39 if let Ok(lock) = Lock::load_file(&lock_path) {
40 for tool in &lock.tools {
41 let pin = tool.platform.get(&plat);
42 let manages = tool.name == name
43 || pin.is_some_and(|p| p.bin.iter().any(|b| basename(b) == name));
44 if !manages {
45 continue;
46 }
47 let Some(pin) = pin.filter(|p| !p.store_key.is_empty()) else {
50 return LockResolution::PinnedButMissing(tool.version.clone());
51 };
52 let Ok(key) = StoreKey::new(pin.store_key.clone()) else {
55 return LockResolution::PinnedButMissing(tool.version.clone());
56 };
57 let rel = pin
58 .bin
59 .iter()
60 .find(|b| basename(b) == name)
61 .cloned()
62 .unwrap_or_else(|| name.to_string());
63 if Path::new(&rel).components().any(|c| {
65 matches!(
66 c,
67 std::path::Component::ParentDir
68 | std::path::Component::RootDir
69 | std::path::Component::Prefix(_)
70 )
71 }) {
72 return LockResolution::NotManaged;
73 }
74 let path = home.join("store").join(key.as_str()).join(&rel);
75 if path.is_file() {
76 return LockResolution::Bin(path);
77 }
78 return LockResolution::PinnedButMissing(tool.version.clone());
79 }
80 }
81 }
82 dir = d.parent().map(Path::to_path_buf);
83 }
84 LockResolution::NotManaged
85}
86
87#[must_use]
90pub fn run() -> ExitCode {
91 let invoked = std::env::args()
92 .next()
93 .and_then(|p| {
94 Path::new(&p)
95 .file_name()
96 .map(|s| s.to_string_lossy().into_owned())
97 })
98 .unwrap_or_default();
99 let name = invoked.strip_suffix(".exe").unwrap_or(&invoked).to_string();
100 let args: Vec<String> = std::env::args().skip(1).collect();
101
102 match dispatch(&name, &args) {
103 Ok(code) => code,
104 Err(msg) => {
105 eprintln!("vanta-shim: {msg}");
106 ExitCode::from(1)
107 }
108 }
109}
110
111pub fn dispatch(name: &str, args: &[String]) -> Result<ExitCode, String> {
118 let home = home().ok_or("cannot determine VANTA_HOME")?;
119
120 match resolve_from_lock(&home, name) {
122 LockResolution::Bin(bin) => return exec(&bin, args),
123 LockResolution::PinnedButMissing(version) => {
124 return Err(format!(
125 "`{name}` is pinned to {version} here but not installed — run `vanta sync`"
126 ));
127 }
128 LockResolution::NotManaged => {} }
130
131 let state = State::open(&home.join("state.db")).map_err(|e| e.to_string())?;
132 let id = state
133 .current()
134 .map_err(|e| e.to_string())?
135 .ok_or("no active generation")?;
136 let generation = state
137 .get_generation(id)
138 .map_err(|e| e.to_string())?
139 .ok_or("active generation is missing")?;
140 let (_, key) = generation
141 .tools
142 .iter()
143 .find(|(tool, _)| tool == name)
144 .ok_or_else(|| format!("`{name}` is not managed by vanta"))?;
145 let key = StoreKey::new(key.clone()).map_err(|e| e.to_string())?;
148 let entry = home.join("store").join(key.as_str());
149 let bin = find_bin(&entry, name)
150 .ok_or_else(|| format!("executable for `{name}` not found in {}", entry.display()))?;
151 exec(&bin, args)
152}
153
154fn find_bin(entry: &Path, name: &str) -> Option<PathBuf> {
155 let candidates = [
156 entry.join("bin").join(name),
157 entry.join(name),
158 entry.join("bin").join(format!("{name}.exe")),
159 entry.join(format!("{name}.exe")),
160 ];
161 candidates.into_iter().find(|c| c.is_file())
162}
163
164#[cfg(unix)]
165fn exec(bin: &Path, args: &[String]) -> Result<ExitCode, String> {
166 use std::os::unix::process::CommandExt;
167 let err = Command::new(bin).args(args).exec();
169 Err(format!("exec {}: {err}", bin.display()))
170}
171
172#[cfg(not(unix))]
173fn exec(bin: &Path, args: &[String]) -> Result<ExitCode, String> {
174 let status = Command::new(bin)
175 .args(args)
176 .status()
177 .map_err(|e| format!("running {}: {e}", bin.display()))?;
178 Ok(ExitCode::from(status.code().unwrap_or(1) as u8))
179}
180
181fn home() -> Option<PathBuf> {
182 if let Ok(h) = std::env::var("VANTA_HOME") {
183 return Some(PathBuf::from(h));
184 }
185 std::env::var("HOME")
186 .or_else(|_| std::env::var("USERPROFILE"))
187 .ok()
188 .map(|base| PathBuf::from(base).join(".vanta"))
189}