dev_prune/adapters/
deno.rs1use super::{
21 BloatDir, EnforcePolicy, PackageManager, dir_size_with_hardlinks, refuse_if_manifest_stale,
22 run_command_with_timeout,
23};
24use anyhow::{Result, anyhow};
25use std::fs;
26use std::path::Path;
27
28pub struct Deno;
30
31const CONFIGS: [&str; 3] = ["deno.json", "deno.jsonc", "package.json"];
33
34impl Deno {
35 fn vendors(path: &Path) -> bool {
50 ["deno.json", "deno.jsonc"].iter().any(|name| {
51 fs::read_to_string(path.join(name))
52 .ok()
53 .and_then(|config| serde_json::from_str::<serde_json::Value>(&config).ok())
54 .and_then(|config| config.get("vendor").and_then(serde_json::Value::as_bool))
55 .unwrap_or(false)
56 })
57 }
58}
59
60impl PackageManager for Deno {
61 fn name(&self) -> &'static str {
62 "deno"
63 }
64
65 fn detect(&self, path: &Path) -> bool {
66 path.join("deno.lock").exists()
67 }
68
69 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
70 let mut dirs = Vec::new();
71 let node_modules = path.join("node_modules");
75 if node_modules.is_dir() {
76 let size = dir_size_with_hardlinks(&node_modules);
77 dirs.push(BloatDir {
78 name: "node_modules".to_string(),
79 path: node_modules,
80 size_bytes: size.freed_bytes,
81 shared_bytes: size.shared_bytes,
82 });
83 }
84 let vendor = path.join("vendor");
85 if vendor.is_dir() && Self::vendors(path) {
86 let size = dir_size_with_hardlinks(&vendor);
87 dirs.push(BloatDir {
88 name: "vendor".to_string(),
89 path: vendor,
90 size_bytes: size.freed_bytes,
91 shared_bytes: size.shared_bytes,
92 });
93 }
94 dirs
95 }
96
97 fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
98 let lock = path.join("deno.lock");
99 let content = fs::read_to_string(&lock).map_err(|e| {
100 anyhow!(
101 "`deno.lock` could not be read ({e}) — without it `deno install` \
102 resolves afresh instead of restoring the versions being deleted."
103 )
104 })?;
105 let parsed: serde_json::Value = serde_json::from_str(&content).map_err(|e| {
109 anyhow!("`deno.lock` is not valid JSON ({e}) — it cannot be a complete lockfile.")
110 })?;
111 if parsed.get("version").is_none() {
112 return Err(anyhow!(
113 "`deno.lock` has no `version` field — it is not a complete Deno \
114 lockfile, so the dependency tree cannot be proven rebuildable from it."
115 ));
116 }
117 for name in CONFIGS {
122 let manifest = path.join(name);
123 if manifest.exists() {
124 refuse_if_manifest_stale(&manifest, &lock, "deno install")?;
125 }
126 }
127 Ok(())
128 }
129
130 fn restore(&self, path: &Path, timeout: std::time::Duration) -> Result<()> {
131 run_command_with_timeout("deno", &["install"], path, timeout)
132 }
133
134 fn lockfiles(&self) -> &'static [&'static str] {
135 &["deno.lock"]
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use tempfile::tempdir;
143
144 fn lockfile(dir: &Path) {
145 fs::write(dir.join("deno.lock"), r#"{"version":"5","specifiers":{}}"#).unwrap();
146 }
147
148 #[test]
149 fn detects_on_the_lockfile_and_not_the_config() {
150 let dir = tempdir().unwrap();
151 fs::write(dir.path().join("deno.json"), "{}").unwrap();
152 assert!(!Deno.detect(dir.path()));
153 lockfile(dir.path());
154 assert!(Deno.detect(dir.path()));
155 }
156
157 #[test]
158 fn claims_node_modules() {
159 let dir = tempdir().unwrap();
160 fs::create_dir(dir.path().join("node_modules")).unwrap();
161 let names: Vec<String> = Deno
162 .bloat_dirs(dir.path())
163 .into_iter()
164 .map(|b| b.name)
165 .collect();
166 assert_eq!(names, vec!["node_modules"]);
167 }
168
169 #[test]
170 fn a_vendor_directory_is_claimed_only_when_the_config_asked_for_one() {
171 let dir = tempdir().unwrap();
172 fs::create_dir(dir.path().join("vendor")).unwrap();
173 assert!(Deno.bloat_dirs(dir.path()).is_empty());
174
175 fs::write(
179 dir.path().join("deno.jsonc"),
180 "{\n // \"vendor\": true\n}\n",
181 )
182 .unwrap();
183 assert!(Deno.bloat_dirs(dir.path()).is_empty());
184
185 fs::write(dir.path().join("deno.json"), "{ \"vendor\": true }").unwrap();
186 let names: Vec<String> = Deno
187 .bloat_dirs(dir.path())
188 .into_iter()
189 .map(|b| b.name)
190 .collect();
191 assert_eq!(names, vec!["vendor"]);
192 }
193
194 #[test]
195 fn a_missing_or_malformed_lockfile_is_refused() {
196 let dir = tempdir().unwrap();
197 assert!(
198 Deno.enforce_lockfile(dir.path(), EnforcePolicy::default())
199 .is_err()
200 );
201 fs::write(dir.path().join("deno.lock"), "<<<<<<< HEAD\n").unwrap();
202 assert!(
203 Deno.enforce_lockfile(dir.path(), EnforcePolicy::default())
204 .is_err()
205 );
206 fs::write(dir.path().join("deno.lock"), r#"{"specifiers":{}}"#).unwrap();
207 assert!(
208 Deno.enforce_lockfile(dir.path(), EnforcePolicy::default())
209 .is_err()
210 );
211 lockfile(dir.path());
212 assert!(
213 Deno.enforce_lockfile(dir.path(), EnforcePolicy::default())
214 .is_ok()
215 );
216 }
217
218 #[test]
219 fn is_not_opt_in_because_none_of_it_is_compiler_output() {
220 assert!(!Deno.opt_in());
223 }
224}