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:
Eric Turner
2025-08-02 20:21:25 -06:00
parent f0c2137e69
commit 3ddad4b543
3 changed files with 1299 additions and 0 deletions

409
lib/update_checker.sh Executable file
View 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"
}