diff options
Diffstat (limited to 'lib/bundler/cli.rb')
| -rw-r--r-- | lib/bundler/cli.rb | 826 |
1 files changed, 826 insertions, 0 deletions
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb new file mode 100644 index 0000000000..40a9dc35b8 --- /dev/null +++ b/lib/bundler/cli.rb @@ -0,0 +1,826 @@ +# frozen_string_literal: true + +require_relative "vendored_thor" + +module Bundler + class CLI < Thor + require_relative "cli/common" + require_relative "cli/install" + + package_name "Bundler" + + AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze + PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze + EXTENSIONS = ["c", "rust", "go"].freeze + + COMMAND_ALIASES = { + "check" => "c", + "install" => "i", + "plugin" => "", + "list" => "ls", + "exec" => ["e", "ex", "exe"], + "cache" => ["package", "pack"], + "version" => ["-v", "--version"], + }.freeze + + def self.start(*) + check_invalid_ext_option(ARGV) if ARGV.include?("--ext") + + super + ensure + Bundler::SharedHelpers.print_major_deprecations! + end + + def self.dispatch(*) + super do |i| + i.send(:print_command) + i.send(:warn_on_outdated_bundler) + end + end + + def self.all_aliases + @all_aliases ||= begin + command_aliases = {} + + COMMAND_ALIASES.each do |name, aliases| + Array(aliases).each do |one_alias| + command_aliases[one_alias] = name + end + end + + command_aliases + end + end + + def self.aliases_for(command_name) + COMMAND_ALIASES.select {|k, _| k == command_name }.invert + end + + def initialize(*args) + super + + current_cmd = args.last[:current_command].name + + # `bundle config` manages stored settings, so avoid promoting settings + # like `gemfile` or `lockfile` to environment variables before it runs. + unless current_cmd == "config" + Bundler.configure_custom_gemfile(options[:gemfile]) + + # lock --lockfile works differently than install --lockfile + unless current_cmd == "lock" + custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile] + if custom_lockfile && !custom_lockfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) + reset_settings = true + end + end + end + + Bundler.reset_settings_and_root! if reset_settings + + Bundler.auto_switch + + Bundler.settings.set_command_option_if_given :retry, options[:retry] + + Bundler.auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) + rescue UnknownArgumentError => e + raise InvalidOption, e.message + ensure + self.options ||= {} + unprinted_warnings = Bundler.ui.unprinted_warnings + Bundler.ui = UI::Shell.new(options) + Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose] + unprinted_warnings.each {|w| Bundler.ui.warn(w) } + end + + check_unknown_options!(except: [:config, :exec]) + stop_on_unknown_option! :exec + + desc "cli_help", "Prints a summary of bundler commands", hide: true + def cli_help + version + Bundler.ui.info "\n" + + primary_commands = ["install", "update", "cache", "exec", "config", "help"] + + list = self.class.printable_commands(true) + by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] } + utilities = by_name.keys.sort - primary_commands + primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first } + utilities.map! {|name| by_name[name].first } + + shell.say "Bundler commands:\n\n" + + shell.say " Primary commands:\n" + shell.print_table(primary_commands, indent: 4, truncate: true) + shell.say + shell.say " Utilities:\n" + shell.print_table(utilities, indent: 4, truncate: true) + shell.say + self.class.send(:class_options_help, shell) + end + + desc "install_or_cli_help", "Deprecated alias of install", hide: true + def install_or_cli_help + Bundler.ui.warn <<~MSG + `bundle install_or_cli_help` is a deprecated alias of `bundle install`. + It might be called due to the 'default_cli_command' being set to 'install_or_cli_help', + if so fix that by running `bundle config set default_cli_command install --global`. + MSG + invoke_other_command("install") + end + + def self.default_command(meth = nil) + return super if meth + + unless Bundler.settings[:default_cli_command] + Bundler.ui.info <<~MSG + In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. + Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. + You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, + or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. + This message will be removed after a default_cli_command value is set. + + MSG + end + + Bundler.settings[:default_cli_command] || "install" + end + + class_option "no-color", type: :boolean, desc: "Disable colorization in output" + class_option "retry", type: :numeric, aliases: "-r", banner: "NUM", + desc: "Specify the number of times you wish to attempt network commands" + class_option "verbose", type: :boolean, desc: "Enable verbose output mode", aliases: "-V" + + def help(cli = nil) + cli = self.class.all_aliases[cli] if self.class.all_aliases[cli] + + if Bundler.settings[:plugins] && Bundler::Plugin.command?(cli) && !self.class.all_commands.key?(cli) + return Bundler::Plugin.exec_command(cli, ["--help"]) + end + + case cli + when "gemfile" then command = "gemfile" + when nil then command = "bundle" + else command = "bundle-#{cli}" + end + + man_path = File.expand_path("man", __dir__) + man_pages = Hash[Dir.glob(File.join(man_path, "**", "*")).grep(/.*\.\d*\Z/).collect do |f| + [File.basename(f, ".*"), f] + end] + + if man_pages.include?(command) + man_page = man_pages[command] + if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+}) + Kernel.exec("man", man_page) + else + puts File.read("#{man_path}/#{File.basename(man_page)}.ronn") + end + elsif command_path = Bundler.which("bundler-#{cli}") + Kernel.exec(command_path, "--help") + else + super + end + end + + def self.handle_no_command_error(command, has_namespace = $thor_runner) + if Bundler.settings[:plugins] && Bundler::Plugin.command?(command) + return Bundler::Plugin.exec_command(command, ARGV[1..-1]) + end + + return super unless command_path = Bundler.which("bundler-#{command}") + + Kernel.exec(command_path, *ARGV[1..-1]) + end + + desc "init [OPTIONS]", "Generates a Gemfile into the current working directory" + long_desc <<-D + Init generates a default Gemfile in the current working directory. When adding a + Gemfile to a gem with a gemspec, the --gemspec option will automatically add each + dependency listed in the gemspec file to the newly created Gemfile. + D + method_option "gemspec", type: :string, banner: "Use the specified .gemspec to create the Gemfile" + method_option "gemfile", type: :string, banner: "Use the specified name for the gemfile instead of 'Gemfile'" + def init + require_relative "cli/init" + Init.new(options.dup).run + end + + desc "check [OPTIONS]", "Checks if the dependencies listed in Gemfile are satisfied by currently installed gems" + long_desc <<-D + Check searches the local machine for each of the gems requested in the Gemfile. If + all gems are found, Bundler prints a success message and exits with a status of 0. + If not, the first missing gem is listed and Bundler exits status 1. + D + method_option "dry-run", type: :boolean, default: false, banner: "Lock the Gemfile" + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)" + def check + remembered_flag_deprecation("path") + + require_relative "cli/check" + Check.new(options).run + end + + map aliases_for("check") + + desc "remove [GEM [GEM ...]]", "Removes gems from the Gemfile" + long_desc <<-D + Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning. + D + method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile (removed)" + def remove(*gems) + if ARGV.include?("--install") + removed_message = "The `--install` flag has been removed. `bundle install` is triggered by default." + raise InvalidOption, removed_message + end + + require_relative "cli/remove" + Remove.new(gems, options).run + end + + desc "install [OPTIONS]", "Install the current environment to the system" + long_desc <<-D + Install will install all of the gems in the current bundle, making them available + for use. In a freshly checked out repository, this command will give you the same + gem versions as the last person who updated the Gemfile and ran `bundle update`. + + Passing [DIR] to install (e.g. vendor) will cause the unpacked gems to be installed + into the [DIR] directory rather than into system gems. + + If the bundle has already been installed, bundler will tell you so and then exit. + D + method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin (removed)" + method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install (removed)" + method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments (removed)" + method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install (removed)" + method_option "full-index", type: :boolean, banner: "Fall back to using the single-file index of all gems" + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" + method_option "jobs", aliases: "-j", type: :numeric, banner: "Specify the number of jobs to run in parallel" + method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "lockfile", type: :string, banner: "Use the specified lockfile instead of the default." + method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" + method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." + method_option "no-lock", type: :boolean, banner: "Don't create a lockfile." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" + method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." + method_option "quiet", type: :boolean, banner: "Only output warnings and errors." + method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default, usually 'ruby' (removed)" + method_option "standalone", type: :array, lazy_default: [], banner: "Make a bundle that can work without the Bundler runtime" + method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application (removed)" + method_option "trust-policy", alias: "P", type: :string, banner: "Gem trust policy (like gem install -P). Must be one of #{Bundler.rubygems.security_policy_keys.join("|")}" + method_option "target-rbconfig", type: :string, banner: "Path to rbconfig.rb for the deployment target platform" + method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group (removed)." + method_option "with", type: :array, banner: "Include gems that are part of the specified named group (removed)." + def install + %w[clean deployment frozen no-prune path shebang without with].each do |option| + remembered_flag_deprecation(option) + end + + print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system") + + remembered_flag_deprecation("deployment", negative: true) + + if ARGV.include?("--binstubs") + removed_message = "The --binstubs option has been removed in favor of `bundle binstubs --all`" + raise InvalidOption, removed_message + end + + require_relative "cli/install" + options = self.options.dup + options["lockfile"] ||= ENV["BUNDLE_LOCKFILE"] + Bundler.settings.temporary(no_install: false) do + Install.new(options).run + end + rescue GemfileNotFound => error + invoke_other_command("cli_help") + raise error # re-raise to show the error and get a failing exit status + end + + map aliases_for("install") + + desc "update [OPTIONS]", "Update the current environment" + long_desc <<-D + Update will install the newest versions of the gems listed in the Gemfile. Use + update when you have changed the Gemfile, or if you want to get the newest + possible versions of the gems in the bundle. + D + method_option "full-index", type: :boolean, banner: "Fall back to using the single-file index of all gems" + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" + method_option "group", aliases: "-g", type: :array, banner: "Update a specific group" + method_option "jobs", aliases: "-j", type: :numeric, banner: "Specify the number of jobs to run in parallel" + method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "quiet", type: :boolean, banner: "Only output warnings and errors." + method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)" + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" + method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock" + method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" + method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" + method_option "minor", type: :boolean, banner: "Prefer updating only to next minor version" + method_option "major", type: :boolean, banner: "Prefer updating to next major version (default)" + method_option "pre", type: :boolean, banner: "Always choose the highest allowed version when updating gems, regardless of prerelease status" + method_option "strict", type: :boolean, banner: "Do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." + method_option "all", type: :boolean, banner: "Update everything." + def update(*gems) + require_relative "cli/update" + Bundler.settings.temporary(no_install: false) do + Update.new(options, gems).run + end + end + + desc "show GEM [OPTIONS]", "Shows all gems that are part of the bundle, or the path to a given gem" + long_desc <<-D + Show lists the names and versions of all gems that are required by your Gemfile. + Calling show with [GEM] will list the exact location of that gem on your machine. + D + method_option "paths", type: :boolean, banner: "List the paths of all gems that are required by your Gemfile." + method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated (removed)." + def show(gem_name = nil) + if ARGV.include?("--outdated") + removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`" + raise InvalidOption, removed_message + end + require_relative "cli/show" + Show.new(options, gem_name).run + end + + desc "list", "List all gems in the bundle" + method_option "name-only", type: :boolean, banner: "print only the gem names" + method_option "only-group", type: :array, default: [], banner: "print gems from a given set of groups" + method_option "without-group", type: :array, default: [], banner: "print all gems except from a given set of groups" + method_option "format", type: :string, banner: "format output ('json' is the only supported format)" + method_option "paths", type: :boolean, banner: "print the path to each gem in the bundle" + def list + require_relative "cli/list" + List.new(options).run + end + + map aliases_for("list") + + desc "info GEM [OPTIONS]", "Show information for the given gem" + method_option "path", type: :boolean, banner: "Print full path to gem" + method_option "version", type: :boolean, banner: "Print gem version" + def info(gem_name) + require_relative "cli/info" + Info.new(options, gem_name).run + end + + desc "binstubs GEM [OPTIONS]", "Install the binstubs of the listed gem" + long_desc <<-D + Generate binstubs for executables in [GEM]. Binstubs are put into bin, + or the --binstubs directory if one has been set. Calling binstubs with [GEM [GEM]] + will create binstubs for all given gems. + D + method_option "force", type: :boolean, default: false, banner: "Overwrite existing binstubs if they exist" + method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory, `bin` by default (removed)" + method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')" + method_option "standalone", type: :boolean, banner: "Make binstubs that can work without the Bundler runtime" + method_option "all", type: :boolean, banner: "Install binstubs for all gems" + method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms" + def binstubs(*gems) + remembered_flag_deprecation("path", option_name: "bin") + + require_relative "cli/binstubs" + Binstubs.new(options, gems).run + end + + desc "add GEM VERSION", "Add gem to Gemfile and run bundle install" + long_desc <<-D + Adds the specified gem to Gemfile (if valid) and run 'bundle install' in one step. + D + method_option "version", aliases: "-v", type: :string + method_option "group", aliases: "-g", type: :string + method_option "source", aliases: "-s", type: :string + method_option "require", aliases: "-r", type: :string, banner: "Adds require path to gem. Provide false, or a path as a string." + method_option "path", type: :string + method_option "git", type: :string + method_option "github", type: :string + method_option "branch", type: :string + method_option "ref", type: :string + method_option "glob", type: :string, banner: "The location of a dependency's .gemspec, expanded within Ruby (single quotes recommended)" + method_option "quiet", type: :boolean, banner: "Only output warnings and errors." + method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it" + method_option "optimistic", type: :boolean, banner: "Ignored (now default behavior)" + method_option "pessimistic", type: :boolean, banner: "Adds pessimistic declaration of version to gem" + method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem" + def add(*gems) + require_relative "cli/add" + Add.new(options.dup, gems).run + end + + desc "outdated GEM [OPTIONS]", "List installed gems with newer versions available" + long_desc <<-D + Outdated lists the names and versions of gems that have a newer version available + in the given source. Calling outdated with [GEM [GEM]] will only check for newer + versions of the given gems. Prerelease gems are ignored by default. If your gems + are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1. + + For more information on patch level options (--major, --minor, --patch, + --strict) see documentation on the same options on the update command. + D + method_option "group", type: :string, banner: "List gems from a specific group" + method_option "groups", type: :boolean, banner: "List gems organized by groups" + method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "pre", type: :boolean, banner: "Check for newer pre-release gems" + method_option "source", type: :array, banner: "Check against a specific source" + method_option "filter-strict", type: :boolean, aliases: "--strict", banner: "Only list newer versions allowed by your Gemfile requirements" + method_option "update-strict", type: :boolean, banner: "Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "minor", type: :boolean, banner: "Prefer updating only to next minor version" + method_option "major", type: :boolean, banner: "Prefer updating to next major version (default)" + method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" + method_option "filter-major", type: :boolean, banner: "Only list major newer versions" + method_option "filter-minor", type: :boolean, banner: "Only list minor newer versions" + method_option "filter-patch", type: :boolean, banner: "Only list patch newer versions" + method_option "parseable", aliases: "--porcelain", type: :boolean, banner: "Use minimal formatting for more parseable output" + method_option "only-explicit", type: :boolean, banner: "Only list gems specified in your Gemfile, not their dependencies" + def outdated(*gems) + require_relative "cli/outdated" + Outdated.new(options, gems).run + end + + desc "fund [OPTIONS]", "Lists information about gems seeking funding assistance" + method_option "group", aliases: "-g", type: :array, banner: "Fetch funding information for a specific group" + def fund + require_relative "cli/fund" + Fund.new(options).run + end + + desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache" + method_option "all", type: :boolean, default: Bundler.settings[:cache_all], banner: "Include all sources (including path and git) (removed)." + method_option "all-platforms", type: :boolean, banner: "Include gems for all platforms present in the lockfile, not only the current one" + method_option "cache-path", type: :string, banner: "Specify a different cache path than the default (vendor/cache)." + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" + method_option "no-install", type: :boolean, banner: "Don't install the gems, only update the cache." + method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." + method_option "quiet", type: :boolean, banner: "Only output warnings and errors." + method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install (removed)" + long_desc <<-D + The cache command will copy the .gem files for every gem in the bundle into the + directory ./vendor/cache. If you then check that directory into your source + control repository, others who check out your source will be able to install the + bundle without having to download any additional gems. + D + def cache + print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all") + print_remembered_flag_deprecation("--no-all", "cache_all", "false") if ARGV.include?("--no-all") + + %w[frozen no-prune].each do |option| + remembered_flag_deprecation(option) + end + + if flag_passed?("--path") + removed_message = + "The `--path` flag has been removed because its semantics were unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + raise InvalidOption, removed_message + end + + require_relative "cli/cache" + Cache.new(options).run + end + + map aliases_for("cache") + + desc "exec [OPTIONS]", "Run the command in context of the bundle" + method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)." + method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile" + long_desc <<-D + Exec runs a command, providing it access to the gems in the bundle. While using + bundle exec you can require and call the bundled gems as if they were installed + into the system wide RubyGems repository. + D + def exec(*args) + if ARGV.include?("--no-keep-file-descriptors") + removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" + raise InvalidOption, removed_message + end + + require_relative "cli/exec" + Exec.new(options, args).run + end + + map aliases_for("exec") + + desc "config NAME [VALUE]", "Retrieve or set a configuration value" + long_desc <<-D + Retrieves or sets a configuration value. If only one parameter is provided, retrieve the value. If two parameters are provided, replace the + existing value with the newly provided one. + + By default, setting a configuration value sets it for all projects + on the machine. + + If a global setting is superseded by local configuration, this command + will show the current value, as well as any superseded values and + where they were specified. + D + require_relative "cli/config" + subcommand "config", Config + + desc "open GEM", "Opens the source directory of the given bundled gem" + method_option "path", type: :string, lazy_default: "", banner: "Open relative path of the gem source." + def open(name) + require_relative "cli/open" + Open.new(options, name).run + end + + desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded" + def console(group = nil) + require_relative "cli/console" + Console.new(options, group).run + end + + desc "version", "Prints Bundler version information" + def version + cli_help = current_command.name == "cli_help" + if cli_help || ARGV.include?("version") + build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" + end + + if !cli_help + Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" + else + Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" + end + end + + map aliases_for("version") + + desc "licenses", "Prints the license of all gems in the bundle" + def licenses + Bundler.load.specs.sort_by {|s| s.license.to_s }.reverse_each do |s| + gem_name = s.name + license = s.license || s.licenses + + if license.empty? + Bundler.ui.warn "#{gem_name}: Unknown" + else + Bundler.ui.info "#{gem_name}: #{license}" + end + end + end + + desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true + def viz + SharedHelpers.feature_removed! "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" + end + + desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem" + method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], banner: "Generate a binary executable for your library." + method_option :coc, type: :boolean, banner: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`." + method_option :edit, type: :string, aliases: "-e", required: false, lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, banner: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" + method_option :ext, type: :string, banner: "Generate the boilerplate for C extension code.", enum: EXTENSIONS + method_option :git, type: :boolean, default: true, banner: "Initialize a git repo inside your library." + method_option :mit, type: :boolean, banner: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." + method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true` (removed)." + method_option :changelog, type: :boolean, banner: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." + method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", enum: %w[rspec minitest test-unit], desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." + method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], banner: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" + method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], banner: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" + method_option :github_username, type: :string, default: Bundler.settings["gem.github_username"], banner: "Set your username on GitHub", desc: "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`." + method_option :bundle, type: :boolean, default: Bundler.settings["gem.bundle"], banner: "Automatically run `bundle install` after creation. Set a default with `bundle config set --global gem.bundle true`" + + def gem(name) + require_relative "cli/gem" + + raise InvalidOption, "--rubocop has been removed, use --linter=rubocop" if ARGV.include?("--rubocop") + raise InvalidOption, "--no-rubocop has been removed, use --no-linter" if ARGV.include?("--no-rubocop") + + cmd_args = args + [self] + cmd_args.unshift(options) + + Gem.new(*cmd_args).run + end + + def self.source_root + File.expand_path("templates", __dir__) + end + + desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory" + method_option "dry-run", type: :boolean, default: false, banner: "Only print out changes, do not clean gems" + method_option "force", type: :boolean, default: false, banner: "Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application." + def clean + require_relative "cli/clean" + Clean.new(options.dup).run + end + + desc "platform [OPTIONS]", "Displays platform compatibility information" + method_option "ruby", type: :boolean, default: false, banner: "only display ruby related platform information" + def platform + require_relative "cli/platform" + Platform.new(options).run + end + + desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", hide: true + def inject(*) + SharedHelpers.feature_removed! "The `inject` command has been replaced by the `add` command" + end + + desc "lock", "Creates a lockfile without installing" + method_option "update", type: :array, lazy_default: true, banner: "ignore the existing lockfile, update all gems by default, or update list of given gems" + method_option "local", type: :boolean, default: false, banner: "do not attempt to fetch remote gemspecs and use the local gem cache only" + method_option "print", type: :boolean, default: false, banner: "print the lockfile to STDOUT instead of writing to the file system" + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" + method_option "lockfile", type: :string, default: nil, banner: "the path the lockfile should be written to" + method_option "full-index", type: :boolean, default: false, banner: "Fall back to using the single-file index of all gems" + method_option "add-checksums", type: :boolean, default: false, banner: "Adds checksums to the lockfile" + method_option "add-platform", type: :array, default: [], banner: "Add a new platform to the lockfile" + method_option "remove-platform", type: :array, default: [], banner: "Remove a platform from the lockfile" + method_option "normalize-platforms", type: :boolean, default: false, banner: "Normalize lockfile platforms" + method_option "patch", type: :boolean, banner: "If updating, prefer updating only to next patch version" + method_option "minor", type: :boolean, banner: "If updating, prefer updating only to next minor version" + method_option "major", type: :boolean, banner: "If updating, prefer updating to next major version (default)" + method_option "pre", type: :boolean, banner: "If updating, always choose the highest allowed version, regardless of prerelease status" + method_option "strict", type: :boolean, banner: "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", type: :boolean, banner: "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" + method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" + def lock + require_relative "cli/lock" + Lock.new(options).run + end + + desc "env", "Print information about the environment Bundler is running under" + def env + Env.write($stdout) + end + + desc "doctor [OPTIONS]", "Checks the bundle for common problems" + require_relative "cli/doctor" + subcommand("doctor", Doctor) + + desc "issue", "Learn how to report an issue in Bundler" + def issue + require_relative "cli/issue" + Issue.new.run + end + + desc "pristine [GEMS...]", "Restores installed gems to pristine condition" + long_desc <<-D + Restores installed gems to pristine condition from files located in the + gem cache. Gems installed from a git repository will be issued `git + checkout --force`. + D + def pristine(*gems) + require_relative "cli/pristine" + Bundler.settings.temporary(no_install: false) do + Pristine.new(gems).run + end + end + + if Bundler.settings[:plugins] + require_relative "cli/plugin" + desc "plugin", "Manage the bundler plugins" + subcommand "plugin", Plugin + end + + # Reformat the arguments passed to bundle that include a --help flag + # into the corresponding `bundle help #{command}` call + def self.reformatted_help_args(args) + bundler_commands = (COMMAND_ALIASES.keys + COMMAND_ALIASES.values).flatten + + help_flags = %w[--help -h] + exec_commands = ["exec"] + COMMAND_ALIASES["exec"] + + help_used = args.index {|a| help_flags.include? a } + exec_used = args.index {|a| exec_commands.include? a } + + command = args.find {|a| bundler_commands.include? a } + + if exec_used && help_used + if exec_used + help_used == 1 + %w[help exec] + else + args + end + elsif help_used + args = args.dup + args.delete_at(help_used) + ["help", command || args].flatten.compact + else + args + end + end + + def self.check_invalid_ext_option(arguments) + # when invalid version of `--ext` is called + if invalid_ext_value?(arguments) + removed_message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension." + raise InvalidOption, removed_message + end + end + + def self.invalid_ext_value?(arguments) + index = arguments.index("--ext") + next_argument = arguments[index + 1] + + # it is ok when --ext is followed with valid extension value + # for example `bundle gem hello --ext c` + return false if EXTENSIONS.include?(next_argument) + + # invalid call when --ext is called with no value in last position + # for example `bundle gem hello_gem --ext` + return true if next_argument.nil? + + # invalid call when --ext is followed by other parameter + # for example `bundle gem --ext --no-ci hello_gem` + return true if next_argument.start_with?("-") + + # invalid call when --ext is followed by gem name + # for example `bundle gem --ext hello_gem` + return true if next_argument + + false + end + + private + + def current_command + _, _, config = @_initializer + config[:current_command] + end + + def invoke_other_command(name) + _, _, config = @_initializer + original_command = config[:current_command] + command = self.class.all_commands[name] + config[:current_command] = command + send(name) + ensure + config[:current_command] = original_command + end + + def current_command=(command) + end + + def print_command + return unless Bundler.ui.debug? + cmd = current_command + command_name = cmd.name + return if PARSEABLE_COMMANDS.include?(command_name) + command = ["bundle", command_name] + args + options_to_print = options.dup + options_to_print.delete_if do |k, v| + next unless o = cmd.options[k] + o.default == v + end + command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip + command.reject!(&:empty?) + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}" + end + + def warn_on_outdated_bundler + return if Bundler.settings[:disable_version_check] + + command_name = current_command.name + return if PARSEABLE_COMMANDS.include?(command_name) + + return unless SharedHelpers.md5_available? + + require_relative "vendored_uri" + remote = Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org")) + cache_path = Bundler.user_cache.join("compact_index", remote.cache_slug) + latest = Bundler::CompactIndexClient.new(cache_path).latest_version("bundler") + return unless latest + + current = Gem::Version.new(VERSION) + return if current >= latest + + Bundler.ui.warn \ + "The latest bundler is #{latest}, but you are currently running #{current}.\n" \ + "To update to the most recent version, run `bundle update --bundler`" + rescue RuntimeError + nil + end + + def remembered_flag_deprecation(name, negative: false, option_name: nil) + option = current_command.options[name] + flag_name = option.switch_name + flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative + return unless flag_passed?(flag_name) + + value = options[name] + value = value.join(" ").to_s if option.type == :array + value = "'#{value}'" unless option.type == :boolean + + print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value) + end + + def print_remembered_flag_deprecation(flag_name, option_name, option_value) + removed_message = + "The `#{flag_name}` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer does. " \ + "Instead please use `bundle config set #{option_name} #{option_value}`, " \ + "and stop using this flag" + raise InvalidOption, removed_message + end + + def flag_passed?(name) + ARGV.any? {|arg| name == arg.split("=")[0] } + end + end +end |
