aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bundler/cli
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/cli')
-rw-r--r--lib/bundler/cli/add.rb26
-rw-r--r--lib/bundler/cli/binstubs.rb41
-rw-r--r--lib/bundler/cli/cache.rb35
-rw-r--r--lib/bundler/cli/check.rb40
-rw-r--r--lib/bundler/cli/clean.rb26
-rw-r--r--lib/bundler/cli/common.rb93
-rw-r--r--lib/bundler/cli/config.rb118
-rw-r--r--lib/bundler/cli/console.rb42
-rw-r--r--lib/bundler/cli/doctor.rb93
-rw-r--r--lib/bundler/cli/exec.rb104
-rw-r--r--lib/bundler/cli/gem.rb248
-rw-r--r--lib/bundler/cli/info.rb51
-rw-r--r--lib/bundler/cli/init.rb35
-rw-r--r--lib/bundler/cli/inject.rb59
-rw-r--r--lib/bundler/cli/install.rb214
-rw-r--r--lib/bundler/cli/issue.rb40
-rw-r--r--lib/bundler/cli/lock.rb64
-rw-r--r--lib/bundler/cli/open.rb26
-rw-r--r--lib/bundler/cli/outdated.rb255
-rw-r--r--lib/bundler/cli/package.rb46
-rw-r--r--lib/bundler/cli/platform.rb45
-rw-r--r--lib/bundler/cli/plugin.rb23
-rw-r--r--lib/bundler/cli/pristine.rb36
-rw-r--r--lib/bundler/cli/show.rb76
-rw-r--r--lib/bundler/cli/update.rb63
-rw-r--r--lib/bundler/cli/viz.rb30
26 files changed, 1929 insertions, 0 deletions
diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb
new file mode 100644
index 0000000000..e80c775433
--- /dev/null
+++ b/lib/bundler/cli/add.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Add
+ def initialize(options, gem_name)
+ @gem_name = gem_name
+ @options = options
+ @options[:group] = @options[:group].split(",").map(&:strip) if !@options[:group].nil? && !@options[:group].empty?
+ end
+
+ def run
+ version = @options[:version].nil? ? nil : @options[:version].split(",").map(&:strip)
+
+ unless version.nil?
+ version.each do |v|
+ raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN =~ v.to_s
+ end
+ end
+ dependency = Bundler::Dependency.new(@gem_name, version, @options)
+
+ Injector.inject([dependency], :conservative_versioning => @options[:version].nil?) # Perform conservative versioning only when version is not specified
+ Installer.install(Bundler.root, Bundler.definition)
+ end
+ end
+end
diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb
new file mode 100644
index 0000000000..95103b7dd8
--- /dev/null
+++ b/lib/bundler/cli/binstubs.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Binstubs
+ attr_reader :options, :gems
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ Bundler.definition.validate_runtime!
+ Bundler.settings[:bin] = options["path"] if options["path"]
+ Bundler.settings[:bin] = nil if options["path"] && options["path"].empty?
+ installer = Installer.new(Bundler.root, Bundler.definition)
+
+ if gems.empty?
+ Bundler.ui.error "`bundle binstubs` needs at least one gem to run."
+ exit 1
+ end
+
+ gems.each do |gem_name|
+ spec = Bundler.definition.specs.find {|s| s.name == gem_name }
+ unless spec
+ raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(
+ gem_name, Bundler.definition.specs
+ )
+ end
+
+ if spec.name == "bundler"
+ Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems."
+ elsif options[:standalone]
+ installer.generate_standalone_bundler_executable_stubs(spec)
+ else
+ installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb
new file mode 100644
index 0000000000..5ba105a31d
--- /dev/null
+++ b/lib/bundler/cli/cache.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Cache
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.definition.validate_runtime!
+ Bundler.definition.resolve_with_cache!
+ setup_cache_all
+ Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms")
+ Bundler.load.cache
+ Bundler.settings[:no_prune] = true if options["no-prune"]
+ Bundler.load.lock
+ rescue GemNotFound => e
+ Bundler.ui.error(e.message)
+ Bundler.ui.warn "Run `bundle install` to install missing gems."
+ exit 1
+ end
+
+ private
+
+ def setup_cache_all
+ Bundler.settings[:cache_all] = options[:all] if options.key?("all")
+
+ if Bundler.definition.has_local_dependencies? && !Bundler.settings[:cache_all]
+ Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
+ "to package them as well, please pass the --all flag. This will be the default " \
+ "on Bundler 2.0."
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb
new file mode 100644
index 0000000000..057a7e5695
--- /dev/null
+++ b/lib/bundler/cli/check.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Check
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ if options[:path]
+ Bundler.settings[:path] = File.expand_path(options[:path])
+ Bundler.settings[:disable_shared_gems] = true
+ end
+
+ begin
+ definition = Bundler.definition
+ definition.validate_runtime!
+ not_installed = definition.missing_specs
+ rescue GemNotFound, VersionConflict
+ Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies."
+ Bundler.ui.warn "Install missing gems with `bundle install`."
+ exit 1
+ end
+
+ if not_installed.any?
+ Bundler.ui.error "The following gems are missing"
+ not_installed.each {|s| Bundler.ui.error " * #{s.name} (#{s.version})" }
+ Bundler.ui.warn "Install missing gems with `bundle install`"
+ exit 1
+ elsif !Bundler.default_lockfile.file? && Bundler.settings[:frozen]
+ Bundler.ui.error "This bundle has been frozen, but there is no #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} present"
+ exit 1
+ else
+ Bundler.load.lock(:preserve_unknown_sections => true) unless options[:"dry-run"]
+ Bundler.ui.info "The Gemfile's dependencies are satisfied"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/clean.rb b/lib/bundler/cli/clean.rb
new file mode 100644
index 0000000000..5eba09c6bc
--- /dev/null
+++ b/lib/bundler/cli/clean.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Clean
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ require_path_or_force unless options[:"dry-run"]
+ Bundler.load.clean(options[:"dry-run"])
+ end
+
+ protected
+
+ def require_path_or_force
+ if !Bundler.settings[:path] && !options[:force]
+ Bundler.ui.error "Cleaning all the gems on your system is dangerous! " \
+ "If you're sure you want to remove every system gem not in this " \
+ "bundle, run `bundle clean --force`."
+ exit 1
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb
new file mode 100644
index 0000000000..bacbb2edc5
--- /dev/null
+++ b/lib/bundler/cli/common.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+module Bundler
+ module CLI::Common
+ def self.output_post_install_messages(messages)
+ return if Bundler.settings["ignore_messages"]
+ messages.to_a.each do |name, msg|
+ print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"]
+ end
+ end
+
+ def self.print_post_install_message(name, msg)
+ Bundler.ui.confirm "Post-install message from #{name}:"
+ Bundler.ui.info msg
+ end
+
+ def self.output_without_groups_message
+ return unless Bundler.settings.without.any?
+ Bundler.ui.confirm without_groups_message
+ end
+
+ def self.without_groups_message
+ groups = Bundler.settings.without
+ group_list = [groups[0...-1].join(", "), groups[-1..-1]].
+ reject {|s| s.to_s.empty? }.join(" and ")
+ group_str = (groups.size == 1) ? "group" : "groups"
+ "Gems in the #{group_str} #{group_list} were not installed."
+ end
+
+ def self.select_spec(name, regex_match = nil)
+ specs = []
+ regexp = Regexp.new(name) if regex_match
+
+ Bundler.definition.specs.each do |spec|
+ return spec if spec.name == name
+ specs << spec if regexp && spec.name =~ regexp
+ end
+
+ case specs.count
+ when 0
+ raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
+ when 1
+ specs.first
+ else
+ ask_for_spec_from(specs)
+ end
+ rescue RegexpError
+ raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
+ end
+
+ def self.ask_for_spec_from(specs)
+ if !$stdout.tty? && ENV["BUNDLE_SPEC_RUN"].nil?
+ raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
+ end
+
+ specs.each_with_index do |spec, index|
+ Bundler.ui.info "#{index.succ} : #{spec.name}", true
+ end
+ Bundler.ui.info "0 : - exit -", true
+
+ num = Bundler.ui.ask("> ").to_i
+ num > 0 ? specs[num - 1] : nil
+ end
+
+ def self.gem_not_found_message(missing_gem_name, alternatives)
+ require "bundler/similarity_detector"
+ message = "Could not find gem '#{missing_gem_name}'."
+ alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
+ suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name)
+ message += "\nDid you mean #{suggestions}?" if suggestions
+ message
+ end
+
+ def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems)
+ locked_names = locked_gems.specs.map(&:name)
+ names.-(locked_names).each do |g|
+ raise GemNotFound, gem_not_found_message(g, locked_names)
+ end
+ end
+
+ def self.configure_gem_version_promoter(definition, options)
+ patch_level = patch_level_options(options)
+ raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1
+ definition.gem_version_promoter.tap do |gvp|
+ gvp.level = patch_level.first || :major
+ gvp.strict = options[:strict] || options["update-strict"]
+ end
+ end
+
+ def self.patch_level_options(options)
+ [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
+ end
+ end
+end
diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb
new file mode 100644
index 0000000000..e8f13620ec
--- /dev/null
+++ b/lib/bundler/cli/config.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Config
+ attr_reader :name, :options, :scope, :thor
+ attr_accessor :args
+
+ def initialize(options, args, thor)
+ @options = options
+ @args = args
+ @thor = thor
+ @name = peek = args.shift
+ @scope = "global"
+ return unless peek && peek.start_with?("--")
+ @name = args.shift
+ @scope = peek[2..-1]
+ end
+
+ def run
+ unless name
+ confirm_all
+ return
+ end
+
+ unless valid_scope?(scope)
+ Bundler.ui.error "Invalid scope --#{scope} given. Please use --local or --global."
+ exit 1
+ end
+
+ if scope == "delete"
+ Bundler.settings.set_local(name, nil)
+ Bundler.settings.set_global(name, nil)
+ return
+ end
+
+ if args.empty?
+ if options[:parseable]
+ if value = Bundler.settings[name]
+ Bundler.ui.info("#{name}=#{value}")
+ end
+ return
+ end
+
+ confirm(name)
+ return
+ end
+
+ Bundler.ui.info(message) if message
+ Bundler.settings.send("set_#{scope}", name, new_value)
+ end
+
+ private
+
+ def confirm_all
+ if @options[:parseable]
+ thor.with_padding do
+ Bundler.settings.all.each do |setting|
+ val = Bundler.settings[setting]
+ Bundler.ui.info "#{setting}=#{val}"
+ end
+ end
+ else
+ Bundler.ui.confirm "Settings are listed in order of priority. The top value will be used.\n"
+ Bundler.settings.all.each do |setting|
+ Bundler.ui.confirm "#{setting}"
+ show_pretty_values_for(setting)
+ Bundler.ui.confirm ""
+ end
+ end
+ end
+
+ def confirm(name)
+ Bundler.ui.confirm "Settings for `#{name}` in order of priority. The top value will be used"
+ show_pretty_values_for(name)
+ end
+
+ def new_value
+ pathname = Pathname.new(args.join(" "))
+ if name.start_with?("local.") && pathname.directory?
+ pathname.expand_path.to_s
+ else
+ args.join(" ")
+ end
+ end
+
+ def message
+ locations = Bundler.settings.locations(name)
+ if @options[:parseable]
+ "#{name}=#{new_value}" if new_value
+ elsif scope == "global"
+ if locations[:local]
+ "Your application has set #{name} to #{locations[:local].inspect}. " \
+ "This will override the global value you are currently setting"
+ elsif locations[:env]
+ "You have a bundler environment variable for #{name} set to " \
+ "#{locations[:env].inspect}. This will take precedence over the global value you are setting"
+ elsif locations[:global] && locations[:global] != args.join(" ")
+ "You are replacing the current global value of #{name}, which is currently " \
+ "#{locations[:global].inspect}"
+ end
+ elsif scope == "local" && locations[:local] != args.join(" ")
+ "You are replacing the current local value of #{name}, which is currently " \
+ "#{locations[:local].inspect}"
+ end
+ end
+
+ def show_pretty_values_for(setting)
+ thor.with_padding do
+ Bundler.settings.pretty_values_for(setting).each do |line|
+ Bundler.ui.info line
+ end
+ end
+ end
+
+ def valid_scope?(scope)
+ %w(delete local global).include?(scope)
+ end
+ end
+end
diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb
new file mode 100644
index 0000000000..715abf2554
--- /dev/null
+++ b/lib/bundler/cli/console.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Console
+ attr_reader :options, :group
+ def initialize(options, group)
+ @options = options
+ @group = group
+ end
+
+ def run
+ Bundler::SharedHelpers.major_deprecation "bundle console will be replaced " \
+ "by `bin/console` generated by `bundle gem <name>`"
+
+ group ? Bundler.require(:default, *(group.split.map!(&:to_sym))) : Bundler.require
+ ARGV.clear
+
+ console = get_console(Bundler.settings[:console] || "irb")
+ console.start
+ end
+
+ def get_console(name)
+ require name
+ get_constant(name)
+ rescue LoadError
+ Bundler.ui.error "Couldn't load console #{name}, falling back to irb"
+ require "irb"
+ get_constant("irb")
+ end
+
+ def get_constant(name)
+ const_name = {
+ "pry" => :Pry,
+ "ripl" => :Ripl,
+ "irb" => :IRB,
+ }[name]
+ Object.const_get(const_name)
+ rescue NameError
+ Bundler.ui.error "Could not find constant #{const_name}"
+ exit 1
+ end
+ end
+end
diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb
new file mode 100644
index 0000000000..ae27983240
--- /dev/null
+++ b/lib/bundler/cli/doctor.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+module Bundler
+ class CLI::Doctor
+ DARWIN_REGEX = /\s+(.+) \(compatibility /
+ LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/
+
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def otool_available?
+ Bundler.which("otool")
+ end
+
+ def ldd_available?
+ Bundler.which("ldd")
+ end
+
+ def dylibs_darwin(path)
+ output = `/usr/bin/otool -L "#{path}"`.chomp
+ dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq
+ # ignore @rpath and friends
+ dylibs.reject {|dylib| dylib.start_with? "@" }
+ end
+
+ def dylibs_ldd(path)
+ output = `/usr/bin/ldd "#{path}"`.chomp
+ output.split("\n").map do |l|
+ match = l.match(LDD_REGEX)
+ next if match.nil?
+ match.captures[0]
+ end.compact
+ end
+
+ def dylibs(path)
+ case RbConfig::CONFIG["host_os"]
+ when /darwin/
+ return [] unless otool_available?
+ dylibs_darwin(path)
+ when /(linux|solaris|bsd)/
+ return [] unless ldd_available?
+ dylibs_ldd(path)
+ else # Windows, etc.
+ Bundler.ui.warn("Dynamic library check not supported on this platform.")
+ []
+ end
+ end
+
+ def bundles_for_gem(spec)
+ Dir.glob("#{spec.full_gem_path}/**/*.bundle")
+ end
+
+ def check!
+ require "bundler/cli/check"
+ Bundler::CLI::Check.new({}).run
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+ check!
+
+ definition = Bundler.definition
+ broken_links = {}
+
+ definition.specs.each do |spec|
+ bundles_for_gem(spec).each do |bundle|
+ bad_paths = dylibs(bundle).select {|f| !File.exist?(f) }
+ if bad_paths.any?
+ broken_links[spec] ||= []
+ broken_links[spec].concat(bad_paths)
+ end
+ end
+ end
+
+ if broken_links.any?
+ message = "The following gems are missing OS dependencies:"
+ broken_links.map do |spec, paths|
+ paths.uniq.map do |path|
+ "\n * #{spec.name}: #{path}"
+ end
+ end.flatten.sort.each {|m| message += m }
+ raise ProductionError, message
+ else
+ Bundler.ui.info "No issues found with the installed bundle"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb
new file mode 100644
index 0000000000..62f7bc26cb
--- /dev/null
+++ b/lib/bundler/cli/exec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+require "bundler/current_ruby"
+
+module Bundler
+ class CLI::Exec
+ attr_reader :options, :args, :cmd
+
+ RESERVED_SIGNALS = %w(SEGV BUS ILL FPE VTALRM KILL STOP).freeze
+
+ def initialize(options, args)
+ @options = options
+ @cmd = args.shift
+ @args = args
+
+ if Bundler.current_ruby.ruby_2? && !Bundler.current_ruby.jruby?
+ @args << { :close_others => !options.keep_file_descriptors? }
+ elsif options.keep_file_descriptors?
+ Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec."
+ end
+ end
+
+ def run
+ validate_cmd!
+ SharedHelpers.set_bundle_environment
+ if bin_path = Bundler.which(cmd)
+ if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path)
+ return kernel_load(bin_path, *args)
+ end
+ # First, try to exec directly to something in PATH
+ if Bundler.current_ruby.jruby_18?
+ kernel_exec(bin_path, *args)
+ else
+ kernel_exec([bin_path, cmd], *args)
+ end
+ else
+ # exec using the given command
+ kernel_exec(cmd, *args)
+ end
+ end
+
+ private
+
+ def validate_cmd!
+ return unless cmd.nil?
+ Bundler.ui.error "bundler: exec needs a command to run"
+ exit 128
+ end
+
+ def kernel_exec(*args)
+ ui = Bundler.ui
+ Bundler.ui = nil
+ Kernel.exec(*args)
+ rescue Errno::EACCES, Errno::ENOEXEC
+ Bundler.ui = ui
+ Bundler.ui.error "bundler: not executable: #{cmd}"
+ exit 126
+ rescue Errno::ENOENT
+ Bundler.ui = ui
+ Bundler.ui.error "bundler: command not found: #{cmd}"
+ Bundler.ui.warn "Install missing gem executables with `bundle install`"
+ exit 127
+ end
+
+ def kernel_load(file, *args)
+ args.pop if args.last.is_a?(Hash)
+ ARGV.replace(args)
+ $0 = file
+ Process.setproctitle(process_title(file, args)) if Process.respond_to?(:setproctitle)
+ ui = Bundler.ui
+ Bundler.ui = nil
+ require "bundler/setup"
+ signals = Signal.list.keys - RESERVED_SIGNALS
+ signals.each {|s| trap(s, "DEFAULT") }
+ Kernel.load(file)
+ rescue SystemExit
+ raise
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ Bundler.ui = ui
+ Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
+ backtrace = e.backtrace.take_while {|bt| !bt.start_with?(__FILE__) }
+ abort "#{e.class}: #{e.message}\n #{backtrace.join("\n ")}"
+ end
+
+ def process_title(file, args)
+ "#{file} #{args.join(" ")}".strip
+ end
+
+ def ruby_shebang?(file)
+ possibilities = [
+ "#!/usr/bin/env ruby\n",
+ "#!/usr/bin/env jruby\n",
+ "#!#{Gem.ruby}\n",
+ ]
+
+ if File.zero?(file)
+ Bundler.ui.warn "#{file} is empty"
+ return false
+ end
+
+ first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) }
+ possibilities.any? {|shebang| first_line.start_with?(shebang) }
+ end
+ end
+end
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb
new file mode 100644
index 0000000000..45782d71a3
--- /dev/null
+++ b/lib/bundler/cli/gem.rb
@@ -0,0 +1,248 @@
+# frozen_string_literal: true
+require "pathname"
+
+module Bundler
+ class CLI
+ Bundler.require_thor_actions
+ include Thor::Actions
+ end
+
+ class CLI::Gem
+ TEST_FRAMEWORK_VERSIONS = {
+ "rspec" => "3.0",
+ "minitest" => "5.0"
+ }.freeze
+
+ attr_reader :options, :gem_name, :thor, :name, :target
+
+ def initialize(options, gem_name, thor)
+ @options = options
+ @gem_name = resolve_name(gem_name)
+
+ @thor = thor
+ thor.behavior = :invoke
+ thor.destination_root = nil
+
+ @name = @gem_name
+ @target = SharedHelpers.pwd.join(gem_name)
+
+ validate_ext_name if options[:ext]
+ end
+
+ def run
+ Bundler.ui.confirm "Creating gem '#{name}'..."
+
+ underscored_name = name.tr("-", "_")
+ namespaced_path = name.tr("-", "/")
+ constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase }
+ constant_array = constant_name.split("::")
+
+ git_installed = Bundler.git_present?
+
+ git_author_name = git_installed ? `git config user.name`.chomp : ""
+ github_username = git_installed ? `git config github.user`.chomp : ""
+ git_user_email = git_installed ? `git config user.email`.chomp : ""
+
+ config = {
+ :name => name,
+ :underscored_name => underscored_name,
+ :namespaced_path => namespaced_path,
+ :makefile_path => "#{underscored_name}/#{underscored_name}",
+ :constant_name => constant_name,
+ :constant_array => constant_array,
+ :author => git_author_name.empty? ? "TODO: Write your name" : git_author_name,
+ :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email,
+ :test => options[:test],
+ :ext => options[:ext],
+ :exe => options[:exe],
+ :bundler_version => bundler_dependency_version,
+ :github_username => github_username.empty? ? "[USERNAME]" : github_username
+ }
+ ensure_safe_gem_name(name, constant_array)
+
+ templates = {
+ "Gemfile.tt" => "Gemfile",
+ "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
+ "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
+ "newgem.gemspec.tt" => "#{name}.gemspec",
+ "Rakefile.tt" => "Rakefile",
+ "README.md.tt" => "README.md",
+ "bin/console.tt" => "bin/console",
+ "bin/setup.tt" => "bin/setup"
+ }
+
+ executables = %w(
+ bin/console
+ bin/setup
+ )
+
+ templates.merge!("gitignore.tt" => ".gitignore") if Bundler.git_present?
+
+ if test_framework = ask_and_set_test_framework
+ config[:test] = test_framework
+ config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework]
+
+ templates.merge!(".travis.yml.tt" => ".travis.yml")
+
+ case test_framework
+ when "rspec"
+ templates.merge!(
+ "rspec.tt" => ".rspec",
+ "spec/spec_helper.rb.tt" => "spec/spec_helper.rb",
+ "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb"
+ )
+ when "minitest"
+ templates.merge!(
+ "test/test_helper.rb.tt" => "test/test_helper.rb",
+ "test/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb"
+ )
+ end
+ end
+
+ config[:test_task] = config[:test] == "minitest" ? "test" : "spec"
+
+ if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?",
+ "This means that any other developer or company will be legally allowed to use your code " \
+ "for free as long as they admit you created it. You can read more about the MIT license " \
+ "at http://choosealicense.com/licenses/mit.")
+ config[:mit] = true
+ Bundler.ui.info "MIT License enabled in config"
+ templates.merge!("LICENSE.txt.tt" => "LICENSE.txt")
+ end
+
+ if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?",
+ "Codes of conduct can increase contributions to your project by contributors who " \
+ "prefer collaborative, safe spaces. You can read more about the code of conduct at " \
+ "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \
+ "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \
+ "address is specified as a contact in the generated code of conduct so that people know " \
+ "who to contact in case of a violation. For suggestions about " \
+ "how to enforce codes of conduct, see http://bit.ly/coc-enforcement.")
+ config[:coc] = true
+ Bundler.ui.info "Code of conduct enabled in config"
+ templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md")
+ end
+
+ templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe]
+
+ if options[:ext]
+ templates.merge!(
+ "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb",
+ "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
+ "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c"
+ )
+ end
+
+ templates.each do |src, dst|
+ destination = target.join(dst)
+ SharedHelpers.filesystem_access(destination) do
+ thor.template("newgem/#{src}", destination, config)
+ end
+ end
+
+ executables.each do |file|
+ SharedHelpers.filesystem_access(target.join(file)) do |path|
+ executable = (path.stat.mode | 0o111)
+ path.chmod(executable)
+ end
+ end
+
+ if Bundler.git_present?
+ Bundler.ui.info "Initializing git repo in #{target}"
+ Dir.chdir(target) do
+ `git init`
+ `git add .`
+ end
+ end
+
+ # Open gemspec in editor
+ open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
+ rescue Errno::EEXIST => e
+ raise GenericSystemCallError.new(e, "There was a conflict while creating the new gem.")
+ end
+
+ private
+
+ def resolve_name(name)
+ SharedHelpers.pwd.join(name).basename.to_s
+ end
+
+ def ask_and_set(key, header, message)
+ choice = options[key]
+ choice = Bundler.settings["gem.#{key}"] if choice.nil?
+
+ if choice.nil?
+ Bundler.ui.confirm header
+ choice = Bundler.ui.yes? "#{message} y/(n):"
+ Bundler.settings.set_global("gem.#{key}", choice)
+ end
+
+ choice
+ end
+
+ def validate_ext_name
+ return unless gem_name.index("-")
+
+ Bundler.ui.error "You have specified a gem name which does not conform to the \n" \
+ "naming guidelines for C extensions. For more information, \n" \
+ "see the 'Extension Naming' section at the following URL:\n" \
+ "http://guides.rubygems.org/gems-with-extensions/\n"
+ exit 1
+ end
+
+ def ask_and_set_test_framework
+ test_framework = options[:test] || Bundler.settings["gem.test"]
+
+ if test_framework.nil?
+ Bundler.ui.confirm "Do you want to generate tests with your gem?"
+ result = Bundler.ui.ask "Type 'rspec' or 'minitest' to generate those test files now and " \
+ "in the future. rspec/minitest/(none):"
+ if result =~ /rspec|minitest/
+ test_framework = result
+ else
+ test_framework = false
+ end
+ end
+
+ if Bundler.settings["gem.test"].nil?
+ Bundler.settings.set_global("gem.test", test_framework)
+ end
+
+ test_framework
+ end
+
+ def bundler_dependency_version
+ v = Gem::Version.new(Bundler::VERSION)
+ req = v.segments[0..1]
+ req << "a" if v.prerelease?
+ req.join(".")
+ end
+
+ def ensure_safe_gem_name(name, constant_array)
+ if name =~ /^\d/
+ Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers."
+ exit 1
+ end
+
+ constant_name = constant_array.join("::")
+
+ existing_constant = constant_array.inject(Object) do |c, s|
+ defined = begin
+ c.const_defined?(s)
+ rescue NameError
+ Bundler.ui.error "Invalid gem name #{name} -- `#{constant_name}` is an invalid constant name"
+ exit 1
+ end
+ (defined && c.const_get(s)) || break
+ end
+
+ return unless existing_constant
+ Bundler.ui.error "Invalid gem name #{name} constant #{constant_name} is already in use. Please choose another gem name."
+ exit 1
+ end
+
+ def open_editor(editor, file)
+ thor.run(%(#{editor} "#{file}"))
+ end
+ end
+end
diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb
new file mode 100644
index 0000000000..4465fba9d4
--- /dev/null
+++ b/lib/bundler/cli/info.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Info
+ attr_reader :gem_name, :options
+ def initialize(options, gem_name)
+ @options = options
+ @gem_name = gem_name
+ end
+
+ def run
+ spec = spec_for_gem(gem_name)
+
+ spec_not_found(gem_name) unless spec
+ return print_gem_path(spec) if @options[:path]
+ print_gem_info(spec)
+ end
+
+ private
+
+ def spec_for_gem(gem_name)
+ spec = Bundler.definition.specs.find {|s| s.name == gem_name }
+ spec || default_gem_spec(gem_name)
+ end
+
+ def default_gem_spec(gem_name)
+ return unless Gem::Specification.respond_to?(:find_all_by_name)
+ gem_spec = Gem::Specification.find_all_by_name(gem_name).last
+ return gem_spec if gem_spec && gem_spec.respond_to?(:default_gem?) && gem_spec.default_gem?
+ end
+
+ def spec_not_found(gem_name)
+ raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies)
+ end
+
+ def print_gem_path(spec)
+ Bundler.ui.info spec.full_gem_path
+ end
+
+ def print_gem_info(spec)
+ gem_info = String.new
+ gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n"
+ gem_info << "\tSummary: #{spec.summary}\n" if spec.summary
+ gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage
+ gem_info << "\tPath: #{spec.full_gem_path}\n"
+ gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem?
+ Bundler.ui.info gem_info
+ end
+ end
+end
diff --git a/lib/bundler/cli/init.rb b/lib/bundler/cli/init.rb
new file mode 100644
index 0000000000..8ffd1db41a
--- /dev/null
+++ b/lib/bundler/cli/init.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Init
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ if File.exist?("Gemfile")
+ Bundler.ui.error "Gemfile already exists at #{SharedHelpers.pwd}/Gemfile"
+ exit 1
+ end
+
+ if options[:gemspec]
+ gemspec = File.expand_path(options[:gemspec])
+ unless File.exist?(gemspec)
+ Bundler.ui.error "Gem specification #{gemspec} doesn't exist"
+ exit 1
+ end
+
+ spec = Bundler.load_gemspec_uncached(gemspec)
+
+ puts "Writing new Gemfile to #{SharedHelpers.pwd}/Gemfile"
+ File.open("Gemfile", "wb") do |file|
+ file << "# Generated from #{gemspec}\n"
+ file << spec.to_gemfile
+ end
+ else
+ puts "Writing new Gemfile to #{SharedHelpers.pwd}/Gemfile"
+ FileUtils.cp(File.expand_path("../../templates/Gemfile", __FILE__), "Gemfile")
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb
new file mode 100644
index 0000000000..b17292643f
--- /dev/null
+++ b/lib/bundler/cli/inject.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Inject
+ attr_reader :options, :name, :version, :group, :source, :gems
+ def initialize(options, name, version)
+ @options = options
+ @name = name
+ @version = version || last_version_number
+ @group = options[:group].split(",") unless options[:group].nil?
+ @source = options[:source]
+ @gems = []
+ end
+
+ def run
+ # The required arguments allow Thor to give useful feedback when the arguments
+ # are incorrect. This adds those first two arguments onto the list as a whole.
+ gems.unshift(source).unshift(group).unshift(version).unshift(name)
+
+ # Build an array of Dependency objects out of the arguments
+ deps = []
+ # when `inject` support addition of more than one gem, then this loop will
+ # help. Currently this loop is running once.
+ gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source|
+ ops = Gem::Requirement::OPS.map {|key, _val| key }
+ has_op = ops.any? {|op| gem_version.start_with? op }
+ gem_version = "~> #{gem_version}" unless has_op
+ deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source)
+ end
+
+ added = Injector.inject(deps, options)
+
+ if added.any?
+ Bundler.ui.confirm "Added to Gemfile:"
+ Bundler.ui.confirm(added.map do |d|
+ name = "'#{d.name}'"
+ requirement = ", '#{d.requirement}'"
+ group = ", :group => #{d.groups.inspect}" if d.groups != Array(:default)
+ source = ", :source => '#{d.source}'" unless d.source.nil?
+ %(gem #{name}#{requirement}#{group}#{source})
+ end.join("\n"))
+ else
+ Bundler.ui.confirm "All gems were already present in the Gemfile"
+ end
+ end
+
+ private
+
+ def last_version_number
+ definition = Bundler.definition(true)
+ definition.resolve_remotely!
+ specs = definition.index[name].sort_by(&:version)
+ unless options[:pre]
+ specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
+ end
+ spec = specs.last
+ spec.version.to_s
+ end
+ end
+end
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
new file mode 100644
index 0000000000..ff6bedd9fd
--- /dev/null
+++ b/lib/bundler/cli/install.rb
@@ -0,0 +1,214 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Install
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+
+ warn_if_root
+
+ [:with, :without].each do |option|
+ if options[option]
+ options[option] = options[option].join(":").tr(" ", ":").split(":")
+ end
+ end
+
+ check_for_group_conflicts
+
+ normalize_groups
+
+ ENV["RB_USER_INSTALL"] = "1" if Bundler::FREEBSD
+
+ # Disable color in deployment mode
+ Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment]
+
+ check_for_options_conflicts
+
+ check_trust_policy
+
+ if options[:deployment] || options[:frozen]
+ unless Bundler.default_lockfile.exist?
+ flag = options[:deployment] ? "--deployment" : "--frozen"
+ raise ProductionError, "The #{flag} flag requires a #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}. Please make " \
+ "sure you have checked your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} into version control " \
+ "before deploying."
+ end
+
+ options[:local] = true if Bundler.app_cache.exist?
+
+ Bundler.settings[:frozen] = "1"
+ end
+
+ # When install is called with --no-deployment, disable deployment mode
+ if options[:deployment] == false
+ Bundler.settings.delete(:frozen)
+ options[:system] = true
+ end
+
+ normalize_settings
+
+ Bundler::Fetcher.disable_endpoint = options["full-index"]
+
+ if options["binstubs"]
+ Bundler::SharedHelpers.major_deprecation \
+ "The --binstubs option will be removed in favor of `bundle binstubs`"
+ end
+
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
+
+ definition = Bundler.definition
+ definition.validate_runtime!
+
+ installer = Installer.install(Bundler.root, definition, options)
+ Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.settings[:frozen]
+
+ Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}."
+ Bundler::CLI::Common.output_without_groups_message
+
+ if Bundler.settings[:path]
+ absolute_path = File.expand_path(Bundler.settings[:path])
+ relative_path = absolute_path.sub(File.expand_path(".") + File::SEPARATOR, "." + File::SEPARATOR)
+ Bundler.ui.confirm "Bundled gems are installed into #{relative_path}."
+ else
+ Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed."
+ end
+
+ Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
+
+ warn_ambiguous_gems
+
+ if Bundler.settings[:clean] && Bundler.settings[:path]
+ require "bundler/cli/clean"
+ Bundler::CLI::Clean.new(options).run
+ end
+ rescue GemNotFound, VersionConflict => e
+ if options[:local] && Bundler.app_cache.exist?
+ Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory."
+ end
+
+ unless Bundler.definition.has_rubygems_remotes?
+ Bundler.ui.warn <<-WARN, :wrap => true
+ Your Gemfile has no gem server sources. If you need gems that are \
+ not already on your machine, add a line like this to your Gemfile:
+ source 'https://rubygems.org'
+ WARN
+ end
+ raise e
+ rescue Gem::InvalidSpecificationException => e
+ Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed."
+ raise e
+ end
+
+ private
+
+ def warn_if_root
+ return if Bundler.settings[:silence_root_warning] || Bundler::WINDOWS || !Process.uid.zero?
+ Bundler.ui.warn "Don't run Bundler as root. Bundler can ask for sudo " \
+ "if it is needed, and installing your bundle as root will break this " \
+ "application for all non-root users on this machine.", :wrap => true
+ end
+
+ def dependencies_count_for(definition)
+ count = definition.dependencies.count
+ "#{count} Gemfile #{count == 1 ? "dependency" : "dependencies"}"
+ end
+
+ def gems_installed_for(definition)
+ count = definition.specs.count
+ "#{count} #{count == 1 ? "gem" : "gems"} now installed"
+ end
+
+ def check_for_group_conflicts
+ if options[:without] && options[:with]
+ conflicting_groups = options[:without] & options[:with]
+ unless conflicting_groups.empty?
+ Bundler.ui.error "You can't list a group in both, --with and --without." \
+ " The offending groups are: #{conflicting_groups.join(", ")}."
+ exit 1
+ end
+ end
+ end
+
+ def check_for_options_conflicts
+ if (options[:path] || options[:deployment]) && options[:system]
+ error_message = String.new
+ error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path]
+ error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment]
+ raise InvalidOption.new(error_message)
+ end
+ end
+
+ def check_trust_policy
+ if options["trust-policy"]
+ unless Bundler.rubygems.security_policies.keys.include?(options["trust-policy"])
+ Bundler.ui.error "Rubygems doesn't know about trust policy '#{options["trust-policy"]}'. " \
+ "The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}."
+ exit 1
+ end
+ Bundler.settings["trust-policy"] = options["trust-policy"]
+ else
+ Bundler.settings["trust-policy"] = nil if Bundler.settings["trust-policy"]
+ end
+ end
+
+ def normalize_groups
+ Bundler.settings.with = [] if options[:with] && options[:with].empty?
+ Bundler.settings.without = [] if options[:without] && options[:without].empty?
+
+ with = options.fetch("with", [])
+ with |= Bundler.settings.with.map(&:to_s)
+ with -= options[:without] if options[:without]
+
+ without = options.fetch("without", [])
+ without |= Bundler.settings.without.map(&:to_s)
+ without -= options[:with] if options[:with]
+
+ options[:with] = with
+ options[:without] = without
+ end
+
+ def normalize_settings
+ Bundler.settings[:path] = nil if options[:system]
+ Bundler.settings[:path] = "vendor/bundle" if options[:deployment]
+ Bundler.settings[:path] = options["path"] if options["path"]
+ Bundler.settings[:path] ||= "bundle" if options["standalone"]
+
+ Bundler.settings[:bin] = options["binstubs"] if options["binstubs"]
+ Bundler.settings[:bin] = nil if options["binstubs"] && options["binstubs"].empty?
+
+ Bundler.settings[:shebang] = options["shebang"] if options["shebang"]
+
+ Bundler.settings[:jobs] = options["jobs"] if options["jobs"]
+
+ Bundler.settings[:no_prune] = true if options["no-prune"]
+
+ Bundler.settings[:no_install] = true if options["no-install"]
+
+ Bundler.settings[:clean] = options["clean"] if options["clean"]
+
+ Bundler.settings.without = options[:without]
+ Bundler.settings.with = options[:with]
+
+ Bundler.settings[:disable_shared_gems] = Bundler.settings[:path] ? true : nil
+ end
+
+ def warn_ambiguous_gems
+ Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris|
+ Bundler.ui.error "Warning: the gem '#{name}' was found in multiple sources."
+ Bundler.ui.error "Installed from: #{installed_from_uri}"
+ Bundler.ui.error "Also found in:"
+ also_found_in_uris.each {|uri| Bundler.ui.error " * #{uri}" }
+ Bundler.ui.error "You should add a source requirement to restrict this gem to your preferred source."
+ Bundler.ui.error "For example:"
+ Bundler.ui.error " gem '#{name}', :source => '#{installed_from_uri}'"
+ Bundler.ui.error "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again."
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb
new file mode 100644
index 0000000000..ace0f985a9
--- /dev/null
+++ b/lib/bundler/cli/issue.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+module Bundler
+ class CLI::Issue
+ def run
+ Bundler.ui.info <<-EOS.gsub(/^ {8}/, "")
+ Did you find an issue with Bundler? Before filing a new issue,
+ be sure to check out these resources:
+
+ 1. Check out our troubleshooting guide for quick fixes to common issues:
+ https://github.com/bundler/bundler/blob/master/doc/TROUBLESHOOTING.md
+
+ 2. Instructions for common Bundler uses can be found on the documentation
+ site: http://bundler.io/
+
+ 3. Information about each Bundler command can be found in the Bundler
+ man pages: http://bundler.io/man/bundle.1.html
+
+ Hopefully the troubleshooting steps above resolved your problem! If things
+ still aren't working the way you expect them to, please let us know so
+ that we can diagnose and help fix the problem you're having. Please
+ view the Filing Issues guide for more information:
+ https://github.com/bundler/bundler/blob/master/doc/contributing/ISSUES.md
+
+ EOS
+
+ Bundler.ui.info Bundler::Env.new.report
+
+ Bundler.ui.info "\n## Bundle Doctor"
+ doctor
+ end
+
+ def doctor
+ require "bundler/cli/doctor"
+ Bundler::CLI::Doctor.new({}).run
+ end
+ end
+end
diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb
new file mode 100644
index 0000000000..223db9419f
--- /dev/null
+++ b/lib/bundler/cli/lock.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Lock
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ unless Bundler.default_gemfile
+ Bundler.ui.error "Unable to find a Gemfile to lock"
+ exit 1
+ end
+
+ print = options[:print]
+ ui = Bundler.ui
+ Bundler.ui = UI::Silent.new if print
+
+ Bundler::Fetcher.disable_endpoint = options["full-index"]
+
+ update = options[:update]
+ if update.is_a?(Array) # unlocking specific gems
+ Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update)
+ update = { :gems => update, :lock_shared_dependencies => options[:conservative] }
+ end
+ definition = Bundler.definition(update)
+
+ Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) if options[:update]
+
+ options["remove-platform"].each do |platform|
+ definition.remove_platform(platform)
+ end
+
+ options["add-platform"].each do |platform_string|
+ platform = Gem::Platform.new(platform_string)
+ if platform.to_s == "unknown"
+ Bundler.ui.warn "The platform `#{platform_string}` is unknown to RubyGems " \
+ "and adding it will likely lead to resolution errors"
+ end
+ definition.add_platform(platform)
+ end
+
+ if definition.platforms.empty?
+ raise InvalidOption, "Removing all platforms from the bundle is not allowed"
+ end
+
+ definition.resolve_remotely! unless options[:local]
+
+ if print
+ puts definition.to_lock
+ else
+ file = options[:lockfile]
+ file = file ? File.expand_path(file) : Bundler.default_lockfile
+ puts "Writing lockfile to #{file}"
+ definition.lock(file)
+ end
+
+ Bundler.ui = ui
+ end
+ end
+end
diff --git a/lib/bundler/cli/open.rb b/lib/bundler/cli/open.rb
new file mode 100644
index 0000000000..9a21f6811c
--- /dev/null
+++ b/lib/bundler/cli/open.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+require "shellwords"
+
+module Bundler
+ class CLI::Open
+ attr_reader :options, :name
+ def initialize(options, name)
+ @options = options
+ @name = name
+ end
+
+ def run
+ editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }
+ return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor
+ return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match)
+ path = spec.full_gem_path
+ Dir.chdir(path) do
+ command = Shellwords.split(editor) + [path]
+ Bundler.with_clean_env do
+ system(*command)
+ end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb
new file mode 100644
index 0000000000..863d0dd388
--- /dev/null
+++ b/lib/bundler/cli/outdated.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Outdated
+ attr_reader :options, :gems
+
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ check_for_deployment_mode
+
+ sources = Array(options[:source])
+
+ gems.each do |gem_name|
+ Bundler::CLI::Common.select_spec(gem_name)
+ end
+
+ Bundler.definition.validate_runtime!
+ current_specs = Bundler.ui.silence { Bundler.definition.resolve }
+ current_dependencies = {}
+ Bundler.ui.silence do
+ Bundler.load.dependencies.each do |dep|
+ current_dependencies[dep.name] = dep
+ end
+ end
+
+ definition = if gems.empty? && sources.empty?
+ # We're doing a full update
+ Bundler.definition(true)
+ else
+ Bundler.definition(:gems => gems, :sources => sources)
+ end
+
+ Bundler::CLI::Common.configure_gem_version_promoter(
+ Bundler.definition,
+ options
+ )
+
+ # the patch level options imply strict is also true. It wouldn't make
+ # sense otherwise.
+ strict = options[:strict] ||
+ Bundler::CLI::Common.patch_level_options(options).any?
+
+ filter_options_patch = options.keys &
+ %w(filter-major filter-minor filter-patch)
+
+ definition_resolution = proc do
+ options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely!
+ end
+
+ if options[:parseable]
+ Bundler.ui.silence(&definition_resolution)
+ else
+ definition_resolution.call
+ end
+
+ Bundler.ui.info ""
+ outdated_gems_by_groups = {}
+ outdated_gems_list = []
+
+ # Loop through the current specs
+ gemfile_specs, dependency_specs = current_specs.partition do |spec|
+ current_dependencies.key? spec.name
+ end
+
+ (gemfile_specs + dependency_specs).sort_by(&:name).each do |current_spec|
+ next if !gems.empty? && !gems.include?(current_spec.name)
+
+ dependency = current_dependencies[current_spec.name]
+ active_spec = retrieve_active_spec(strict, definition, current_spec)
+
+ next if active_spec.nil?
+ if filter_options_patch.any?
+ update_present = update_present_via_semver_portions(current_spec, active_spec, options)
+ next unless update_present
+ end
+
+ gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
+ next unless gem_outdated || (current_spec.git_version != active_spec.git_version)
+ groups = nil
+ if dependency && !options[:parseable]
+ groups = dependency.groups.join(", ")
+ end
+
+ outdated_gems_list << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
+
+ outdated_gems_by_groups[groups] ||= []
+ outdated_gems_by_groups[groups] << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
+ end
+
+ if outdated_gems_list.empty?
+ display_nothing_outdated_message(filter_options_patch)
+ else
+ unless options[:parseable]
+ if options[:pre]
+ Bundler.ui.info "Outdated gems included in the bundle (including " \
+ "pre-releases):"
+ else
+ Bundler.ui.info "Outdated gems included in the bundle:"
+ end
+ end
+
+ options_include_groups = [:group, :groups].select do |v|
+ options.keys.include?(v.to_s)
+ end
+
+ if options_include_groups.any?
+ ordered_groups = outdated_gems_by_groups.keys.compact.sort
+ [nil, ordered_groups].flatten.each do |groups|
+ gems = outdated_gems_by_groups[groups]
+ contains_group = if groups
+ groups.split(",").include?(options[:group])
+ else
+ options[:group] == "group"
+ end
+
+ next if (!options[:groups] && !contains_group) || gems.nil?
+
+ unless options[:parseable]
+ if groups
+ Bundler.ui.info "===== Group #{groups} ====="
+ else
+ Bundler.ui.info "===== Without group ====="
+ end
+ end
+
+ gems.each do |gem|
+ print_gem(
+ gem[:current_spec],
+ gem[:active_spec],
+ gem[:dependency],
+ groups,
+ options_include_groups.any?
+ )
+ end
+ end
+ else
+ outdated_gems_list.each do |gem|
+ print_gem(
+ gem[:current_spec],
+ gem[:active_spec],
+ gem[:dependency],
+ gem[:groups],
+ options_include_groups.any?
+ )
+ end
+ end
+
+ exit 1
+ end
+ end
+
+ private
+
+ def retrieve_active_spec(strict, definition, current_spec)
+ if strict
+ active_spec = definition.find_resolved_spec(current_spec)
+ else
+ active_specs = definition.find_indexed_specs(current_spec)
+ if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
+ active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
+ end
+ active_spec = active_specs.last
+ end
+
+ active_spec
+ end
+
+ def display_nothing_outdated_message(filter_options_patch)
+ unless options[:parseable]
+ if filter_options_patch.any?
+ display = filter_options_patch.map do |o|
+ o.sub("filter-", "")
+ end.join(" or ")
+
+ Bundler.ui.info "No #{display} updates to display.\n"
+ else
+ Bundler.ui.info "Bundle up to date!\n"
+ end
+ end
+ end
+
+ def print_gem(current_spec, active_spec, dependency, groups, options_include_groups)
+ spec_version = "#{active_spec.version}#{active_spec.git_version}"
+ spec_version += " (from #{active_spec.loaded_from})" if Bundler.ui.debug? && active_spec.loaded_from
+ current_version = "#{current_spec.version}#{current_spec.git_version}"
+
+ if dependency && dependency.specific?
+ dependency_version = %(, requested #{dependency.requirement})
+ end
+
+ spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \
+ "installed #{current_version}#{dependency_version})"
+
+ output_message = if options[:parseable]
+ spec_outdated_info.to_s
+ elsif options_include_groups || !groups
+ " * #{spec_outdated_info}"
+ else
+ " * #{spec_outdated_info} in groups \"#{groups}\""
+ end
+
+ Bundler.ui.info output_message.rstrip
+ end
+
+ def check_for_deployment_mode
+ if Bundler.settings[:frozen]
+ raise ProductionError, "You are trying to check outdated gems in " \
+ "deployment mode. Run `bundle outdated` elsewhere.\n" \
+ "\nIf this is a development machine, remove the " \
+ "#{Bundler.default_gemfile} freeze" \
+ "\nby running `bundle install --no-deployment`."
+ end
+ end
+
+ def update_present_via_semver_portions(current_spec, active_spec, options)
+ current_major = current_spec.version.segments.first
+ active_major = active_spec.version.segments.first
+
+ update_present = false
+ update_present = active_major > current_major if options["filter-major"]
+
+ if !update_present && (options["filter-minor"] || options["filter-patch"]) && current_major == active_major
+ current_minor = get_version_semver_portion_value(current_spec, 1)
+ active_minor = get_version_semver_portion_value(active_spec, 1)
+
+ update_present = active_minor > current_minor if options["filter-minor"]
+
+ if !update_present && options["filter-patch"] && current_minor == active_minor
+ current_patch = get_version_semver_portion_value(current_spec, 2)
+ active_patch = get_version_semver_portion_value(active_spec, 2)
+
+ update_present = active_patch > current_patch
+ end
+ end
+
+ update_present
+ end
+
+ def get_version_semver_portion_value(spec, version_portion_index)
+ version_section = spec.version.segments[version_portion_index, 1]
+ version_section.nil? ? 0 : (version_section.first || 0)
+ end
+ end
+end
diff --git a/lib/bundler/cli/package.rb b/lib/bundler/cli/package.rb
new file mode 100644
index 0000000000..cf65e8a68c
--- /dev/null
+++ b/lib/bundler/cli/package.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Package
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+ Bundler.settings[:path] = File.expand_path(options[:path]) if options[:path]
+ Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms")
+ Bundler.settings[:cache_path] = options["cache-path"] if options.key?("cache-path")
+
+ setup_cache_all
+ install
+
+ # TODO: move cache contents here now that all bundles are locked
+ custom_path = Pathname.new(options[:path]) if options[:path]
+ Bundler.load.cache(custom_path)
+ end
+
+ private
+
+ def install
+ require "bundler/cli/install"
+ options = self.options.dup
+ if Bundler.settings[:cache_all_platforms]
+ options["local"] = false
+ options["update"] = true
+ end
+ Bundler::CLI::Install.new(options).run
+ end
+
+ def setup_cache_all
+ Bundler.settings[:cache_all] = options[:all] if options.key?("all")
+
+ if Bundler.definition.has_local_dependencies? && !Bundler.settings[:cache_all]
+ Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
+ "to package them as well, please pass the --all flag. This will be the default " \
+ "on Bundler 2.0."
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/platform.rb b/lib/bundler/cli/platform.rb
new file mode 100644
index 0000000000..9fdab0a53c
--- /dev/null
+++ b/lib/bundler/cli/platform.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Platform
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ platforms, ruby_version = Bundler.ui.silence do
+ locked_ruby_version = Bundler.locked_gems && Bundler.locked_gems.ruby_version
+ gemfile_ruby_version = Bundler.definition.ruby_version && Bundler.definition.ruby_version.single_version_string
+ [Bundler.definition.platforms.map {|p| "* #{p}" },
+ locked_ruby_version || gemfile_ruby_version]
+ end
+ output = []
+
+ if options[:ruby]
+ if ruby_version
+ output << ruby_version
+ else
+ output << "No ruby version specified"
+ end
+ else
+ output << "Your platform is: #{RUBY_PLATFORM}"
+ output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}"
+
+ if ruby_version
+ output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}"
+
+ begin
+ Bundler.definition.validate_runtime!
+ output << "Your current platform satisfies the Ruby version requirement."
+ rescue RubyVersionMismatch => e
+ output << e.message
+ end
+ else
+ output << "Your Gemfile does not specify a Ruby version requirement."
+ end
+ end
+
+ Bundler.ui.info output.join("\n\n")
+ end
+ end
+end
diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb
new file mode 100644
index 0000000000..277822dafc
--- /dev/null
+++ b/lib/bundler/cli/plugin.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+require "bundler/vendored_thor"
+module Bundler
+ class CLI::Plugin < Thor
+ desc "install PLUGINS", "Install the plugin from the source"
+ long_desc <<-D
+ Install plugins either from the rubygems source provided (with --source option) or from a git source provided with (--git option). If no sources are provided, it uses Gem.sources
+ D
+ method_option "source", :type => :string, :default => nil, :banner =>
+ "URL of the RubyGems source to fetch the plugin from"
+ method_option "version", :type => :string, :default => nil, :banner =>
+ "The version of the plugin to fetch"
+ method_option "git", :type => :string, :default => nil, :banner =>
+ "URL of the git repo to fetch from"
+ method_option "branch", :type => :string, :default => nil, :banner =>
+ "The git branch to checkout"
+ method_option "ref", :type => :string, :default => nil, :banner =>
+ "The git revision to check out"
+ def install(*plugins)
+ Bundler::Plugin.install(plugins, options)
+ end
+ end
+end
diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb
new file mode 100644
index 0000000000..10d03b4b41
--- /dev/null
+++ b/lib/bundler/cli/pristine.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Pristine
+ def run
+ definition = Bundler.definition
+ definition.validate_runtime!
+ installer = Bundler::Installer.new(Bundler.root, definition)
+
+ Bundler.load.specs.each do |spec|
+ next if spec.name == "bundler" # Source::Rubygems doesn't install bundler
+
+ gem_name = "#{spec.name} (#{spec.version}#{spec.git_version})"
+ gem_name += " (#{spec.platform})" if !spec.platform.nil? && spec.platform != Gem::Platform::RUBY
+
+ case source = spec.source
+ when Source::Rubygems
+ cached_gem = spec.cache_file
+ unless File.exist?(cached_gem)
+ Bundler.ui.error("Failed to pristine #{gem_name}. Cached gem #{cached_gem} does not exist.")
+ next
+ end
+ when Source::Git
+ source.remote!
+ else
+ Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
+ next
+ end
+ FileUtils.rm_rf spec.full_gem_path
+
+ Bundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb
new file mode 100644
index 0000000000..47d4470aec
--- /dev/null
+++ b/lib/bundler/cli/show.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Show
+ attr_reader :options, :gem_name, :latest_specs
+ def initialize(options, gem_name)
+ @options = options
+ @gem_name = gem_name
+ @verbose = options[:verbose] || options[:outdated]
+ @latest_specs = fetch_latest_specs if @verbose
+ end
+
+ def run
+ Bundler.ui.silence do
+ Bundler.definition.validate_runtime!
+ Bundler.load.lock
+ end
+
+ if gem_name
+ if gem_name == "bundler"
+ path = File.expand_path("../../../..", __FILE__)
+ else
+ spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match)
+ return unless spec
+ path = spec.full_gem_path
+ unless File.directory?(path)
+ Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at:"
+ end
+ end
+ return Bundler.ui.info(path)
+ end
+
+ if options[:paths]
+ Bundler.load.specs.sort_by(&:name).map do |s|
+ Bundler.ui.info s.full_gem_path
+ end
+ else
+ Bundler.ui.info "Gems included by the bundle:"
+ Bundler.load.specs.sort_by(&:name).each do |s|
+ desc = " * #{s.name} (#{s.version}#{s.git_version})"
+ if @verbose
+ latest = latest_specs.find {|l| l.name == s.name }
+ Bundler.ui.info <<-END.gsub(/^ +/, "")
+ #{desc}
+ \tSummary: #{s.summary || "No description available."}
+ \tHomepage: #{s.homepage || "No website available."}
+ \tStatus: #{outdated?(s, latest) ? "Outdated - #{s.version} < #{latest.version}" : "Up to date"}
+ END
+ else
+ Bundler.ui.info desc
+ end
+ end
+ end
+ end
+
+ private
+
+ def fetch_latest_specs
+ definition = Bundler.definition(true)
+ if options[:outdated]
+ Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
+ Bundler.ui.silence { definition.resolve_remotely! }
+ else
+ definition.resolve_with_cache!
+ end
+ Bundler.reset!
+ definition.specs
+ end
+
+ def outdated?(current, latest)
+ return false unless latest
+ Gem::Version.new(current.version) < Gem::Version.new(latest.version)
+ end
+ end
+end
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
new file mode 100644
index 0000000000..df7524f004
--- /dev/null
+++ b/lib/bundler/cli/update.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Update
+ attr_reader :options, :gems
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
+
+ sources = Array(options[:source])
+ groups = Array(options[:group]).map(&:to_sym)
+
+ if gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !options[:bundler]
+ # We're doing a full update
+ Bundler.definition(true)
+ else
+ unless Bundler.default_lockfile.exist?
+ raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \
+ "Run `bundle install` to update and install the bundled gems."
+ end
+ Bundler::CLI::Common.ensure_all_gems_in_lockfile!(gems)
+
+ if groups.any?
+ specs = Bundler.definition.specs_for groups
+ gems.concat(specs.map(&:name))
+ end
+
+ Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby],
+ :lock_shared_dependencies => options[:conservative])
+ end
+
+ Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options)
+
+ Bundler::Fetcher.disable_endpoint = options["full-index"]
+
+ opts = options.dup
+ opts["update"] = true
+ opts["local"] = options[:local]
+
+ Bundler.settings[:jobs] = opts["jobs"] if opts["jobs"]
+
+ Bundler.definition.validate_runtime!
+ installer = Installer.install Bundler.root, Bundler.definition, opts
+ Bundler.load.cache if Bundler.app_cache.exist?
+
+ if Bundler.settings[:clean] && Bundler.settings[:path]
+ require "bundler/cli/clean"
+ Bundler::CLI::Clean.new(options).run
+ end
+
+ Bundler.ui.confirm "Bundle updated!"
+ Bundler::CLI::Common.output_without_groups_message
+ Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
+ end
+ end
+end
diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb
new file mode 100644
index 0000000000..767fe8f3de
--- /dev/null
+++ b/lib/bundler/cli/viz.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+module Bundler
+ class CLI::Viz
+ attr_reader :options, :gem_name
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ # make sure we get the right `graphviz`. There is also a `graphviz`
+ # gem we're not built to support
+ gem "ruby-graphviz"
+ require "graphviz"
+
+ options[:without] = options[:without].join(":").tr(" ", ":").split(":")
+ output_file = File.expand_path(options[:file])
+
+ graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without])
+ graph.viz
+ rescue LoadError => e
+ Bundler.ui.error e.inspect
+ Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:"
+ Bundler.ui.warn "`gem install ruby-graphviz`"
+ rescue StandardError => e
+ raise unless e.message =~ /GraphViz not installed or dot not in PATH/
+ Bundler.ui.error e.message
+ Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`."
+ end
+ end
+end