lib/bundler/patch/cli.rb in bundler-patch-1.0.0 vs lib/bundler/patch/cli.rb in bundler-patch-1.1.0.pre1

- old
+ new

@@ -1,13 +1,16 @@ require 'bundler' require 'bundler/vendor/thor/lib/thor' require 'bundler/advise' require 'slop' +require 'open3' module Bundler::Patch class CLI def self.execute + original_command = ARGV.join(' ') + opts = Slop.parse! do banner "Bundler Patch Version #{Bundler::Patch::VERSION}\nUsage: bundle patch [options] [gems-to-update]\n\nbundler-patch attempts to update gems conservatively.\n" on '-m', '--minor', 'Prefer update to the latest minor.patch version.' on '-n', '--minimal', 'Prefer minimal version updates over most recent patch (or minor if -m used).' on '-s', '--strict', 'Restrict any gem to be upgraded past most recent patch (or minor if -m used).' @@ -15,12 +18,15 @@ on '-v', '--vulnerable-gems-only', 'Only update vulnerable gems.' on '-a=', '--advisory-db-path=', 'Optional custom advisory db path. `gems` dir will be appended to this path.' on '-d=', '--ruby-advisory-db-path=', 'Optional path for ruby advisory db. `gems` dir will be appended to this path.' on '-r', '--ruby', 'Update Ruby version in related files.' on '--rubies=', 'Supported Ruby versions. Comma delimited or multiple switches.', as: Array, delimiter: ',' + on '-g=', '--gemfile=', 'Optional Gemfile to execute against. Defaults to Gemfile in current directory.' + on '--use_target_ruby', 'Optionally attempt to use Ruby version of target bundle specified in --gemfile.' on '-h', 'Show this help' on '--help', 'Show README.md' + # will be stripped in help display and normalized to hyphenated options on '--vulnerable_gems_only' on '--advisory_db_path=' on '--ruby_advisory_db_path=' on '-p', '--prefer_minimal' @@ -28,10 +34,11 @@ on '--strict_updates' end options = opts.to_hash options[:gems_to_update] = ARGV + options[:original_command] = original_command STDERR.puts options.inspect if ENV['DEBUG'] show_help(opts) if options[:h] show_readme if ARGV.include?('help') || options[:help] @@ -56,15 +63,27 @@ def patch(options={}) Bundler.ui = Bundler::UI::Shell.new normalize_options(options) - return list(options) if options[:list] + process_gemfile_option(options) - patch_ruby(options[:rubies]) if options[:ruby] + if options[:use_target_ruby] # TODO: && different_ruby_found + tb = options[:target] + ruby = tb.ruby_bin_exe + tb.install_bundler_patch_in_target + bundler_patch = File.join(tb.ruby_bin, 'bundler-patch') # uses 'latest' bundler-patch, which can work after we've installed ours. + full_command = "#{ruby} #{bundler_patch} #{options[:original_command].gsub(/use_target_ruby/, '')}" + result = shell_command(full_command) + puts result[:stdout] unless ENV['BP_DEBUG'] + else + return list(options) if options[:list] - patch_gems(options) + patch_ruby(options) if options[:ruby] + + patch_gems(options) + end end def normalize_options(options) map = {:prefer_minimal => :minimal, :strict_updates => :strict, :minor_preferred => :minor} {}.tap do |target| @@ -76,10 +95,23 @@ end end private + def process_gemfile_option(options) + # copy/pasta from Bundler + custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] + if custom_gemfile && !custom_gemfile.empty? + ENV['BUNDLE_GEMFILE'] = File.expand_path(custom_gemfile) + dir, gemfile = [File.dirname(custom_gemfile), File.basename(custom_gemfile)] + target_bundle = TargetBundle.new(dir: dir, gemfile: gemfile) + options[:target] = target_bundle + else + options[:target] = TargetBundle.new + end + end + def list(options) gem_patches = AdvisoryConsolidator.new(options).vulnerable_gems if gem_patches.empty? Bundler.ui.info @no_vulns_message @@ -89,12 +121,13 @@ Bundler.ui.info '-------------------------' Bundler.ui.info gem_patches.map(&:to_s).uniq.sort.join("\n") end end - def patch_ruby(supported) - RubyVersion.new(patched_versions: supported).update + def patch_ruby(options) + supported = options[:rubies] + RubyVersion.new(target_bundle: options[:target], patched_versions: supported).update end def patch_gems(options) vulnerable_patches = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch requested_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) } @@ -136,13 +169,29 @@ prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep } # update => true is very important, otherwise without any Gemfile changes, the installer # may end up concluding everything can be resolved locally, nothing is changing, # and then nothing is done. lib/bundler/cli/update.rb also hard-codes this. - Bundler::Installer.install(Bundler.root, prep.bundler_def, {'update' => true}) + Bundler::Installer.install(options[:target].dir, prep.bundler_def, {'update' => true}) Bundler.load.cache if Bundler.app_cache.exist? end end +end + +def shell_command(command) + stdout, stderr, status = Open3.capture3(command) + if ENV['BP_DEBUG'] + puts "-command: #{command}" + puts "--stdout:#{indent(stdout)}" + puts "--stderr:#{indent(stderr)}" + end + {stdout: stdout, + stderr: stderr, + status: status} +end + +def indent(s) + s.split("\n").map { |ln| " #{ln}" }.join("\n") end if __FILE__ == $0 Bundler::Patch::CLI.execute end