Skip to content

Commit 27599d8

Browse files
committed
feat(cli): add package manager utility commands
1 parent 290f4ac commit 27599d8

176 files changed

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

0 commit comments

Comments
 (0)