class BundleUpdateInteractive::CLI
def generate_report(options)
def generate_report(options) whisper "Resolving latest gem versions..." updater_class = options.latest? ? Latest::Updater : Updater updater = updater_class.new(groups: options.exclusively, only_explicit: options.only_explicit?) report = updater.generate_report populate_vulnerabilities_and_changelogs_concurrently(report) unless report.empty? [report, updater] end
def handle_exception(error)
def handle_exception(error) case error when Errno::EPIPE # Ignore when BundleUpdateInteractive::Error, OptionParser::ParseError, Interrupt, Bundler::Dsl::DSLError $stderr.puts BundleUpdateInteractive.pastel.red(error.message) exit false else raise end end
def legend
def legend pastel = BundleUpdateInteractive.pastel <<~LEGEND Color legend: #{pastel.white.on_red('<inverse>')} Known security vulnerability #{pastel.red('<red>')} Major update; likely to have breaking changes, high risk #{pastel.yellow('<yellow>')} Minor update; changes and additions, moderate risk #{pastel.green('<green>')} Patch update; bug fixes, low risk #{pastel.cyan('<cyan>')} Possibly unreleased git commits; unknown risk LEGEND end
def populate_vulnerabilities_and_changelogs_concurrently(report)
def populate_vulnerabilities_and_changelogs_concurrently(report) pool = ThreadPool.new(max_threads: 25) whisper "Checking for security vulnerabilities..." scan_promise = pool.future(report, &:scan_for_vulnerabilities!) changelog_promises = report.all_gems.map do |_, outdated_gem| pool.future(outdated_gem, &:changelog_uri) end progress "Finding changelogs", changelog_promises, &:value! scan_promise.value! end
def progress(message, items, &block)
def progress(message, items, &block) $stderr.print(message) items.each_slice([1, items.length / 12].max) do |slice| slice.each(&block) $stderr.print(".") end $stderr.print("\n") end
def puts_gemfile_modified_notice
def puts_gemfile_modified_notice puts BundleUpdateInteractive.pastel.yellow("Your Gemfile was changed to accommodate the latest gem versions.") end
def puts_legend_and_withheld_gems(report)
def puts_legend_and_withheld_gems(report) puts puts legend puts return if report.withheld_gems.empty? puts "The following gems are being held back and cannot be updated." puts Table.withheld(report.withheld_gems).render puts end
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize options = Options.parse(argv) report, updater = generate_report(options) puts_legend_and_withheld_gems(report) unless report.empty? puts("No gems to update.").then { return } if report.updatable_gems.empty? selected_gems = MultiSelect.prompt_for_gems_to_update(report.updatable_gems) puts("No gems to update.").then { return } if selected_gems.empty? puts "Updating the following gems." puts Table.updatable(selected_gems).render puts if options.commit? GitCommitter.new(updater).apply_updates_as_individual_commits(*selected_gems.keys) else updater.apply_updates(*selected_gems.keys) end puts_gemfile_modified_notice if updater.modified_gemfile? rescue Exception => e # rubocop:disable Lint/RescueException handle_exception(e) end
def whisper(message)
def whisper(message) $stderr.puts(message) end