Skip to content

Commit e3b682f

Browse files
committed
feat(cli): add package manager utility commands
1 parent 864b698 commit e3b682f

134 files changed

Lines changed: 7962 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
use std::{collections::HashMap, process::ExitStatus};
2+
3+
use vite_command::run_command;
4+
use vite_error::Error;
5+
use vite_path::AbsolutePath;
6+
7+
use crate::package_manager::{
8+
PackageManager, PackageManagerType, ResolveCommandResult, format_path_env,
9+
};
10+
11+
/// Options for the cache command.
12+
#[derive(Debug)]
13+
pub struct CacheCommandOptions<'a> {
14+
pub subcommand: &'a str,
15+
pub pass_through_args: Option<&'a [String]>,
16+
}
17+
18+
impl PackageManager {
19+
/// Run the cache command with the package manager.
20+
/// Returns ExitStatus with success (0) if the command is not supported.
21+
#[must_use]
22+
pub async fn run_cache_command(
23+
&self,
24+
options: &CacheCommandOptions<'_>,
25+
cwd: impl AsRef<AbsolutePath>,
26+
) -> Result<ExitStatus, Error> {
27+
let Some(resolve_command) = self.resolve_cache_command(options) else {
28+
// Command not supported, return success
29+
return Ok(ExitStatus::default());
30+
};
31+
run_command(&resolve_command.bin_path, &resolve_command.args, &resolve_command.envs, cwd)
32+
.await
33+
}
34+
35+
/// Resolve the cache command.
36+
/// Returns None if the command is not supported by the package manager.
37+
#[must_use]
38+
pub fn resolve_cache_command(
39+
&self,
40+
options: &CacheCommandOptions,
41+
) -> Option<ResolveCommandResult> {
42+
let bin_name: String;
43+
let envs = HashMap::from([("PATH".to_string(), format_path_env(self.get_bin_prefix()))]);
44+
let mut args: Vec<String> = Vec::new();
45+
46+
match self.client {
47+
PackageManagerType::Pnpm => {
48+
bin_name = "pnpm".into();
49+
50+
match options.subcommand {
51+
"dir" | "path" => {
52+
args.push("store".into());
53+
args.push("path".into());
54+
}
55+
"clean" => {
56+
args.push("store".into());
57+
args.push("prune".into());
58+
}
59+
_ => {
60+
println!(
61+
"Warning: pnpm cache subcommand '{}' not supported",
62+
options.subcommand
63+
);
64+
return None;
65+
}
66+
}
67+
}
68+
PackageManagerType::Npm => {
69+
bin_name = "npm".into();
70+
71+
match options.subcommand {
72+
"dir" | "path" => {
73+
// npm uses 'config get cache' to get cache directory
74+
args.push("config".into());
75+
args.push("get".into());
76+
args.push("cache".into());
77+
}
78+
"clean" => {
79+
args.push("cache".into());
80+
args.push("clean".into());
81+
}
82+
_ => {
83+
println!(
84+
"Warning: npm cache subcommand '{}' not supported",
85+
options.subcommand
86+
);
87+
return None;
88+
}
89+
}
90+
}
91+
PackageManagerType::Yarn => {
92+
bin_name = "yarn".into();
93+
let is_yarn1 = self.version.starts_with("1.");
94+
95+
match options.subcommand {
96+
"dir" | "path" => {
97+
if is_yarn1 {
98+
args.push("cache".into());
99+
args.push("dir".into());
100+
} else {
101+
args.push("config".into());
102+
args.push("get".into());
103+
args.push("cacheFolder".into());
104+
}
105+
}
106+
"clean" => {
107+
args.push("cache".into());
108+
args.push("clean".into());
109+
}
110+
_ => {
111+
println!(
112+
"Warning: yarn cache subcommand '{}' not supported",
113+
options.subcommand
114+
);
115+
return None;
116+
}
117+
}
118+
}
119+
}
120+
121+
// Add pass-through args
122+
if let Some(pass_through_args) = options.pass_through_args {
123+
args.extend_from_slice(pass_through_args);
124+
}
125+
126+
Some(ResolveCommandResult { bin_path: bin_name, args, envs })
127+
}
128+
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use tempfile::{TempDir, tempdir};
133+
use vite_path::AbsolutePathBuf;
134+
use vite_str::Str;
135+
136+
use super::*;
137+
138+
fn create_temp_dir() -> TempDir {
139+
tempdir().expect("Failed to create temp directory")
140+
}
141+
142+
fn create_mock_package_manager(pm_type: PackageManagerType, version: &str) -> PackageManager {
143+
let temp_dir = create_temp_dir();
144+
let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
145+
let install_dir = temp_dir_path.join("install");
146+
147+
PackageManager {
148+
client: pm_type,
149+
package_name: pm_type.to_string().into(),
150+
version: Str::from(version),
151+
hash: None,
152+
bin_name: pm_type.to_string().into(),
153+
workspace_root: temp_dir_path.clone(),
154+
install_dir,
155+
}
156+
}
157+
158+
#[test]
159+
fn test_pnpm_cache_dir() {
160+
let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0");
161+
let result = pm.resolve_cache_command(&CacheCommandOptions {
162+
subcommand: "dir",
163+
pass_through_args: None,
164+
});
165+
assert!(result.is_some());
166+
let result = result.unwrap();
167+
assert_eq!(result.bin_path, "pnpm");
168+
assert_eq!(result.args, vec!["store", "path"]);
169+
}
170+
171+
#[test]
172+
fn test_npm_cache_dir() {
173+
let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0");
174+
let result = pm.resolve_cache_command(&CacheCommandOptions {
175+
subcommand: "dir",
176+
pass_through_args: None,
177+
});
178+
assert!(result.is_some());
179+
let result = result.unwrap();
180+
assert_eq!(result.bin_path, "npm");
181+
assert_eq!(result.args, vec!["config", "get", "cache"]);
182+
}
183+
184+
#[test]
185+
fn test_yarn1_cache_dir() {
186+
let pm = create_mock_package_manager(PackageManagerType::Yarn, "1.22.0");
187+
let result = pm.resolve_cache_command(&CacheCommandOptions {
188+
subcommand: "dir",
189+
pass_through_args: None,
190+
});
191+
assert!(result.is_some());
192+
let result = result.unwrap();
193+
assert_eq!(result.bin_path, "yarn");
194+
assert_eq!(result.args, vec!["cache", "dir"]);
195+
}
196+
197+
#[test]
198+
fn test_yarn2_cache_dir() {
199+
let pm = create_mock_package_manager(PackageManagerType::Yarn, "4.0.0");
200+
let result = pm.resolve_cache_command(&CacheCommandOptions {
201+
subcommand: "dir",
202+
pass_through_args: None,
203+
});
204+
assert!(result.is_some());
205+
let result = result.unwrap();
206+
assert_eq!(result.bin_path, "yarn");
207+
assert_eq!(result.args, vec!["config", "get", "cacheFolder"]);
208+
}
209+
210+
#[test]
211+
fn test_pnpm_cache_clean() {
212+
let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0");
213+
let result = pm.resolve_cache_command(&CacheCommandOptions {
214+
subcommand: "clean",
215+
pass_through_args: None,
216+
});
217+
assert!(result.is_some());
218+
let result = result.unwrap();
219+
assert_eq!(result.bin_path, "pnpm");
220+
assert_eq!(result.args, vec!["store", "prune"]);
221+
}
222+
223+
#[test]
224+
fn test_npm_cache_clean() {
225+
let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0");
226+
let result = pm.resolve_cache_command(&CacheCommandOptions {
227+
subcommand: "clean",
228+
pass_through_args: None,
229+
});
230+
assert!(result.is_some());
231+
let result = result.unwrap();
232+
assert_eq!(result.bin_path, "npm");
233+
assert_eq!(result.args, vec!["cache", "clean"]);
234+
}
235+
236+
#[test]
237+
fn test_yarn1_cache_clean() {
238+
let pm = create_mock_package_manager(PackageManagerType::Yarn, "1.22.0");
239+
let result = pm.resolve_cache_command(&CacheCommandOptions {
240+
subcommand: "clean",
241+
pass_through_args: None,
242+
});
243+
assert!(result.is_some());
244+
let result = result.unwrap();
245+
assert_eq!(result.bin_path, "yarn");
246+
assert_eq!(result.args, vec!["cache", "clean"]);
247+
}
248+
249+
#[test]
250+
fn test_yarn2_cache_clean() {
251+
let pm = create_mock_package_manager(PackageManagerType::Yarn, "4.0.0");
252+
let result = pm.resolve_cache_command(&CacheCommandOptions {
253+
subcommand: "clean",
254+
pass_through_args: None,
255+
});
256+
assert!(result.is_some());
257+
let result = result.unwrap();
258+
assert_eq!(result.bin_path, "yarn");
259+
assert_eq!(result.args, vec!["cache", "clean"]);
260+
}
261+
}

0 commit comments

Comments
 (0)