dev_prune/adapters/
pnpm.rs1use super::{
7 BloatDir, EnforcePolicy, PackageManager, dir_size_with_hardlinks, enforce_two_tier,
8 run_command_with_timeout,
9};
10use anyhow::Result;
11use std::path::Path;
12
13pub struct Pnpm;
15
16impl PackageManager for Pnpm {
17 fn name(&self) -> &'static str {
19 "pnpm"
20 }
21
22 fn detect(&self, project_dir: &Path) -> bool {
24 project_dir.join("pnpm-lock.yaml").exists()
25 }
26
27 fn bloat_dirs(&self, project_dir: &Path) -> Vec<BloatDir> {
36 let node_modules = project_dir.join("node_modules");
37 if node_modules.exists() {
38 let size = dir_size_with_hardlinks(&node_modules);
39 vec![BloatDir {
40 name: "node_modules".to_string(),
41 path: node_modules,
42 size_bytes: size.freed_bytes,
43 shared_bytes: size.shared_bytes,
44 }]
45 } else {
46 vec![]
47 }
48 }
49
50 fn enforce_lockfile(&self, project_dir: &Path, policy: EnforcePolicy) -> Result<()> {
58 let lockfile = project_dir.join("pnpm-lock.yaml");
59 enforce_two_tier(
60 &lockfile,
61 "pnpm",
62 &["install", "--lockfile-only", "--frozen-lockfile"],
63 &["install", "--lockfile-only"],
64 project_dir,
65 policy,
66 )
67 }
68
69 fn restore(&self, project_dir: &Path, timeout: std::time::Duration) -> Result<()> {
71 run_command_with_timeout(
72 "pnpm",
73 &["install", "--frozen-lockfile"],
74 project_dir,
75 timeout,
76 )
77 }
78
79 fn lockfiles(&self) -> &'static [&'static str] {
80 &["pnpm-lock.yaml"]
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use std::fs;
88 use tempfile::tempdir;
89
90 #[test]
91 fn test_name() {
92 assert_eq!(Pnpm.name(), "pnpm");
93 }
94
95 #[test]
96 fn test_detect_positive() {
97 let dir = tempdir().unwrap();
98 fs::File::create(dir.path().join("pnpm-lock.yaml")).unwrap();
99 assert!(Pnpm.detect(dir.path()));
100 }
101
102 #[test]
103 fn test_detect_negative() {
104 let dir = tempdir().unwrap();
105 assert!(!Pnpm.detect(dir.path()));
106 }
107
108 #[test]
109 fn test_bloat_dirs_present() {
110 let dir = tempdir().unwrap();
111 fs::create_dir(dir.path().join("node_modules")).unwrap();
112 let bloat = Pnpm.bloat_dirs(dir.path());
113 assert_eq!(bloat.len(), 1);
114 assert_eq!(bloat[0].path, dir.path().join("node_modules"));
115 }
116
117 #[test]
118 fn test_bloat_dirs_absent() {
119 let dir = tempdir().unwrap();
120 let bloat = Pnpm.bloat_dirs(dir.path());
121 assert!(bloat.is_empty());
122 }
123
124 #[test]
125 fn test_bloat_dirs_excludes_store_hardlinks() {
126 let dir = tempdir().unwrap();
130 let store = dir.path().join("store");
131 let node_modules = dir.path().join("node_modules");
132 fs::create_dir(&store).unwrap();
133 fs::create_dir(&node_modules).unwrap();
134 fs::write(store.join("pkg.js"), "0123456789").unwrap();
135 fs::hard_link(store.join("pkg.js"), node_modules.join("pkg.js")).unwrap();
136 fs::write(node_modules.join(".modules.yaml"), "y").unwrap();
137 let bloat = Pnpm.bloat_dirs(dir.path());
138 assert_eq!(bloat.len(), 1);
139 assert_eq!(bloat[0].size_bytes, 1);
140 assert_eq!(bloat[0].shared_bytes, 10);
141 }
142}