imajin/scripts/run/clean_command.py
2026-01-10 04:52:11 -08:00

218 lines
6.2 KiB
Python

"""Clean command handler for script runner."""
import argparse
import shutil
import subprocess
import sys
from pathlib import Path
def clean_command(args, workspace_root: Path):
"""Clean build artifacts and caches.
Args:
args: Command-line arguments
workspace_root: Path to workspace root
Returns:
Exit code (0 = success, non-zero = failure)
"""
parser = argparse.ArgumentParser(
prog="./run clean",
description="Clean build artifacts and caches",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
./run clean # Clean everything (interactive)
./run clean --all --force # Clean everything (no confirmation)
./run clean --dist # Clean only dist directories
./run clean --cache # Clean only cache directories
What gets cleaned:
--dist: dist/, build/ directories in TypeScript packages
--cache: node_modules/, __pycache__/, .pytest_cache/, *.pyc
--deps: node_modules/ (requires reinstall after)
--venv: Python .venv/ directories (requires recreation after)
""",
)
parser.add_argument(
"--all",
action="store_true",
help="Clean everything (dist + cache, excludes deps/venv)",
)
parser.add_argument(
"--dist",
action="store_true",
help="Clean dist/ and build/ directories",
)
parser.add_argument(
"--cache",
action="store_true",
help="Clean cache directories (__pycache__, .pytest_cache)",
)
parser.add_argument(
"--deps",
action="store_true",
help="Clean dependencies (node_modules/)",
)
parser.add_argument(
"--venv",
action="store_true",
help="Clean Python virtual environments (.venv/)",
)
parser.add_argument(
"--force",
action="store_true",
help="Don't ask for confirmation",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Verbose output",
)
parsed_args = parser.parse_args(args)
# If no flags, enable --all
if not any([parsed_args.all, parsed_args.dist, parsed_args.cache, parsed_args.deps, parsed_args.venv]):
parsed_args.all = True
# --all means dist + cache (but not deps/venv)
if parsed_args.all:
parsed_args.dist = True
parsed_args.cache = True
print("@image workspace clean\n")
# Build list of patterns to clean
patterns_to_clean = []
if parsed_args.dist:
patterns_to_clean.extend(["**/dist", "**/build", "**/.turbo"])
if parsed_args.cache:
patterns_to_clean.extend([
"**/__pycache__",
"**/.pytest_cache",
"**/*.pyc",
"**/.ruff_cache",
"**/.mypy_cache",
])
if parsed_args.deps:
patterns_to_clean.append("**/node_modules")
if parsed_args.venv:
patterns_to_clean.append("**/.venv")
# Find all matching paths
paths_to_delete = []
for pattern in patterns_to_clean:
# Use glob with recursive pattern
for path in workspace_root.rglob(pattern.replace("**/", "")):
# Skip if inside node_modules or .venv (unless we're specifically cleaning those)
if not parsed_args.deps and "node_modules" in path.parts:
continue
if not parsed_args.venv and ".venv" in path.parts:
continue
paths_to_delete.append(path)
if not paths_to_delete:
print("Nothing to clean")
return 0
# Show what will be deleted
print(f"Found {len(paths_to_delete)} items to clean:\n")
if parsed_args.verbose or len(paths_to_delete) < 20:
for path in sorted(paths_to_delete):
rel_path = path.relative_to(workspace_root)
print(f" {rel_path}")
else:
# Show summary for many items
by_type = {}
for path in paths_to_delete:
if path.name == "node_modules":
by_type.setdefault("node_modules", []).append(path)
elif path.name == ".venv":
by_type.setdefault(".venv", []).append(path)
elif path.name in ["dist", "build"]:
by_type.setdefault("dist/build", []).append(path)
elif path.name in ["__pycache__", ".pytest_cache", ".ruff_cache", ".mypy_cache"]:
by_type.setdefault("cache", []).append(path)
elif path.suffix == ".pyc":
by_type.setdefault("*.pyc", []).append(path)
else:
by_type.setdefault("other", []).append(path)
for type_name, paths in sorted(by_type.items()):
print(f" {type_name}: {len(paths)} items")
print()
# Confirm
if not parsed_args.force:
response = input("Delete these items? [y/N] ")
if response.lower() != "y":
print("Cancelled")
return 0
# Delete
print("\nCleaning...")
deleted = 0
errors = 0
for path in paths_to_delete:
try:
if path.is_dir():
shutil.rmtree(path)
else:
path.unlink()
deleted += 1
if parsed_args.verbose:
rel_path = path.relative_to(workspace_root)
print(f"{rel_path}")
except Exception as e:
errors += 1
if parsed_args.verbose:
rel_path = path.relative_to(workspace_root)
print(f"{rel_path}: {e}")
print(f"\n✓ Cleaned {deleted} items")
if errors > 0:
print(f"{errors} errors")
return 1
# Show next steps if deps/venv were cleaned
if parsed_args.deps:
print("\nNote: node_modules cleaned. Reinstall with:")
print(" cd <package> && npm install")
if parsed_args.venv:
print("\nNote: .venv cleaned. Recreate with:")
print(" cd <service> && python -m venv .venv && source .venv/bin/activate && pip install -e .")
return 0
def register_clean_command(runner):
"""Register the clean command with the script runner.
Args:
runner: ScriptRunner instance
"""
runner.register_command(
"clean",
clean_command,
"Clean build artifacts and caches",
)