#!/bin/bash # Package Update Checker for Dotfiles # Efficiently checks for updates without running expensive update commands UPDATE_LOG="$HOME/.dotfiles/.update.log" UPDATE_CACHE="$HOME/.dotfiles/.update_cache" LAST_CHECK_FILE="$HOME/.dotfiles/.last_update_check" # Function to log update operations update_log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$UPDATE_LOG" } # Function to check if we should check for updates (avoid too frequent checks) should_check_updates() { local current_time=$(date +%s) local last_check_time=0 local force_check=${1:-false} if [[ -f "$LAST_CHECK_FILE" ]]; then last_check_time=$(cat "$LAST_CHECK_FILE") fi # If force check, always allow if [[ "$force_check" == "true" ]]; then return 0 fi # Only check if more than 6 hours (21600 seconds) have passed local time_diff=$((current_time - last_check_time)) if [[ $time_diff -lt 21600 ]]; then return 1 fi return 0 } # Function to check npm packages for updates check_npm_updates() { local packages_to_check=() local updates_available=false update_log "Checking npm packages for updates" # Get profile-aware package list if [[ -f "$HOME/.dotfiles/lib/profile_manager.sh" ]]; then source "$HOME/.dotfiles/lib/profile_manager.sh" local binary_packages=$(get_profile_packages "binary") local npm_packages=$(get_profile_packages "npm") # Add binary packages that are npm-based for package in $binary_packages; do case "$package" in "claude-code") packages_to_check+=("@anthropic-ai/claude-code") ;; "gemini-cli") packages_to_check+=("@google/gemini-cli") ;; "task-master") packages_to_check+=("task-master-ai") ;; esac done # Add npm packages for package in $npm_packages; do packages_to_check+=("$package") done else # Fallback list packages_to_check=("@anthropic-ai/claude-code" "@google/gemini-cli" "task-master-ai" "pm2") fi if [[ ${#packages_to_check[@]} -eq 0 ]]; then echo "No npm packages to check" return 1 fi echo "🔍 Checking npm packages for updates..." # Use npm outdated to check for updates (much faster than npm update) local outdated_output if command -v npm >/dev/null 2>&1; then # npm outdated returns non-zero if packages are outdated, but that's expected outdated_output=$(npm outdated -g --json 2>/dev/null || true) if [[ -n "$outdated_output" && "$outdated_output" != "{}" ]]; then echo "$outdated_output" > "$UPDATE_CACHE.npm" # Parse and display outdated packages for package in "${packages_to_check[@]}"; do if echo "$outdated_output" | grep -q "\"$package\""; then local current_version=$(echo "$outdated_output" | jq -r ".\"$package\".current // \"unknown\"" 2>/dev/null || echo "unknown") local wanted_version=$(echo "$outdated_output" | jq -r ".\"$package\".wanted // \"unknown\"" 2>/dev/null || echo "unknown") local latest_version=$(echo "$outdated_output" | jq -r ".\"$package\".latest // \"unknown\"" 2>/dev/null || echo "unknown") echo " đŸ“Ļ $package: $current_version → $latest_version" updates_available=true update_log "Update available for $package: $current_version → $latest_version" fi done else echo " ✅ All npm packages are up to date" update_log "All npm packages are up to date" fi else echo " âš ī¸ npm not available" update_log "npm not available for update check" fi if [[ "$updates_available" == "true" ]]; then return 0 else return 1 fi } # Function to check GitHub packages for updates check_github_updates() { local updates_available=false update_log "Checking GitHub packages for updates" # Get profile-aware GitHub packages local github_packages if [[ -f "$HOME/.dotfiles/lib/profile_manager.sh" ]]; then source "$HOME/.dotfiles/lib/profile_manager.sh" github_packages=$(get_profile_packages "github") else github_packages="fzf bat ripgrep fd delta" fi if [[ -z "$github_packages" ]]; then echo "No GitHub packages to check" return 1 fi echo "🐙 Checking GitHub packages for updates..." for package in $github_packages; do local repo="" local current_version="" local cmd_name="$package" case "$package" in "fzf") repo="junegunn/fzf" current_version=$(fzf --version 2>/dev/null | cut -d' ' -f1 || echo "unknown") ;; "bat") repo="sharkdp/bat" current_version=$(bat --version 2>/dev/null | grep -o 'bat [0-9.]*' | cut -d' ' -f2 || echo "unknown") ;; "ripgrep") repo="BurntSushi/ripgrep" cmd_name="rg" current_version=$(rg --version 2>/dev/null | head -1 | grep -o '[0-9.]*' || echo "unknown") ;; "fd") repo="sharkdp/fd" current_version=$(fd --version 2>/dev/null | grep -o '[0-9.]*' || echo "unknown") ;; "delta") repo="dandavison/delta" current_version=$(delta --version 2>/dev/null | grep -o '[0-9.]*' || echo "unknown") ;; esac if [[ -n "$repo" ]] && command -v "$cmd_name" >/dev/null 2>&1; then # Get latest release from GitHub API local latest_version=$(curl -s "https://api.github.com/repos/$repo/releases/latest" | grep -o '"tag_name": "v*[^"]*"' | cut -d'"' -f4 | sed 's/^v//') if [[ -n "$latest_version" && "$latest_version" != "$current_version" ]]; then echo " đŸ“Ļ $package: $current_version → $latest_version" updates_available=true update_log "Update available for $package: $current_version → $latest_version" echo "$package|$current_version|$latest_version|$repo" >> "$UPDATE_CACHE.github" else echo " ✅ $package: $current_version (latest)" fi elif [[ -n "$repo" ]]; then echo " âš ī¸ $package not installed" fi done if [[ "$updates_available" == "true" ]]; then return 0 else return 1 fi } # Function to perform actual updates update_npm_packages() { echo "🔄 Updating npm packages..." if [[ -f "$UPDATE_CACHE.npm" ]]; then local outdated_output=$(cat "$UPDATE_CACHE.npm") # Get list of packages that need updating from our profile local packages_to_update=() if [[ -f "$HOME/.dotfiles/lib/profile_manager.sh" ]]; then source "$HOME/.dotfiles/lib/profile_manager.sh" local binary_packages=$(get_profile_packages "binary") local npm_packages=$(get_profile_packages "npm") for package in $binary_packages; do case "$package" in "claude-code") if echo "$outdated_output" | grep -q "@anthropic-ai/claude-code"; then packages_to_update+=("@anthropic-ai/claude-code") fi ;; "gemini-cli") if echo "$outdated_output" | grep -q "@google/gemini-cli"; then packages_to_update+=("@google/gemini-cli") fi ;; "task-master") if echo "$outdated_output" | grep -q "task-master-ai"; then packages_to_update+=("task-master-ai") fi ;; esac done for package in $npm_packages; do if echo "$outdated_output" | grep -q "\"$package\""; then packages_to_update+=("$package") fi done fi if [[ ${#packages_to_update[@]} -gt 0 ]]; then echo " Updating: ${packages_to_update[*]}" npm update -g "${packages_to_update[@]}" update_log "Updated npm packages: ${packages_to_update[*]}" else echo " No packages to update" fi # Clean up cache rm -f "$UPDATE_CACHE.npm" else echo " No update information available" fi } # Function to update GitHub packages update_github_packages() { echo "🐙 Updating GitHub packages..." if [[ -f "$UPDATE_CACHE.github" ]]; then while IFS='|' read -r package current_version latest_version repo; do echo " Updating $package: $current_version → $latest_version" # Source package manager for installation functions if [[ -f "$HOME/.dotfiles/lib/package_manager.sh" ]]; then source "$HOME/.dotfiles/lib/package_manager.sh" case "$package" in "fzf") # Reinstall fzf if install_github_package "fzf" "$repo" "clone_and_install" "$HOME/.fzf" "" "" "$HOME/.fzf/install --all"; then echo " ✅ Updated $package" update_log "Updated $package to $latest_version" else echo " ❌ Failed to update $package" fi ;; "bat"|"ripgrep"|"fd"|"delta") local cmd_name="$package" [[ "$package" == "ripgrep" ]] && cmd_name="rg" local pattern="x86_64-unknown-linux-gnu" if [[ "$OSTYPE" == "darwin"* ]]; then pattern="x86_64-apple-darwin" fi if install_github_package "$package" "$repo" "release_binary" "$HOME/.local/bin" "$cmd_name" "$pattern"; then echo " ✅ Updated $package" update_log "Updated $package to $latest_version" else echo " ❌ Failed to update $package" fi ;; esac fi done < "$UPDATE_CACHE.github" # Clean up cache rm -f "$UPDATE_CACHE.github" else echo " No update information available" fi } # Function to show update status show_update_status() { echo "🔍 Package Update Status" echo "=======================" if [[ -f "$LAST_CHECK_FILE" ]]; then local last_check=$(cat "$LAST_CHECK_FILE") local last_check_date=$(date -d "@$last_check" 2>/dev/null || echo "Unknown") echo "Last checked: $last_check_date" else echo "Last checked: Never" fi echo "" # Check for cached update information local updates_pending=false if [[ -f "$UPDATE_CACHE.npm" ]]; then echo "đŸ“Ļ npm updates pending:" local outdated_output=$(cat "$UPDATE_CACHE.npm") echo "$outdated_output" | jq -r 'to_entries[] | " â€ĸ \(.key): \(.value.current) → \(.value.latest)"' 2>/dev/null || echo " â€ĸ Updates available (run dotupdate to see details)" updates_pending=true fi if [[ -f "$UPDATE_CACHE.github" ]]; then echo "🐙 GitHub updates pending:" while IFS='|' read -r package current_version latest_version repo; do echo " â€ĸ $package: $current_version → $latest_version" done < "$UPDATE_CACHE.github" updates_pending=true fi if [[ "$updates_pending" == "false" ]]; then echo "✅ No pending updates" echo "" echo "💡 Run 'dotupdatecheck' to check for new updates" else echo "" echo "💡 Run 'dotupdate' to install pending updates" fi } # Main update check function check_for_updates() { local force_check=${1:-false} local show_output=${2:-true} if ! should_check_updates "$force_check"; then if [[ "$show_output" == "true" ]]; then echo "â„šī¸ Skipping update check (checked recently)" echo " Use 'dotupdatecheck --force' to force check" fi return 0 fi update_log "Starting update check (force: $force_check)" # Clean old cache files rm -f "$UPDATE_CACHE.npm" "$UPDATE_CACHE.github" local npm_updates=false local github_updates=false if check_npm_updates; then npm_updates=true fi echo "" if check_github_updates; then github_updates=true fi # Update last check time echo "$(date +%s)" > "$LAST_CHECK_FILE" if [[ "$npm_updates" == "true" || "$github_updates" == "true" ]]; then echo "" echo "💡 Updates available! Run 'dotupdate' to install them." update_log "Updates found and cached" return 0 else echo "" echo "✅ All packages are up to date!" update_log "No updates available" return 1 fi } # Main update function update_packages() { echo "🔄 Updating packages..." update_log "Starting package updates" local updated_something=false if [[ -f "$UPDATE_CACHE.npm" ]]; then update_npm_packages updated_something=true echo "" fi if [[ -f "$UPDATE_CACHE.github" ]]; then update_github_packages updated_something=true echo "" fi if [[ "$updated_something" == "false" ]]; then echo "No cached updates found. Run 'dotupdatecheck' first." return 1 fi echo "🎉 Package updates completed!" update_log "Package updates completed" }