feat: add core library functions for package and profile management
- Add package_manager.sh for handling system package installations - Add profile_manager.sh for managing machine-specific configurations - Add update_checker.sh for version control and updates 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
409
lib/update_checker.sh
Executable file
409
lib/update_checker.sh
Executable file
@@ -0,0 +1,409 @@
|
||||
#!/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"
|
||||
}
|
||||
Reference in New Issue
Block a user