rusty_stack/installers/components/
vllm_studio.rs1use crate::installers::common::command_exists;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ShellCommand {
20 pub program: String,
22 pub args: Vec<String>,
24 pub env: Vec<(String, String)>,
26}
27
28impl ShellCommand {
29 pub fn to_command_string(&self) -> String {
31 let env_prefix = self
32 .env
33 .iter()
34 .map(|(k, v)| format!("{k}={v}"))
35 .collect::<Vec<_>>()
36 .join(" ");
37 let cmd = if self.args.is_empty() {
38 self.program.clone()
39 } else {
40 format!("{} {}", self.program, self.args.join(" "))
41 };
42 if env_prefix.is_empty() {
43 cmd
44 } else {
45 format!("{env_prefix} {cmd}")
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct VllmStudioConfig {
53 pub install_dir: String,
55 pub python_bin: String,
57 pub dry_run: bool,
59}
60
61impl Default for VllmStudioConfig {
62 fn default() -> Self {
63 Self {
64 install_dir: default_install_dir(),
65 python_bin: "python3".to_string(),
66 dry_run: false,
67 }
68 }
69}
70
71pub const REPO_URL: &str = "https://github.com/0xSero/vllm-studio.git";
73
74pub const DEFAULT_BRANCH: &str = "main";
76
77pub fn default_install_dir() -> String {
79 format!("{}/vllm-studio", std::env::var("HOME").unwrap_or_default())
80}
81
82pub struct VllmStudioInstaller {
84 config: VllmStudioConfig,
85}
86
87impl VllmStudioInstaller {
88 pub fn new(config: VllmStudioConfig) -> Self {
90 Self { config }
91 }
92
93 pub fn with_defaults() -> Self {
95 Self::new(VllmStudioConfig::default())
96 }
97
98 pub fn build_git_clone_command(&self) -> ShellCommand {
107 ShellCommand {
108 program: "git".to_string(),
109 args: vec![
110 "clone".to_string(),
111 REPO_URL.to_string(),
112 self.config.install_dir.clone(),
113 ],
114 env: vec![],
115 }
116 }
117
118 pub fn build_git_update_commands(&self) -> Vec<ShellCommand> {
126 let dir = &self.config.install_dir;
127 vec![
128 ShellCommand {
129 program: "git".to_string(),
130 args: vec![
131 "-C".to_string(),
132 dir.clone(),
133 "remote".to_string(),
134 "set-head".to_string(),
135 "origin".to_string(),
136 "-a".to_string(),
137 ],
138 env: vec![],
139 },
140 ShellCommand {
141 program: "git".to_string(),
142 args: vec![
143 "-C".to_string(),
144 dir.clone(),
145 "checkout".to_string(),
146 DEFAULT_BRANCH.to_string(),
147 ],
148 env: vec![],
149 },
150 ShellCommand {
151 program: "git".to_string(),
152 args: vec![
153 "-C".to_string(),
154 dir.clone(),
155 "fetch".to_string(),
156 "--all".to_string(),
157 ],
158 env: vec![],
159 },
160 ShellCommand {
161 program: "git".to_string(),
162 args: vec![
163 "-C".to_string(),
164 dir.clone(),
165 "reset".to_string(),
166 "--hard".to_string(),
167 format!("origin/{DEFAULT_BRANCH}"),
168 ],
169 env: vec![],
170 },
171 ]
172 }
173
174 pub fn detect_pkg_manager(&self) -> Option<String> {
176 if command_exists("bun") {
177 Some("bun".to_string())
178 } else if command_exists("npm") {
179 Some("npm".to_string())
180 } else {
181 None
182 }
183 }
184
185 pub fn build_pkg_install_command(&self, pkg_mgr: &str) -> ShellCommand {
187 ShellCommand {
188 program: pkg_mgr.to_string(),
189 args: vec!["install".to_string()],
190 env: vec![],
191 }
192 }
193
194 pub fn build_frontend_build_command(&self, pkg_mgr: &str) -> ShellCommand {
196 ShellCommand {
197 program: pkg_mgr.to_string(),
198 args: vec!["run".to_string(), "build".to_string()],
199 env: vec![],
200 }
201 }
202
203 pub fn build_shim_content(&self, pkg_mgr: &str) -> String {
208 format!(
209 "#!/bin/bash\ncd \"{}/controller\" && {} run start\n",
210 self.config.install_dir, pkg_mgr,
211 )
212 }
213
214 pub fn install_dir(&self) -> &str {
216 &self.config.install_dir
217 }
218
219 pub fn repo_url(&self) -> &str {
221 REPO_URL
222 }
223
224 pub fn default_branch(&self) -> &str {
226 DEFAULT_BRANCH
227 }
228}
229
230#[cfg(test)]
235mod tests {
236 use super::*;
237
238 fn make_installer(dir: &str) -> VllmStudioInstaller {
239 VllmStudioInstaller::new(VllmStudioConfig {
240 install_dir: dir.to_string(),
241 python_bin: "python3".to_string(),
242 dry_run: false,
243 })
244 }
245
246 #[test]
251 fn test_git_clone_url_matches_original_script() {
252 let installer = make_installer("/home/user/vllm-studio");
253 let cmd = installer.build_git_clone_command();
254 assert_eq!(cmd.program, "git");
255 assert!(cmd.args.contains(&"clone".to_string()));
256 assert!(cmd.args.contains(&REPO_URL.to_string()));
257 assert!(cmd.args.contains(&"/home/user/vllm-studio".to_string()));
258 }
259
260 #[test]
261 fn test_git_clone_uses_correct_repo_url() {
262 assert_eq!(
263 REPO_URL, "https://github.com/0xSero/vllm-studio.git",
264 "URL must match install_vllm_studio.sh REPO_URL"
265 );
266 }
267
268 #[test]
269 fn test_git_clone_command_string() {
270 let installer = make_installer("/home/user/vllm-studio");
271 let cmd = installer.build_git_clone_command();
272 let s = cmd.to_command_string();
273 assert!(s.contains("git clone"));
274 assert!(s.contains(REPO_URL));
275 assert!(s.contains("/home/user/vllm-studio"));
276 }
277
278 #[test]
283 fn test_default_install_dir_is_home_vllm_studio() {
284 let config = VllmStudioConfig::default();
285 assert!(
286 config.install_dir.ends_with("/vllm-studio"),
287 "Default dir must end with /vllm-studio, got: {}",
288 config.install_dir,
289 );
290 }
291
292 #[test]
293 fn test_custom_install_dir() {
294 let installer = make_installer("/opt/apps/vllm-studio");
295 assert_eq!(installer.install_dir(), "/opt/apps/vllm-studio");
296 }
297
298 #[test]
299 fn test_git_update_commands_reference_correct_dir() {
300 let installer = make_installer("/home/test/vllm-studio");
301 let cmds = installer.build_git_update_commands();
302 assert_eq!(cmds.len(), 4);
303 for cmd in &cmds {
305 assert!(
306 cmd.args.contains(&"/home/test/vllm-studio".to_string()),
307 "Command {} should reference install dir",
308 cmd.to_command_string(),
309 );
310 }
311 }
312
313 #[test]
314 fn test_pkg_install_command_bun() {
315 let installer = make_installer("/home/user/vllm-studio");
316 let cmd = installer.build_pkg_install_command("bun");
317 assert_eq!(cmd.program, "bun");
318 assert_eq!(cmd.args, vec!["install"]);
319 }
320
321 #[test]
322 fn test_pkg_install_command_npm() {
323 let installer = make_installer("/home/user/vllm-studio");
324 let cmd = installer.build_pkg_install_command("npm");
325 assert_eq!(cmd.program, "npm");
326 assert_eq!(cmd.args, vec!["install"]);
327 }
328
329 #[test]
330 fn test_frontend_build_command() {
331 let installer = make_installer("/home/user/vllm-studio");
332 let cmd = installer.build_frontend_build_command("npm");
333 assert_eq!(cmd.program, "npm");
334 assert!(cmd.args.contains(&"run".to_string()));
335 assert!(cmd.args.contains(&"build".to_string()));
336 }
337
338 #[test]
339 fn test_shim_content_references_controller_dir() {
340 let installer = make_installer("/home/user/vllm-studio");
341 let shim = installer.build_shim_content("npm");
342 assert!(shim.contains("/home/user/vllm-studio/controller"));
343 assert!(shim.contains("npm run start"));
344 assert!(shim.starts_with("#!/bin/bash"));
345 }
346
347 #[test]
348 fn test_default_branch_is_main() {
349 assert_eq!(DEFAULT_BRANCH, "main");
350 }
351}