Created
December 16, 2025 08:38
-
-
Save timmo001/f544d6a2aa66a7379431a9bec22df7e4 to your computer and use it in GitHub Desktop.
Cleanup all build and dependency files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { join } from "https://deno.land/[email protected]/path/mod.ts"; | |
| import { | |
| Checkbox, | |
| Command, | |
| Confirm, | |
| } from "https://deno.land/x/[email protected]/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