Skip to content

Instantly share code, notes, and snippets.

@timmo001
Created December 16, 2025 08:38
Show Gist options
  • Select an option

  • Save timmo001/f544d6a2aa66a7379431a9bec22df7e4 to your computer and use it in GitHub Desktop.

Select an option

Save timmo001/f544d6a2aa66a7379431a9bec22df7e4 to your computer and use it in GitHub Desktop.
Cleanup all build and dependency files
import { join } from "https://deno.land/std@0.203.0/path/mod.ts";
import {
Checkbox,
Command,
Confirm,
} from "https://deno.land/x/cliffy@v0.25.7/mod.ts";
const TARGET_DIRECTORIES = [
".cxx",
".expo",
".next",
".sst",
"build",
"dist",
"node_modules",
"out",
];
// Map to collect found directories by type
const foundDirs: Record<string, string[]> = {};
for (const dir of TARGET_DIRECTORIES) {
foundDirs[dir] = [];
}
// List of all found root target directories
const allFoundTargetDirs: string[] = [];
function isSubdirOfAnyTarget(path: string, targets: string[]): boolean {
// Normalize for comparison
const normalized = path.endsWith("/") ? path : path + "/";
return targets.some((target) => {
const t = target.endsWith("/") ? target : target + "/";
return normalized.startsWith(t);
});
}
// Helper to check if a directory is tracked by git (added/committed)
async function isGitTracked(dirPath: string): Promise<boolean> {
// Find the git root
let current = dirPath;
let last = "";
while (true) {
try {
const stat = await Deno.stat(join(current, ".git"));
if (stat.isDirectory) break;
} catch (_) {}
const parent = join(current, "..");
if (parent === current || parent === last) return false;
last = current;
current = parent;
}
// Use git ls-files to check if the directory is tracked
// We need to pass the path relative to the git root
const relPath = dirPath.substring(current.length + 1);
const p = Deno.run({
cmd: ["git", "ls-files", "--error-unmatch", relPath],
cwd: current,
stdout: "null",
stderr: "null",
});
const status = await p.status();
p.close();
return status.success;
}
async function traverse(currentDir: string, types: string[]) {
try {
for await (const entry of Deno.readDir(currentDir)) {
const fullPath = join(currentDir, entry.name);
if (entry.isDirectory) {
// If this dir is under any already found target dir, skip it
if (isSubdirOfAnyTarget(fullPath, allFoundTargetDirs)) {
continue;
}
// Check if this directory matches any of our targets
if (types.includes(entry.name)) {
// Only add if not tracked by git
if (!(await isGitTracked(fullPath))) {
foundDirs[entry.name].push(fullPath);
allFoundTargetDirs.push(fullPath);
}
// Do not recurse into this directory
continue;
}
// Recursively traverse subdirectories
await traverse(fullPath, types);
}
}
} catch (error) {
console.error(`Error reading directory ${currentDir}:`, error);
}
}
async function main(types: string[]) {
await traverse(Deno.cwd(), types);
// Collect all selected directories for deletion
const dirsToDelete: string[] = [];
for (const dirType of types) {
const dirs = foundDirs[dirType];
if (dirs.length > 0) {
console.log(`\nFound the following '${dirType}' directories:`);
dirs.forEach((d) => console.log(` ${d}`));
const selected = await Checkbox.prompt({
message: `Select which '${dirType}' directories to delete (default: all):`,
options: dirs.map((d) => ({ name: d, value: d, checked: true })),
minOptions: 0,
});
if (selected.length > 0) {
console.log(`You chose to delete these '${dirType}' directories:`);
selected.forEach((d: string) => console.log(` ${d}`));
dirsToDelete.push(...selected);
} else {
console.log(`Skipped all '${dirType}' directories.`);
}
}
}
if (dirsToDelete.length > 0) {
console.log("\nSummary of directories to be deleted:");
dirsToDelete.forEach((d) => console.log(` ${d}`));
const confirmed = await Confirm.prompt(
"Are you sure you want to permanently delete ALL of the above directories?"
);
if (confirmed) {
for (const dir of dirsToDelete) {
try {
await Deno.remove(dir, { recursive: true });
console.log(`Deleted: ${dir}`);
} catch (err) {
console.error(`Failed to delete ${dir}:`, err);
}
}
} else {
console.log("Aborted deletion. No directories were deleted.");
}
} else {
console.log("No directories selected for deletion.");
}
}
await new Command()
.name("cleanup")
.description("Find and optionally take action on build artifact directories.")
.option(
"-t, --type <type:string>",
"Directory type to target (can be specified multiple times)",
{ collect: true }
)
.action((options) => {
const types =
options.type && options.type.length > 0
? options.type.filter((t: string) => TARGET_DIRECTORIES.includes(t))
: TARGET_DIRECTORIES;
if (types.length === 0) {
console.error("No valid directory types specified.");
Deno.exit(1);
}
return main(types);
})
.parse(Deno.args);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment