updatemaster (4463B)
1 #!/usr/bin/env ruby 2 # frozen_string_literal: true 3 4 def check_deps(deps) 5 deps.each do |dep| 6 unless system("command -v #{dep} >/dev/null 2>&1") 7 warn "error: command #{dep} not found" 8 exit 1 9 end 10 end 11 end 12 13 # The Homebrew package manager for macOS 14 class Homebrew 15 def initialize 16 check_deps ['brew'] 17 puts '-- HOMEBREW --' 18 2.times { system 'brew update' } 19 @outdated_casks = `brew outdated --cask --greedy --verbose`.lines.map(&:chomp) 20 @cask_names = @outdated_casks.map { |c| c.split.first } 21 @outdated_formulae = `brew outdated --formula` 22 end 23 24 def start_sudo_cache 25 puts 'Please enter the administrator password to install cask upgrades.' 26 system 'sudo -v' 27 @sudo_cache_pid = spawn 'while :; do sudo -n true; sleep 60; done 2>/dev/null' 28 end 29 30 def prompt_user 31 return if @outdated_casks.empty? 32 33 puts "\nOutdated casks:" 34 @outdated_casks.each_with_index { |c, i| puts "#{i} #{c}" } 35 print 'Numbers of casks to upgrade (space-separated): ' 36 begin 37 @to_upgrade = $stdin.gets.chomp.split.map { |casknum| @cask_names[Integer(casknum)] } 38 rescue ArgumentError 39 warn 'Only numbers allowed' 40 exit 1 41 end 42 start_sudo_cache unless @to_upgrade.empty? 43 end 44 45 def do_upgrade 46 if @outdated_formulae.empty? 47 puts "\nNo formulae to upgrade." 48 else 49 puts "\nUpgrading all formulae.\nTo upgrade:" 50 puts @outdated_formulae 51 system 'brew upgrade --formula' 52 end 53 54 return if @outdated_casks.empty? 55 56 if @to_upgrade.empty? 57 puts 'Not upgrading any casks.' 58 else 59 puts "\nUpgrading casks:" 60 puts @to_upgrade 61 62 @to_upgrade.each { |cask| system "brew upgrade --cask --greedy --verbose #{cask}" } 63 puts 'Upgrade complete.' 64 end 65 end 66 67 def cleanup 68 Process.kill('TERM', @sudo_cache_pid) if @sudo_cache_pid 69 system 'brew cleanup -s --prune-prefix' 70 system 'brew cleanup' 71 system 'brew doctor' 72 system 'brew missing' 73 74 puts "Homebrew cache disk usage: #{`du -skh "$(brew --cache)" | cut -f1`}" 75 puts "Remove with:\trm -rf $(brew --cache)" 76 system 'brew bundle dump --force --file=~/.dotfiles/Brewfile;' 77 end 78 end 79 80 # Pipx for Python 81 class Pipx 82 def initialize 83 check_deps ['pipx'] 84 puts '-- PIPX --' 85 end 86 87 def do_upgrade 88 system 'pipx upgrade-all' 89 end 90 end 91 92 # Gems for Ruby 93 class RubyGems 94 def initialize 95 check_deps ['gem'] 96 puts '-- GEM --' 97 puts 'To upgrade:' 98 system 'gem outdated' 99 end 100 101 def do_upgrade 102 system 'gem update' 103 end 104 105 def cleanup 106 system 'gem cleanup' 107 end 108 end 109 110 # NPM for JS 111 class NPM 112 def initialize 113 check_deps ['npm'] 114 puts '-- NPM --' 115 puts 'To upgrade:' 116 system 'npm -g outdated' 117 end 118 119 def do_upgrade 120 outdated = `npm outdated -g --parseable | sed 's!:.*!!g; s!.*/!!'`.split 121 outdated.each { |pkg| system "npm -g install #{pkg}@latest" } 122 system 'npm -g upgrade' 123 end 124 end 125 126 def do_prompts(systems) 127 system_instances = systems.map(&:new) 128 system_instances.each(&:prompt_user) if (['-n', '--dry-run'] & ARGV).empty? 129 system_instances 130 end 131 132 def split_systems_by_prompting(all_systems) 133 systems_with_prompt = all_systems.select { |s| s.method_defined? 'prompt_user' } 134 { prompting: systems_with_prompt, 135 no_prompting: all_systems - systems_with_prompt } 136 end 137 138 def upgrade_cleanup(system_instance) 139 system_instance.do_upgrade if (['-n', '--dry-run'] & ARGV).empty? 140 system_instance.cleanup if system_instance.class.method_defined? 'cleanup' 141 end 142 143 def main 144 package_systems = [Homebrew, Pipx, RubyGems, NPM] 145 146 split_systems = split_systems_by_prompting package_systems 147 148 # Do any prompts to get interactivity out of the way, then upgrade those first 149 do_prompts(split_systems[:prompting]).each { |s| upgrade_cleanup s } unless split_systems[:prompting].empty? 150 151 # Finish up by upgrading systems that don't need user input 152 split_systems[:no_prompting].each do |nonprompting_sys| 153 pksys = nonprompting_sys.new 154 upgrade_cleanup pksys 155 end 156 end 157 158 def handle_help 159 return if (['-h', '--help'] & ARGV).empty? 160 161 puts <<~END_HELPTEXT 162 Usage: updatemaster [options] 163 164 Options: 165 -h, --help display this helptext 166 -n, --dry-run don't upgrade anything, only update & cleanup 167 END_HELPTEXT 168 exit 0 169 end 170 171 handle_help 172 begin 173 caffeinate = spawn 'caffeinate -d' 174 main 175 rescue SystemExit, Interrupt 176 puts "\nInterrupted, exiting..." 177 end 178 Process.kill('SIGTERM', caffeinate) 179 Process.detach(caffeinate)