Skip to content

Instantly share code, notes, and snippets.

@david30907d
Last active May 16, 2026 09:26
Show Gist options
  • Select an option

  • Save david30907d/d15546e89dd0a66a78db1bda2bf93328 to your computer and use it in GitHub Desktop.

Select an option

Save david30907d/d15546e89dd0a66a78db1bda2bf93328 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash

set -u

PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"

HOME_DIR="${HOME:-/Users/chouyasushi}"
MOBILE_DIR="${MOBILE_DIR:-$HOME_DIR/htdocs/david/from-fed-to-chain-mono/apps/mobile}"
ZAPENGINE_DIR="${ZAPENGINE_DIR:-$HOME_DIR/htdocs/zapPilot/zapEngine}"

MODE=""
DRY_RUN=0
YES=0
PRUNE_JS=0
PRUNE_PYTHON=0
SYSTEM_CORESIM_DYLD_CACHE="${SYSTEM_CORESIM_DYLD_CACHE:-/Library/Developer/CoreSimulator/Caches/dyld}"

usage() {
  cat <<'USAGE'
Usage:
  cleanup.sh [--dry-run]
  cleanup.sh --light [--dry-run]
  cleanup.sh --dev-cache --yes
  cleanup.sh --dev-cache --dry-run
  cleanup.sh --ios-deep --yes
  cleanup.sh --ios-deep --dry-run

Modes:
  --light       Daily safe cleanup. This is the default when no mode is passed.
  --dev-cache   Conservative dev/app cache cleanup. Deletes rebuildable caches only,
                and preserves Docker, Colima, Claude VM bundles, node_modules, and venvs.
                Requires --yes unless --dry-run is set.
  --ios-deep    Explicit iOS cleanup. Deletes Simulator devices/caches, Xcode DerivedData,
                system CoreSimulator dyld caches, and Flutter mobile build output.
                Requires --yes unless --dry-run is set.

Options:
  --dry-run      Print actions without deleting files.
  --yes          Confirm destructive cleanup for --dev-cache or --ios-deep.
  --prune-js     Optional: prune pnpm store. Not used by daily cleanup.
  --prune-python Compatibility flag; uv unreachable cache is pruned by daily cleanup.
  --help         Show this help.

Preserved by default:
  - ~/.pnpm-store
  - zapEngine node_modules
  - zapEngine apps/analytics-engine/.venv
  - Docker volumes, including local PostgreSQL volumes
  - Colima VM disks
  - Claude VM bundles
  - Xcode Archives
  - /Applications/Xcode.app
USAGE
}

log() {
  printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}

warn() {
  printf '[%s] WARN: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}

die() {
  printf '[%s] ERROR: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
  exit 1
}

run_cmd() {
  if [[ "$DRY_RUN" -eq 1 ]]; then
    printf '[dry-run]'
    printf ' %q' "$@"
    printf '\n'
    return 0
  fi

  "$@"
  local status=$?
  if [[ "$status" -ne 0 ]]; then
    warn "command failed ($status): $*"
  fi
  return 0
}

delete_path() {
  local path="$1"

  if [[ ! -e "$path" && ! -L "$path" ]]; then
    log "skip missing: $path"
    return 0
  fi

  if [[ "$DRY_RUN" -eq 1 ]]; then
    log "[dry-run] rm -rf -- $path"
    return 0
  fi

  rm -rf -- "$path"
  local status=$?
  if [[ "$status" -eq 0 ]]; then
    log "deleted: $path"
  else
    warn "failed to delete ($status): $path"
  fi
}

delete_system_coresim_dyld_cache() {
  local path="$SYSTEM_CORESIM_DYLD_CACHE"

  case "$path" in
    /Library/Developer/CoreSimulator/Caches/dyld|/System/Volumes/Data/Library/Developer/CoreSimulator/Caches/dyld)
      ;;
    *)
      die "refusing to delete non-allowlisted system CoreSimulator cache: $path"
      ;;
  esac

  if [[ ! -e "$path" && ! -L "$path" ]]; then
    log "skip missing: $path"
    return 0
  fi

  if [[ "$DRY_RUN" -eq 1 ]]; then
    log "[dry-run] rm -rf -- $path"
    return 0
  fi

  rm -rf -- "$path" 2>/dev/null
  local status=$?
  if [[ "$status" -eq 0 ]]; then
    log "deleted: $path"
    return 0
  fi

  warn "failed to delete without admin permissions ($status): $path"
  if command -v sudo >/dev/null 2>&1; then
    log "retrying with sudo; macOS may prompt for your password"
    sudo rm -rf -- "$path"
    status=$?
    if [[ "$status" -eq 0 ]]; then
      log "deleted with sudo: $path"
    else
      warn "failed to delete with sudo ($status): $path"
    fi
  else
    warn "sudo is unavailable; leaving system CoreSimulator dyld cache in place"
  fi
}

delete_old_files() {
  local dir="$1"
  local days="$2"
  local label="$3"

  if [[ ! -d "$dir" ]]; then
    log "skip missing: $dir"
    return 0
  fi

  log "cleaning $label older than ${days}d in $dir"
  if [[ "$DRY_RUN" -eq 1 ]]; then
    local count
    count="$(find "$dir" -type f -mtime +"$days" -print 2>/dev/null | wc -l | tr -d ' ')"
    log "[dry-run] would delete ${count:-0} $label file(s)"
    return 0
  fi

  find "$dir" -type f -mtime +"$days" -delete 2>/dev/null || warn "some old files could not be deleted in $dir"
}

show_size() {
  local path="$1"
  if [[ -e "$path" || -L "$path" ]]; then
    du -sh "$path" 2>/dev/null || true
  else
    printf 'missing\t%s\n' "$path"
  fi
}

show_preserved_stack() {
  log "preserving active development state by default"
  show_size "$ZAPENGINE_DIR/node_modules"
  show_size "$ZAPENGINE_DIR/apps/analytics-engine/.venv"
  show_size "$HOME_DIR/.pnpm-store"
  show_size "$HOME_DIR/.cache/uv"
  show_size "$HOME_DIR/.cache"
  show_size "$HOME_DIR/Library/Caches/Yarn"
  show_size "$HOME_DIR/Library/Containers/com.docker.docker"
  show_size "$HOME_DIR/.colima"
  show_size "$HOME_DIR/Library/Application Support/Claude/vm_bundles"
}

docker_cleanup() {
  if ! command -v docker >/dev/null 2>&1; then
    log "skip missing command: docker"
    return 0
  fi

  if ! docker info >/dev/null 2>&1; then
    log "skip Docker cleanup; Docker daemon is not accessible"
    return 0
  fi

  log "pruning Docker images, containers, networks, and build cache older than 7d"
  run_cmd docker system prune -af --filter until=168h
  run_cmd docker builder prune -af --filter until=168h
  log "Docker volumes are intentionally preserved"
}

light_cleanup() {
  log "=== Cleanup started: light ==="

  if command -v brew >/dev/null 2>&1; then
    run_cmd brew cleanup --prune=14
  else
    log "skip missing command: brew"
  fi

  if command -v npm >/dev/null 2>&1; then
    run_cmd npm cache verify
    delete_old_files "$HOME_DIR/.npm/_logs" 14 "npm logs"
  else
    log "skip missing command: npm"
  fi

  delete_old_files "$HOME_DIR/Library/Logs" 30 "user logs"
  delete_path "$HOME_DIR/Library/Caches/com.apple.dt.Xcode"
  delete_path "$HOME_DIR/Library/Caches/Yarn"
  docker_cleanup

  if [[ "$PRUNE_JS" -eq 1 ]]; then
    if command -v pnpm >/dev/null 2>&1; then
      run_cmd pnpm store prune
    else
      log "skip missing command: pnpm"
    fi
  else
    log "skip pnpm store prune; pass --prune-js to run it explicitly"
  fi

  if command -v uv >/dev/null 2>&1; then
    run_cmd uv cache prune
  else
    log "skip missing command: uv"
  fi

  show_preserved_stack
  log "=== Cleanup finished: light ==="
}

conservative_dev_cache_cleanup() {
  log "cleaning conservative rebuildable dev/app caches"

  delete_path "$HOME_DIR/Library/Caches/ms-playwright"
  delete_path "$HOME_DIR/Library/Caches/ms-playwright-go"
  delete_path "$HOME_DIR/Library/Caches/typescript"
  delete_path "$HOME_DIR/Library/Caches/pip"
  delete_path "$HOME_DIR/Library/Caches/pypoetry"
  delete_path "$HOME_DIR/Library/Caches/pnpm"
  delete_path "$HOME_DIR/Library/Caches/node-gyp"
  delete_path "$HOME_DIR/Library/Caches/Jedi"
  delete_path "$HOME_DIR/Library/Caches/hardhat-nodejs"
  delete_path "$HOME_DIR/Library/Caches/claude-cli-nodejs"
  delete_path "$HOME_DIR/Library/Caches/Google"
  delete_path "$HOME_DIR/Library/Caches/BraveSoftware"
  delete_path "$HOME_DIR/Library/Caches/net.imput.helium"
  delete_path "$HOME_DIR/Library/Caches/Trae"
  delete_path "$HOME_DIR/Library/Caches/com.openai.atlas"
  delete_path "$HOME_DIR/Library/Caches/com.spotify.client"
}

dev_cache_cleanup() {
  if [[ "$DRY_RUN" -eq 0 && "$YES" -ne 1 ]]; then
    die "--dev-cache is destructive and requires --yes, or use --dry-run first"
  fi

  log "=== Cleanup started: dev-cache ==="
  log "disk before:"
  df -h / /System/Volumes/Data 2>/dev/null || df -h /

  conservative_dev_cache_cleanup

  log "preserving Docker/Colima and Claude VM data"
  show_preserved_stack
  log "disk after:"
  df -h / /System/Volumes/Data 2>/dev/null || df -h /
  log "=== Cleanup finished: dev-cache ==="
}

ios_deep_cleanup() {
  if [[ "$DRY_RUN" -eq 0 && "$YES" -ne 1 ]]; then
    die "--ios-deep is destructive and requires --yes, or use --dry-run first"
  fi

  log "=== Cleanup started: ios-deep ==="
  log "disk before:"
  df -h / /System/Volumes/Data 2>/dev/null || df -h /

  if command -v xcrun >/dev/null 2>&1; then
    run_cmd xcrun simctl shutdown all
  else
    log "skip missing command: xcrun"
  fi

  delete_system_coresim_dyld_cache
  delete_path "$HOME_DIR/Library/Developer/CoreSimulator/Caches"
  delete_path "$HOME_DIR/Library/Developer/CoreSimulator/Devices"
  delete_path "$HOME_DIR/Library/Developer/Xcode/DerivedData"
  delete_path "$MOBILE_DIR/build"
  delete_path "$MOBILE_DIR/.dart_tool/flutter_build"

  log "preserving Xcode Archives: $HOME_DIR/Library/Developer/Xcode/Archives"
  log "preserving Docker/Colima and Claude VM data"
  log "disk after:"
  df -h / /System/Volumes/Data 2>/dev/null || df -h /
  log "=== Cleanup finished: ios-deep ==="
}

while [[ "$#" -gt 0 ]]; do
  case "$1" in
    --light)
      MODE="light"
      ;;
    --dev-cache)
      MODE="dev-cache"
      ;;
    --ios-deep)
      MODE="ios-deep"
      ;;
    --dry-run)
      DRY_RUN=1
      ;;
    --yes)
      YES=1
      ;;
    --prune-js)
      PRUNE_JS=1
      ;;
    --prune-python)
      PRUNE_PYTHON=1
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      die "unknown argument: $1"
      ;;
  esac
  shift
done

if [[ -z "$MODE" ]]; then
  MODE="light"
fi

case "$MODE" in
  light)
    light_cleanup
    ;;
  dev-cache)
    dev_cache_cleanup
    ;;
  ios-deep)
    ios_deep_cleanup
    ;;
  *)
    die "invalid mode: $MODE"
    ;;
esac
@david30907d

Copy link
Copy Markdown
Author

cronjob:

#!/bin/bash

echo "🧹 Cleaning mobile app build & local caches..."

# 專案內部快取與編譯產物
find . -type d -name "build" -exec rm -rf {} +
rm -rf ios/build android/build ios/Pods node_modules

# Flutter 清理(如有)
if command -v flutter &> /dev/null; then
  flutter clean
  flutter pub cache clean
fi

# 全域工具快取
rm -rf ~/.npm/_cacache
rm -rf ~/.gradle/caches
rm -rf ~/.cache/yarn/v6
yarn cache clean

echo "✅ Done!"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment