diskforge_core/rules/
dev_artifacts.rs1use std::path::Path;
2use std::sync::mpsc;
3
4use ignore::{WalkBuilder, WalkState};
5
6use crate::detector;
7use crate::sizing;
8use crate::types::{Category, CleanableItem, Risk};
9
10pub fn scan_dev_artifacts(root: &Path) -> Vec<CleanableItem> {
12 let (tx, rx) = mpsc::channel::<CleanableItem>();
13
14 let walker = WalkBuilder::new(root)
15 .follow_links(false)
16 .git_ignore(false)
17 .git_global(false)
18 .git_exclude(false)
19 .hidden(false) .build_parallel();
21
22 walker.run(|| {
23 let tx = tx.clone();
24 Box::new(move |entry_result| {
25 let entry = match entry_result {
26 Ok(e) => e,
27 Err(_) => return WalkState::Continue,
28 };
29
30 if entry.file_type().map(|t| !t.is_dir()).unwrap_or(true) {
32 return WalkState::Continue;
33 }
34
35 let dir_name = entry.file_name().to_string_lossy();
36
37 if dir_name.starts_with('.')
39 && dir_name != ".next"
40 && dir_name != ".dart_tool"
41 && dir_name != ".angular"
42 && dir_name != ".turbo"
43 && dir_name != ".expo"
44 && dir_name != ".venv"
45 && dir_name != ".gradle"
46 && dir_name != ".build"
47 {
48 return WalkState::Skip;
49 }
50
51 if is_artifact_dir(&dir_name) {
53 return WalkState::Skip;
54 }
55
56 let path = entry.path();
57
58 let Some(project_type) = detector::detect_project_type(path) else {
60 return WalkState::Continue;
61 };
62
63 let artifact_dirs = detector::artifact_dirs(&project_type);
64 let hint = detector::rebuild_hint(&project_type);
65
66 for artifact_dir_name in artifact_dirs {
67 let artifact_path = path.join(artifact_dir_name);
68 if !artifact_path.is_dir() {
69 continue;
70 }
71 let stats = sizing::dir_stats(&artifact_path);
72 if stats.size <= 1024 * 1024 {
73 continue;
74 }
75 let _ = tx.send(CleanableItem {
76 category: Category::DevArtifact(project_type.clone()),
77 path: artifact_path,
78 size: stats.size,
79 risk: Risk::Low,
80 regenerates: true,
81 regeneration_hint: Some(hint.to_string()),
82 last_modified: stats.last_modified,
83 description: format!("{artifact_dir_name} ({project_type})"),
84 cleanup_command: None,
85 });
86 }
87
88 WalkState::Skip
90 })
91 });
92
93 drop(tx);
95
96 rx.into_iter().collect()
97}
98
99fn is_artifact_dir(name: &str) -> bool {
100 matches!(
101 name,
102 "node_modules"
103 | "target"
104 | "build"
105 | "dist"
106 | "Pods"
107 | ".next"
108 | ".nuxt"
109 | ".dart_tool"
110 | ".turbo"
111 | ".expo"
112 | ".angular"
113 | ".venv"
114 | "venv"
115 | "__pycache__"
116 | "zig-cache"
117 | "zig-out"
118 | ".build"
119 | ".gradle"
120 )
121}