vx_tool_npm/
lib.rs

1//! Simple example showing how easy it is to implement a package manager
2//!
3//! This demonstrates the minimal code needed to add package manager support.
4
5use anyhow::Result;
6use std::path::Path;
7use vx_plugin::{Ecosystem, PackageSpec, VxPackageManager, VxPlugin, VxTool};
8
9/// Example: Simple NPM package manager implementation
10///
11/// This shows how little code is needed to add package manager support to vx.
12#[derive(Default)]
13pub struct SimpleNpmPackageManager;
14
15#[async_trait::async_trait]
16impl VxPackageManager for SimpleNpmPackageManager {
17    fn name(&self) -> &str {
18        "npm"
19    }
20
21    fn ecosystem(&self) -> Ecosystem {
22        Ecosystem::Node
23    }
24
25    fn description(&self) -> &str {
26        "Node Package Manager"
27    }
28
29    /// Detect if this is an npm project by looking for package.json
30    fn is_preferred_for_project(&self, project_path: &Path) -> bool {
31        project_path.join("package.json").exists()
32    }
33
34    fn get_config_files(&self) -> Vec<&str> {
35        vec!["package.json", "package-lock.json"]
36    }
37
38    /// Main method to implement - install packages
39    async fn install_packages(&self, packages: &[PackageSpec], project_path: &Path) -> Result<()> {
40        if packages.is_empty() {
41            // Just run `npm install` to install from package.json
42            self.run_command(&["install"], &[], project_path).await
43        } else {
44            // Install specific packages
45            let package_names: Vec<String> = packages
46                .iter()
47                .map(|pkg| {
48                    if let Some(version) = &pkg.version {
49                        format!("{}@{}", pkg.name, version)
50                    } else {
51                        pkg.name.clone()
52                    }
53                })
54                .collect();
55
56            let command = if packages.iter().any(|pkg| pkg.dev_dependency) {
57                vec!["install", "--save-dev"]
58            } else {
59                vec!["install", "--save"]
60            };
61
62            self.run_command(&command, &package_names, project_path)
63                .await
64        }
65    }
66
67    /// Override the default remove command
68    async fn remove_packages(&self, packages: &[String], project_path: &Path) -> Result<()> {
69        self.run_command(&["uninstall"], packages, project_path)
70            .await
71    }
72
73    /// Override the default update command
74    async fn update_packages(&self, packages: &[String], project_path: &Path) -> Result<()> {
75        if packages.is_empty() {
76            self.run_command(&["update"], &[], project_path).await
77        } else {
78            self.run_command(&["update"], packages, project_path).await
79        }
80    }
81}
82
83/// Example: Simple plugin that provides NPM package manager
84#[derive(Default)]
85pub struct SimpleNpmPlugin;
86
87#[async_trait::async_trait]
88impl VxPlugin for SimpleNpmPlugin {
89    fn name(&self) -> &str {
90        "simple-npm"
91    }
92
93    fn description(&self) -> &str {
94        "Simple NPM package manager support for vx"
95    }
96
97    fn version(&self) -> &str {
98        "1.0.0"
99    }
100
101    fn tools(&self) -> Vec<Box<dyn VxTool>> {
102        vec![]
103    }
104
105    fn package_managers(&self) -> Vec<Box<dyn VxPackageManager>> {
106        vec![Box::new(SimpleNpmPackageManager)]
107    }
108
109    fn supports_tool(&self, _tool_name: &str) -> bool {
110        false
111    }
112}
113
114/// Factory function to create the plugin
115pub fn create_simple_npm_plugin() -> Box<dyn VxPlugin> {
116    Box::new(SimpleNpmPlugin)
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_simple_npm_package_manager() {
125        let pm = SimpleNpmPackageManager;
126
127        assert_eq!(pm.name(), "npm");
128        assert_eq!(pm.ecosystem(), Ecosystem::Node);
129        assert_eq!(pm.description(), "Node Package Manager");
130
131        // Test project detection
132        let temp_dir = std::env::temp_dir().join("test-npm-project");
133        std::fs::create_dir_all(&temp_dir).unwrap();
134
135        // Should not be preferred without package.json
136        assert!(!pm.is_preferred_for_project(&temp_dir));
137
138        // Should be preferred with package.json
139        std::fs::write(temp_dir.join("package.json"), "{}").unwrap();
140        assert!(pm.is_preferred_for_project(&temp_dir));
141
142        // Cleanup
143        let _ = std::fs::remove_dir_all(&temp_dir);
144    }
145
146    #[test]
147    fn test_simple_npm_plugin() {
148        let plugin = SimpleNpmPlugin;
149
150        assert_eq!(plugin.name(), "simple-npm");
151        assert_eq!(plugin.version(), "1.0.0");
152        assert!(plugin.supports_package_manager("npm"));
153        assert!(!plugin.supports_package_manager("yarn"));
154
155        let package_managers = plugin.package_managers();
156        assert_eq!(package_managers.len(), 1);
157        assert_eq!(package_managers[0].name(), "npm");
158    }
159
160    #[tokio::test]
161    async fn test_package_spec_formatting() {
162        let _pm = SimpleNpmPackageManager;
163
164        let packages = vec![
165            PackageSpec::new("express".to_string()),
166            PackageSpec::new("lodash".to_string()).with_version("4.17.21".to_string()),
167            PackageSpec::new("jest".to_string()).as_dev_dependency(),
168        ];
169
170        // This would test the package formatting logic
171        // In a real test, we'd mock the command execution
172        assert_eq!(packages.len(), 3);
173        assert_eq!(packages[0].name, "express");
174        assert_eq!(packages[1].version, Some("4.17.21".to_string()));
175        assert!(packages[2].dev_dependency);
176    }
177}
178
179/*
180USAGE EXAMPLE:
181
182To use this package manager plugin in your vx installation:
183
184```rust
185use vx_core::PluginRegistry;
186use vx_pm_npm::create_simple_npm_plugin;
187
188let mut registry = PluginRegistry::new();
189registry.register(create_simple_npm_plugin())?;
190
191// Now you can use npm through vx:
192// vx npm install express
193// vx npm install --save-dev jest
194// vx npm update
195// vx npm uninstall lodash
196```
197
198That's it! With just ~100 lines of code, you have full NPM support in vx.
199
200The framework handles:
201- ✅ Project detection (package.json)
202- ✅ Command execution
203- ✅ Error handling
204- ✅ Plugin registration
205- ✅ Ecosystem classification
206
207You only need to implement:
208- ✅ Package installation logic
209- ✅ Project detection rules
210- ✅ Package manager metadata
211
212For even simpler cases, you can rely entirely on the default implementations
213and just specify the package manager name and config files!
214*/