vx_tool_node/
tool.rs

1//! Node.js tool implementations - JavaScript runtime and package management tools
2
3use crate::config::NodeUrlBuilder;
4use anyhow::Result;
5use std::collections::HashMap;
6use vx_plugin::{ToolContext, ToolExecutionResult, VersionInfo, VxTool};
7use vx_tool_standard::StandardUrlBuilder;
8use vx_version::{NodeVersionFetcher, VersionFetcher};
9// use vx_core::{UrlBuilder, VersionParser};
10
11/// Macro to generate Node.js tool implementations using VxTool trait
12macro_rules! node_vx_tool {
13    ($name:ident, $cmd:literal, $desc:literal, $homepage:expr) => {
14        #[derive(Debug, Clone)]
15        pub struct $name {
16            version_fetcher: NodeVersionFetcher,
17        }
18
19        impl $name {
20            pub fn new() -> Self {
21                Self {
22                    version_fetcher: NodeVersionFetcher::new(),
23                }
24            }
25        }
26
27        #[async_trait::async_trait]
28        impl VxTool for $name {
29            fn name(&self) -> &str {
30                $cmd
31            }
32
33            fn description(&self) -> &str {
34                $desc
35            }
36
37            fn aliases(&self) -> Vec<&str> {
38                match $cmd {
39                    "node" => vec!["nodejs"],
40                    "npm" => vec![],
41                    "npx" => vec![],
42                    _ => vec![],
43                }
44            }
45
46            async fn fetch_versions(&self, include_prerelease: bool) -> Result<Vec<VersionInfo>> {
47                // For Node.js, fetch from official API
48                self.version_fetcher
49                    .fetch_versions(include_prerelease)
50                    .await
51                    .map_err(|e| anyhow::anyhow!("Failed to fetch versions: {}", e))
52            }
53
54            async fn install_version(&self, version: &str, force: bool) -> Result<()> {
55                if !force && self.is_version_installed(version).await? {
56                    return Err(anyhow::anyhow!(
57                        "Version {} of {} is already installed",
58                        version,
59                        self.name()
60                    ));
61                }
62
63                let install_dir = self.get_version_install_dir(version);
64                let _exe_path = self.default_install_workflow(version, &install_dir).await?;
65
66                // Verify installation
67                if !self.is_version_installed(version).await? {
68                    return Err(anyhow::anyhow!(
69                        "Installation verification failed for {} version {}",
70                        self.name(),
71                        version
72                    ));
73                }
74
75                Ok(())
76            }
77
78            async fn is_version_installed(&self, version: &str) -> Result<bool> {
79                // Simple implementation - check if version directory exists
80                let install_dir = self.get_version_install_dir(version);
81                Ok(install_dir.exists())
82            }
83
84            async fn execute(
85                &self,
86                args: &[String],
87                context: &ToolContext,
88            ) -> Result<ToolExecutionResult> {
89                // Simple implementation - execute the tool directly
90                let mut cmd = std::process::Command::new($cmd);
91                cmd.args(args);
92
93                if let Some(cwd) = &context.working_directory {
94                    cmd.current_dir(cwd);
95                }
96
97                for (key, value) in &context.environment_variables {
98                    cmd.env(key, value);
99                }
100
101                let status = cmd
102                    .status()
103                    .map_err(|e| anyhow::anyhow!("Failed to execute {}: {}", $cmd, e))?;
104
105                Ok(ToolExecutionResult {
106                    exit_code: status.code().unwrap_or(1),
107                    stdout: None,
108                    stderr: None,
109                })
110            }
111
112            async fn get_active_version(&self) -> Result<String> {
113                // Simple implementation - return a default version
114                Ok("latest".to_string())
115            }
116
117            async fn get_installed_versions(&self) -> Result<Vec<String>> {
118                // Simple implementation - return empty list
119                Ok(vec![])
120            }
121
122            async fn get_download_url(&self, version: &str) -> Result<Option<String>> {
123                Ok(NodeUrlBuilder::download_url(version))
124            }
125
126            fn metadata(&self) -> HashMap<String, String> {
127                let mut meta = HashMap::new();
128                meta.insert("homepage".to_string(), $homepage.unwrap_or("").to_string());
129                meta.insert("ecosystem".to_string(), "javascript".to_string());
130                meta
131            }
132        }
133
134        impl Default for $name {
135            fn default() -> Self {
136                Self::new()
137            }
138        }
139    };
140}
141
142// Define Node.js tools using the VxTool macro
143node_vx_tool!(
144    NodeTool,
145    "node",
146    "Node.js JavaScript runtime",
147    Some("https://nodejs.org/")
148);
149node_vx_tool!(
150    NpmTool,
151    "npm",
152    "Node.js package manager",
153    Some("https://www.npmjs.com/")
154);
155node_vx_tool!(
156    NpxTool,
157    "npx",
158    "Node.js package runner",
159    Some("https://www.npmjs.com/package/npx")
160);
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_node_tool_creation() {
168        let tool = NodeTool::new();
169        assert_eq!(tool.name(), "node");
170        assert!(!tool.description().is_empty());
171        assert!(tool.aliases().contains(&"nodejs"));
172    }
173
174    #[test]
175    fn test_npm_tool_creation() {
176        let tool = NpmTool::new();
177        assert_eq!(tool.name(), "npm");
178        assert!(!tool.description().is_empty());
179    }
180
181    #[test]
182    fn test_npx_tool_creation() {
183        let tool = NpxTool::new();
184        assert_eq!(tool.name(), "npx");
185        assert!(!tool.description().is_empty());
186    }
187
188    #[test]
189    fn test_node_tool_metadata() {
190        let tool = NodeTool::new();
191        let metadata = tool.metadata();
192
193        assert!(metadata.contains_key("homepage"));
194        assert!(metadata.contains_key("ecosystem"));
195        assert_eq!(metadata.get("ecosystem"), Some(&"javascript".to_string()));
196    }
197}