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