1use std::path::Path;
6use vx_core::{Ecosystem, PackageSpec, Result, VxPackageManager, VxPlugin, VxTool};
7
8#[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 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 async fn install_packages(&self, packages: &[PackageSpec], project_path: &Path) -> Result<()> {
39 if packages.is_empty() {
40 self.run_command(&["install"], &[], project_path).await
42 } else {
43 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 async fn remove_packages(&self, packages: &[String], project_path: &Path) -> Result<()> {
68 self.run_command(&["uninstall"], packages, project_path)
69 .await
70 }
71
72 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#[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
113pub 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 let temp_dir = std::env::temp_dir().join("test-npm-project");
132 std::fs::create_dir_all(&temp_dir).unwrap();
133
134 assert!(!pm.is_preferred_for_project(&temp_dir));
136
137 std::fs::write(temp_dir.join("package.json"), "{}").unwrap();
139 assert!(pm.is_preferred_for_project(&temp_dir));
140
141 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 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