vx_cli/commands/
switch.rs1use crate::ui::UI;
4use vx_core::{PluginRegistry, Result, VxEnvironment, VxError, VxShimManager};
5
6pub async fn handle(_registry: &PluginRegistry, tool_version: &str, global: bool) -> Result<()> {
7 let (tool_name, version) = parse_tool_version(tool_version)?;
9
10 UI::info(&format!("Switching {} to version {}", tool_name, version));
11
12 let environment = VxEnvironment::new()?;
14 let shim_manager = VxShimManager::new(environment.clone())?;
15
16 if !environment.is_version_installed(&tool_name, &version) {
18 UI::error(&format!(
19 "Version {} of {} is not installed. Install it first with: vx install {}@{}",
20 version, tool_name, tool_name, version
21 ));
22 return Err(VxError::VersionNotInstalled {
23 tool_name: tool_name.clone(),
24 version: version.clone(),
25 });
26 }
27
28 let installation = environment
30 .get_installation_info(&tool_name, &version)?
31 .ok_or_else(|| VxError::VersionNotInstalled {
32 tool_name: tool_name.clone(),
33 version: version.clone(),
34 })?;
35
36 shim_manager.switch_tool_version(&tool_name, &version, &installation.executable_path)?;
38
39 if global {
40 environment.set_active_version(&tool_name, &version)?;
42 UI::success(&format!(
43 "Switched {} to version {} globally",
44 tool_name, version
45 ));
46 UI::hint(&format!(
47 "All new terminal sessions will use {}@{}",
48 tool_name, version
49 ));
50 } else {
51 UI::success(&format!(
53 "Switched {} to version {} in current session",
54 tool_name, version
55 ));
56 UI::hint(&format!(
57 "Use 'vx switch {}@{} --global' to make this the default for all sessions",
58 tool_name, version
59 ));
60 }
61
62 let shim_dir = environment.shim_dir()?;
64 let shim_path = shim_dir.join(if cfg!(windows) {
65 format!("{}.exe", tool_name)
66 } else {
67 tool_name.clone()
68 });
69
70 if shim_path.exists() {
71 UI::info(&format!("Shim created at: {}", shim_path.display()));
72 UI::hint(&format!(
73 "Make sure {} is in your PATH to use the switched version",
74 shim_dir.display()
75 ));
76 }
77
78 Ok(())
79}
80
81pub fn parse_tool_version(tool_version: &str) -> Result<(String, String)> {
83 if let Some((tool, version)) = tool_version.split_once('@') {
84 if tool.is_empty() || version.is_empty() {
85 return Err(VxError::ParseError {
86 message: format!("Invalid tool@version format: {}", tool_version),
87 });
88 }
89 Ok((tool.to_string(), version.to_string()))
90 } else {
91 Err(VxError::ParseError {
92 message: format!(
93 "Invalid format: {}. Expected format: tool@version (e.g., node@20.10.0)",
94 tool_version
95 ),
96 })
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_parse_tool_version() {
106 assert_eq!(
108 parse_tool_version("node@20.10.0").unwrap(),
109 ("node".to_string(), "20.10.0".to_string())
110 );
111 assert_eq!(
112 parse_tool_version("python@3.11.0").unwrap(),
113 ("python".to_string(), "3.11.0".to_string())
114 );
115
116 assert!(parse_tool_version("node").is_err());
118 assert!(parse_tool_version("@20.10.0").is_err());
119 assert!(parse_tool_version("node@").is_err());
120 assert!(parse_tool_version("").is_err());
121 }
122}