Skip to content

Commit 51b0932

Browse files
ericgregorylferrazzini95
authored andcommitted
clean up .DS_Store files
Signed-off-by: Eric Gregory <eric@cosmonic.com>
1 parent a201862 commit 51b0932

File tree

5 files changed

+244
-1
lines changed

5 files changed

+244
-1
lines changed

.DS_Store

-6 KB
Binary file not shown.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ wasmtime = { workspace = true }
6464
wasmtime-wasi = { workspace = true }
6565
which = { workspace = true }
6666
wit-component = { workspace = true }
67+
walkdir.workspace = true
6768

6869
# Enable WebGPU support on non-Windows platforms
6970
# WebGPU is disabled on Windows due to dependency version conflicts with the windows crate
@@ -131,6 +132,7 @@ tracing = { version = "0.1.41", default-features = false, features = ["attribute
131132
tracing-subscriber = { version = "0.3.19", default-features = false, features = ["env-filter", "ansi", "time", "json"] }
132133
url = { version = "2.5", default-features = false }
133134
uuid = { version = "1.17.0", default-features = false }
135+
walkdir = {version = "2.5.0", default-features = false }
134136
wasm-pkg-client = { version = "0.10.0", default-features = false }
135137
wasm-pkg-core = { version = "0.10.0", default-features = false }
136138
wasm-metadata = { version = "0.239.0", default-features = false, features = ["oci"] }

crates/wash/src/cli/config.rs

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
use anyhow::Context as _;
22
use clap::Subcommand;
33
use tracing::instrument;
4+
use tracing::{error, info, warn};
5+
use walkdir::WalkDir;
46

57
use crate::{
68
cli::{CliCommand, CliContext, CommandOutput},
79
config::{generate_default_config, local_config_path},
810
};
11+
use std::io::{self};
12+
use std::path::{Path, PathBuf};
913

14+
#[instrument(skip(dir), fields(path = %dir.display()))]
15+
fn get_all_paths_in_dir(dir: &Path) -> io::Result<Vec<PathBuf>> {
16+
let paths: Vec<PathBuf> = WalkDir::new(dir)
17+
.into_iter()
18+
.filter_map(|entry| entry.ok())
19+
.filter(|entry| entry.path() != dir)
20+
.map(|entry| entry.into_path())
21+
.collect();
22+
23+
Ok(paths)
24+
}
1025
/// Create a new component project from a template, git repository, or local path
1126
#[derive(Subcommand, Debug, Clone)]
1227
pub enum ConfigCommand {
@@ -24,7 +39,27 @@ pub enum ConfigCommand {
2439
/// Print the current configuration file for wash
2540
Show {},
2641
// TODO(#27): validate config command
27-
// TODO(#29): cleanup config command, to clean the dirs we use
42+
/// Clean up wash directories and cached data
43+
#[clap(group = clap::ArgGroup::new("cleanup_targets")
44+
.required(true)
45+
.multiple(true))]
46+
Cleanup {
47+
/// Remove config directory
48+
#[clap(long, group = "cleanup_targets")]
49+
config: bool,
50+
/// Remove cache directory
51+
#[clap(long, group = "cleanup_targets")]
52+
cache: bool,
53+
/// Remove data directory
54+
#[clap(long, group = "cleanup_targets")]
55+
data: bool,
56+
/// Remove all wash directories (config + cache + data)
57+
#[clap(long, group = "cleanup_targets")]
58+
all: bool,
59+
/// Show what would be removed without actually deleting
60+
#[clap(long)]
61+
dry_run: bool,
62+
},
2863
}
2964

3065
impl CliCommand for ConfigCommand {
@@ -83,6 +118,211 @@ impl CliCommand for ConfigCommand {
83118
Some(serde_json::to_value(&config).context("failed to serialize config")?),
84119
))
85120
}
121+
ConfigCommand::Cleanup {
122+
config,
123+
cache,
124+
data,
125+
all,
126+
dry_run,
127+
} => {
128+
let config_dir = ctx.config_dir();
129+
let cache_dir = ctx.cache_dir();
130+
let data_dir = ctx.data_dir();
131+
132+
let mut cleanup_paths: Vec<PathBuf> = Vec::new();
133+
134+
if *config || *all {
135+
let config_paths: Vec<PathBuf> = get_all_paths_in_dir(config_dir.as_path())?;
136+
cleanup_paths.extend(config_paths);
137+
}
138+
139+
if *cache || *all {
140+
let cache_paths: Vec<PathBuf> = get_all_paths_in_dir(cache_dir.as_path())?;
141+
cleanup_paths.extend(cache_paths);
142+
}
143+
144+
if *data || *all {
145+
let data_paths: Vec<PathBuf> = get_all_paths_in_dir(data_dir.as_path())?;
146+
cleanup_paths.extend(data_paths);
147+
}
148+
149+
let cleanup_files: Vec<PathBuf> = cleanup_paths
150+
.iter()
151+
.filter(|p| p.is_file())
152+
.cloned()
153+
.collect();
154+
155+
let mut cleanup_dirs: Vec<PathBuf> = cleanup_paths
156+
.iter()
157+
.filter(|p| p.is_dir())
158+
.cloned()
159+
.collect();
160+
161+
// Sort to first delete the deepest directories
162+
cleanup_dirs.sort_by_key(|path| std::cmp::Reverse(path.components().count()));
163+
164+
// Gather all files as a string for output
165+
let files_summary = cleanup_paths
166+
.iter()
167+
.filter(|p| p.is_file())
168+
.map(|p| p.display().to_string())
169+
.collect::<Vec<String>>()
170+
.join("\n");
171+
172+
if cleanup_files.is_empty() {
173+
return Ok(CommandOutput::ok(
174+
"No files were found to clean up.",
175+
Some(serde_json::json!({
176+
"message": "No files were found to clean up.",
177+
"success": true,
178+
})),
179+
));
180+
}
181+
182+
if *dry_run {
183+
return Ok(CommandOutput::ok(
184+
format!(
185+
"Found {} files for cleanup (Dry Run):\n{}\n\n",
186+
cleanup_files.len(),
187+
files_summary
188+
),
189+
Some(serde_json::json!({
190+
"message": "Dry run executed successfully. No files were deleted.",
191+
"success": true,
192+
"file_count": cleanup_files.len(),
193+
"files": files_summary
194+
})),
195+
));
196+
}
197+
198+
warn!(
199+
"Found {} files for cleanup. Files to be deleted:\n{}\n\nDo you want to proceed with the deletion? (y/N)",
200+
cleanup_files.len(),
201+
files_summary
202+
);
203+
204+
let mut successful_deletions = 0;
205+
let mut failed_paths = Vec::new();
206+
207+
let mut confirmation = String::new();
208+
io::stdin().read_line(&mut confirmation)?;
209+
210+
if !confirmation.trim().eq_ignore_ascii_case("y") {
211+
return Ok(CommandOutput::ok(
212+
format!("Skipped deletion of {} files", cleanup_files.len()),
213+
Some(serde_json::json!({
214+
"message": "File deletion skipped.",
215+
"success": true,
216+
})),
217+
));
218+
}
219+
220+
for path in &cleanup_files {
221+
match std::fs::remove_file(path) {
222+
Ok(_) => {
223+
info!("Successfully deleted file: {:?}", path.display());
224+
successful_deletions += 1
225+
}
226+
Err(e) => {
227+
error!("Failed to delete {} file: {}", path.display(), e);
228+
failed_paths.push(path.clone());
229+
}
230+
}
231+
}
232+
233+
for path in &cleanup_dirs {
234+
match std::fs::remove_dir_all(path) {
235+
Ok(_) => {
236+
info!("Successfully deleted dir: {:?}", path.display());
237+
}
238+
Err(e) => {
239+
error!("Failed to delete dir {}: {}", path.display(), e);
240+
failed_paths.push(path.clone());
241+
}
242+
}
243+
}
244+
245+
if !failed_paths.is_empty() {
246+
return Ok(CommandOutput::error(
247+
format!("Failed to delete {} files", failed_paths.len()),
248+
Some(serde_json::json!({
249+
"message": format!("Partial failure: Deleted {}/{} files.",
250+
successful_deletions,
251+
cleanup_files.len()),
252+
"deleted": successful_deletions,
253+
"failed_count": failed_paths.len(),
254+
"success": false,
255+
})),
256+
));
257+
}
258+
259+
return Ok(CommandOutput::ok(
260+
format!("Successfully deleted {successful_deletions} files"),
261+
Some(serde_json::json!({
262+
"message": format!("{successful_deletions} files deleted successfully."),
263+
"deleted": successful_deletions,
264+
})),
265+
));
266+
}
86267
}
87268
}
88269
}
270+
271+
#[cfg(test)]
272+
mod tests {
273+
use super::*;
274+
use tempfile::TempDir;
275+
276+
#[test]
277+
fn test_get_all_paths_in_dir() {
278+
let temp_dir = TempDir::new().expect("failed to create temp dir");
279+
let root_path = temp_dir.path();
280+
281+
let file1_path = root_path.join("file1.txt");
282+
let subdir_path = root_path.join("subdir");
283+
let file2_path = subdir_path.join("file2.log");
284+
let emptydir_path = root_path.join("empty");
285+
286+
// Create the files and directories
287+
std::fs::write(&file1_path, "content")
288+
.expect(&format!("failed to create file {}", file1_path.display()));
289+
std::fs::create_dir(&subdir_path).expect(&format!(
290+
"failed to create directory {}",
291+
subdir_path.display()
292+
));
293+
std::fs::write(&file2_path, "more content")
294+
.expect(&format!("failed to create file {}", file2_path.display()));
295+
std::fs::create_dir(&emptydir_path).expect(&format!(
296+
"failed to create directory {}",
297+
emptydir_path.display()
298+
));
299+
300+
let mut actual_paths = get_all_paths_in_dir(root_path).expect(&format!(
301+
"failed to get files from root path {}",
302+
root_path.display()
303+
));
304+
305+
let mut expected_files = vec![
306+
file1_path.to_path_buf(),
307+
subdir_path.to_path_buf(),
308+
file2_path.to_path_buf(),
309+
emptydir_path.to_path_buf(),
310+
];
311+
312+
// Sort to ensure the order of results doesn't cause the test to fail
313+
actual_paths.sort();
314+
expected_files.sort();
315+
316+
assert_eq!(
317+
actual_paths, expected_files,
318+
"Actual files and expected files do not match."
319+
);
320+
321+
// Explicitly check the count for clarity
322+
assert_eq!(
323+
actual_paths.len(),
324+
4,
325+
"Should have found exactly two files."
326+
);
327+
}
328+
}

examples/.DS_Store

-8 KB
Binary file not shown.

0 commit comments

Comments
 (0)