This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Add
|
||||
attr_reader :gems, :options, :version
|
||||
|
||||
def initialize(options, gems)
|
||||
@gems = gems
|
||||
@options = options
|
||||
@options[:group] = options[:group].split(",").map(&:strip) unless options[:group].nil?
|
||||
@version = options[:version].split(",").map(&:strip) unless options[:version].nil?
|
||||
end
|
||||
|
||||
def run
|
||||
validate_options!
|
||||
inject_dependencies
|
||||
perform_bundle_install unless options["skip-install"]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def perform_bundle_install
|
||||
Installer.install(Bundler.root, Bundler.definition)
|
||||
Bundler.load.cache if Bundler.app_cache.exist?
|
||||
end
|
||||
|
||||
def inject_dependencies
|
||||
dependencies = gems.map {|g| Bundler::Dependency.new(g, version, options) }
|
||||
|
||||
Injector.inject(dependencies,
|
||||
conservative_versioning: options[:version].nil?, # Perform conservative versioning only when version is not specified
|
||||
optimistic: options[:optimistic],
|
||||
strict: options[:strict])
|
||||
end
|
||||
|
||||
def validate_options!
|
||||
raise InvalidOption, "You can not specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic]
|
||||
|
||||
# raise error when no gems are specified
|
||||
raise InvalidOption, "Please specify gems to add." if gems.empty?
|
||||
|
||||
version.to_a.each do |v|
|
||||
raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN.match?(v.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Binstubs
|
||||
attr_reader :options, :gems
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.definition.validate_runtime!
|
||||
path_option = options["path"]
|
||||
path_option = nil if path_option&.empty?
|
||||
Bundler.settings.set_command_option :bin, path_option if options["path"]
|
||||
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
|
||||
installer = Installer.new(Bundler.root, Bundler.definition)
|
||||
|
||||
installer_opts = {
|
||||
force: options[:force],
|
||||
binstubs_cmd: true,
|
||||
all_platforms: options["all-platforms"],
|
||||
}
|
||||
|
||||
if options[:all]
|
||||
raise InvalidOption, "Cannot specify --all with specific gems" unless gems.empty?
|
||||
@gems = Bundler.definition.specs.map(&:name)
|
||||
installer_opts.delete(:binstubs_cmd)
|
||||
elsif 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 options[:standalone]
|
||||
if gem_name == "bundler"
|
||||
Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") unless options[:all]
|
||||
next
|
||||
end
|
||||
|
||||
Bundler.settings.temporary(path: Bundler.settings[:path] || Bundler.root) do
|
||||
installer.generate_standalone_bundler_executable_stubs(spec, installer_opts)
|
||||
end
|
||||
else
|
||||
installer.generate_bundler_executable_stubs(spec, installer_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Cache
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
Bundler.settings.set_command_option_if_given :path, options[:path]
|
||||
Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"]
|
||||
|
||||
setup_cache_all
|
||||
install
|
||||
|
||||
# TODO: move cache contents here now that all bundles are locked
|
||||
custom_path = Bundler.settings[:path] if options[:path]
|
||||
|
||||
Bundler.settings.temporary(cache_all_platforms: options["all-platforms"]) do
|
||||
Bundler.load.cache(custom_path)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def install
|
||||
require_relative "install"
|
||||
options = self.options.dup
|
||||
options["local"] = false if Bundler.settings[:cache_all_platforms]
|
||||
options["no-cache"] = true
|
||||
Bundler::CLI::Install.new(options).run
|
||||
end
|
||||
|
||||
def setup_cache_all
|
||||
all = options.fetch(:all, Bundler.feature_flag.cache_all? || nil)
|
||||
|
||||
Bundler.settings.set_command_option_if_given :cache_all, all
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Check
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.settings.set_command_option_if_given :path, options[:path]
|
||||
|
||||
definition = Bundler.definition
|
||||
definition.validate_runtime!
|
||||
|
||||
begin
|
||||
definition.resolve_only_locally!
|
||||
not_installed = definition.missing_specs
|
||||
rescue GemNotFound, SolveFailure
|
||||
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.frozen_bundle?
|
||||
Bundler.ui.error "This bundle has been frozen, but there is no #{SharedHelpers.relative_lockfile_path} 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
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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
|
||||
return unless Bundler.use_system_gems? && !options[:force]
|
||||
raise InvalidOption, "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`."
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,138 @@
|
||||
# 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_fund_metadata_summary
|
||||
return if Bundler.settings["ignore_funding_requests"]
|
||||
definition = Bundler.definition
|
||||
current_dependencies = definition.requested_dependencies
|
||||
current_specs = definition.specs
|
||||
|
||||
count = current_dependencies.count {|dep| current_specs[dep.name].first.metadata.key?("funding_uri") }
|
||||
|
||||
return if count.zero?
|
||||
|
||||
intro = count > 1 ? "#{count} installed gems you directly depend on are" : "#{count} installed gem you directly depend on is"
|
||||
message = "#{intro} looking for funding.\n Run `bundle fund` for details"
|
||||
Bundler.ui.info message
|
||||
end
|
||||
|
||||
def self.output_without_groups_message(command)
|
||||
return if Bundler.settings[:without].empty?
|
||||
Bundler.ui.confirm without_groups_message(command)
|
||||
end
|
||||
|
||||
def self.without_groups_message(command)
|
||||
command_in_past_tense = command == :install ? "installed" : "updated"
|
||||
groups = Bundler.settings[:without]
|
||||
"Gems in the #{verbalize_groups(groups)} were not #{command_in_past_tense}."
|
||||
end
|
||||
|
||||
def self.verbalize_groups(groups)
|
||||
groups.map! {|g| "'#{g}'" }
|
||||
group_list = [groups[0...-1].join(", "), groups[-1..-1]].
|
||||
reject {|s| s.to_s.empty? }.join(" and ")
|
||||
group_str = groups.size == 1 ? "group" : "groups"
|
||||
"#{group_str} #{group_list}"
|
||||
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.match?(regexp)
|
||||
end
|
||||
|
||||
default_spec = default_gem_spec(name)
|
||||
specs << default_spec if default_spec
|
||||
|
||||
case specs.count
|
||||
when 0
|
||||
dep_in_other_group = Bundler.definition.current_dependencies.find {|dep|dep.name == name }
|
||||
|
||||
if dep_in_other_group
|
||||
raise GemNotFound, "Could not find gem '#{name}', because it's in the #{verbalize_groups(dep_in_other_group.groups)}, configured to be ignored."
|
||||
else
|
||||
raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
|
||||
end
|
||||
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.default_gem_spec(name)
|
||||
gem_spec = Gem::Specification.find_all_by_name(name).last
|
||||
gem_spec if gem_spec&.default_gem?
|
||||
end
|
||||
|
||||
def self.ask_for_spec_from(specs)
|
||||
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_relative "../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)
|
||||
return unless locked_gems
|
||||
|
||||
locked_names = locked_gems.specs.map(&:name).uniq
|
||||
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)
|
||||
patch_level << :patch if patch_level.empty? && Bundler.settings[:prefer_patch]
|
||||
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["filter-strict"]
|
||||
gvp.pre = options[:pre]
|
||||
end
|
||||
end
|
||||
|
||||
def self.patch_level_options(options)
|
||||
[:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
|
||||
end
|
||||
|
||||
def self.clean_after_install?
|
||||
clean = Bundler.settings[:clean]
|
||||
return clean unless clean.nil?
|
||||
clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil?
|
||||
clean &&= !Bundler.use_system_gems?
|
||||
clean
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,204 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Config < Thor
|
||||
class_option :parseable, type: :boolean, banner: "Use minimal formatting for more parseable output"
|
||||
|
||||
def self.scope_options
|
||||
method_option :global, type: :boolean, banner: "Only change the global config"
|
||||
method_option :local, type: :boolean, banner: "Only change the local config"
|
||||
end
|
||||
private_class_method :scope_options
|
||||
|
||||
desc "base NAME [VALUE]", "The Bundler 1 config interface", hide: true
|
||||
scope_options
|
||||
method_option :delete, type: :boolean, banner: "delete"
|
||||
def base(name = nil, *value)
|
||||
new_args =
|
||||
if ARGV.size == 1
|
||||
["config", "list"]
|
||||
elsif ARGV.include?("--delete")
|
||||
ARGV.map {|arg| arg == "--delete" ? "unset" : arg }
|
||||
elsif ARGV.include?("--global") || ARGV.include?("--local") || ARGV.size == 3
|
||||
["config", "set", *ARGV[1..-1]]
|
||||
else
|
||||
["config", "get", ARGV[1]]
|
||||
end
|
||||
|
||||
message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead."
|
||||
removed_message = "Using the `config` command without a subcommand [list, get, set, unset] is has been removed. Use `bundle #{new_args.join(" ")}` instead."
|
||||
SharedHelpers.major_deprecation 3, message, removed_message: removed_message
|
||||
|
||||
Base.new(options, name, value, self).run
|
||||
end
|
||||
|
||||
desc "list", "List out all configured settings"
|
||||
def list
|
||||
Base.new(options, nil, nil, self).run
|
||||
end
|
||||
|
||||
desc "get NAME", "Returns the value for the given key"
|
||||
def get(name)
|
||||
Base.new(options, name, nil, self).run
|
||||
end
|
||||
|
||||
desc "set NAME VALUE", "Sets the given value for the given key"
|
||||
scope_options
|
||||
def set(name, value, *value_)
|
||||
Base.new(options, name, value_.unshift(value), self).run
|
||||
end
|
||||
|
||||
desc "unset NAME", "Unsets the value for the given key"
|
||||
scope_options
|
||||
def unset(name)
|
||||
options[:delete] = true
|
||||
Base.new(options, name, nil, self).run
|
||||
end
|
||||
|
||||
default_task :base
|
||||
|
||||
class Base
|
||||
attr_reader :name, :value, :options, :scope, :thor
|
||||
|
||||
def initialize(options, name, value, thor)
|
||||
@options = options
|
||||
@name = name
|
||||
value = Array(value)
|
||||
@value = value.empty? ? nil : value.join(" ")
|
||||
@thor = thor
|
||||
validate_scope!
|
||||
end
|
||||
|
||||
def run
|
||||
unless name
|
||||
warn_unused_scope "Ignoring --#{scope}"
|
||||
confirm_all
|
||||
return
|
||||
end
|
||||
|
||||
if options[:delete]
|
||||
if !explicit_scope? || scope != "global"
|
||||
Bundler.settings.set_local(name, nil)
|
||||
end
|
||||
if !explicit_scope? || scope != "local"
|
||||
Bundler.settings.set_global(name, nil)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if value.nil?
|
||||
warn_unused_scope "Ignoring --#{scope} since no value to set was given"
|
||||
|
||||
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
|
||||
|
||||
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(value)
|
||||
if name.start_with?("local.") && pathname.directory?
|
||||
pathname.expand_path.to_s
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def message
|
||||
locations = Bundler.settings.locations(name)
|
||||
if @options[:parseable]
|
||||
"#{name}=#{new_value}" if new_value
|
||||
elsif scope == "global"
|
||||
if !locations[:local].nil?
|
||||
"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].nil? && locations[:global] != value
|
||||
"You are replacing the current global value of #{name}, which is currently " \
|
||||
"#{locations[:global].inspect}"
|
||||
end
|
||||
elsif scope == "local" && !locations[:local].nil? && locations[:local] != value
|
||||
"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 explicit_scope?
|
||||
@explicit_scope
|
||||
end
|
||||
|
||||
def warn_unused_scope(msg)
|
||||
return unless explicit_scope?
|
||||
return if options[:parseable]
|
||||
|
||||
Bundler.ui.warn(msg)
|
||||
end
|
||||
|
||||
def validate_scope!
|
||||
@explicit_scope = true
|
||||
scopes = %w[global local].select {|s| options[s] }
|
||||
case scopes.size
|
||||
when 0
|
||||
@scope = inside_app? ? "local" : "global"
|
||||
@explicit_scope = false
|
||||
when 1
|
||||
@scope = scopes.first
|
||||
else
|
||||
raise InvalidOption,
|
||||
"The options #{scopes.join " and "} were specified. Please only use one of the switches at a time."
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inside_app?
|
||||
Bundler.root
|
||||
true
|
||||
rescue GemfileNotFound
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Console
|
||||
attr_reader :options, :group
|
||||
def initialize(options, group)
|
||||
@options = options
|
||||
@group = group
|
||||
end
|
||||
|
||||
def run
|
||||
message = "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`"
|
||||
removed_message = "bundle console has been replaced by `bin/console` generated by `bundle gem <name>`"
|
||||
Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message
|
||||
|
||||
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
|
||||
@@ -0,0 +1,157 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
require "shellwords"
|
||||
require "fiddle"
|
||||
|
||||
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.shellescape}`.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.shellescape}`.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_relative "check"
|
||||
Bundler::CLI::Check.new({}).run
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
Bundler.settings.validate!
|
||||
check!
|
||||
|
||||
definition = Bundler.definition
|
||||
broken_links = {}
|
||||
|
||||
definition.specs.each do |spec|
|
||||
bundles_for_gem(spec).each do |bundle|
|
||||
bad_paths = dylibs(bundle).select do |f|
|
||||
Fiddle.dlopen(f)
|
||||
false
|
||||
rescue Fiddle::DLError
|
||||
true
|
||||
end
|
||||
if bad_paths.any?
|
||||
broken_links[spec] ||= []
|
||||
broken_links[spec].concat(bad_paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
permissions_valid = check_home_permissions
|
||||
|
||||
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
|
||||
elsif !permissions_valid
|
||||
Bundler.ui.info "No issues found with the installed bundle"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_home_permissions
|
||||
require "find"
|
||||
files_not_readable_or_writable = []
|
||||
files_not_rw_and_owned_by_different_user = []
|
||||
files_not_owned_by_current_user_but_still_rw = []
|
||||
broken_symlinks = []
|
||||
Find.find(Bundler.bundle_path.to_s).each do |f|
|
||||
if !File.exist?(f)
|
||||
broken_symlinks << f
|
||||
elsif !File.writable?(f) || !File.readable?(f)
|
||||
if File.stat(f).uid != Process.uid
|
||||
files_not_rw_and_owned_by_different_user << f
|
||||
else
|
||||
files_not_readable_or_writable << f
|
||||
end
|
||||
elsif File.stat(f).uid != Process.uid
|
||||
files_not_owned_by_current_user_but_still_rw << f
|
||||
end
|
||||
end
|
||||
|
||||
ok = true
|
||||
|
||||
if broken_symlinks.any?
|
||||
Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_owned_by_current_user_but_still_rw.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
|
||||
"user, but are still readable/writable. These files are:\n - #{files_not_owned_by_current_user_but_still_rw.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_rw_and_owned_by_different_user.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
|
||||
"user, and are not readable/writable. These files are:\n - #{files_not_rw_and_owned_by_different_user.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
if files_not_readable_or_writable.any?
|
||||
Bundler.ui.warn "Files exist in the Bundler home that are not " \
|
||||
"readable/writable by the current user. These files are:\n - #{files_not_readable_or_writable.join("\n - ")}"
|
||||
|
||||
ok = false
|
||||
end
|
||||
|
||||
ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../current_ruby"
|
||||
|
||||
module Bundler
|
||||
class CLI::Exec
|
||||
attr_reader :options, :args, :cmd
|
||||
|
||||
TRAPPED_SIGNALS = %w[INT].freeze
|
||||
|
||||
def initialize(options, args)
|
||||
@options = options
|
||||
@cmd = args.shift
|
||||
@args = args
|
||||
@args << { close_others: !options.keep_file_descriptors? } unless Bundler.current_ruby.jruby?
|
||||
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
|
||||
kernel_exec(bin_path, *args)
|
||||
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)
|
||||
Kernel.exec(*args)
|
||||
rescue Errno::EACCES, Errno::ENOEXEC
|
||||
Bundler.ui.error "bundler: not executable: #{cmd}"
|
||||
exit 126
|
||||
rescue Errno::ENOENT
|
||||
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)
|
||||
require_relative "../setup"
|
||||
TRAPPED_SIGNALS.each {|s| trap(s, "DEFAULT") }
|
||||
Kernel.load(file)
|
||||
rescue SystemExit, SignalException
|
||||
raise
|
||||
rescue Exception # rubocop:disable Lint/RescueException
|
||||
Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
|
||||
Bundler::FriendlyErrors.disable!
|
||||
raise
|
||||
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",
|
||||
"#!/usr/bin/env truffleruby\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
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Fund
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.definition.validate_runtime!
|
||||
|
||||
groups = Array(options[:group]).map(&:to_sym)
|
||||
|
||||
deps = if groups.any?
|
||||
Bundler.definition.dependencies_for(groups)
|
||||
else
|
||||
Bundler.definition.current_dependencies
|
||||
end
|
||||
|
||||
fund_info = deps.each_with_object([]) do |dep, arr|
|
||||
spec = Bundler.definition.specs[dep.name].first
|
||||
if spec.metadata.key?("funding_uri")
|
||||
arr << "* #{spec.name} (#{spec.version})\n Funding: #{spec.metadata["funding_uri"]}"
|
||||
end
|
||||
end
|
||||
|
||||
if fund_info.empty?
|
||||
Bundler.ui.info "None of the installed gems you directly depend on are looking for funding."
|
||||
else
|
||||
Bundler.ui.info fund_info.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,471 @@
|
||||
# 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.16",
|
||||
"test-unit" => "3.0",
|
||||
}.freeze
|
||||
|
||||
attr_reader :options, :gem_name, :thor, :name, :target, :extension
|
||||
|
||||
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)
|
||||
|
||||
@extension = options[:ext]
|
||||
|
||||
validate_ext_name if @extension
|
||||
validate_rust_builder_rubygems_version if @extension == "rust"
|
||||
travis_removal_info
|
||||
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("::")
|
||||
minitest_constant_name = constant_array.clone.tap {|a| a[-1] = "Test#{a[-1]}" }.join("::") # Foo::Bar => Foo::TestBar
|
||||
|
||||
use_git = Bundler.git_present? && options[:git]
|
||||
|
||||
git_author_name = use_git ? `git config user.name`.chomp : ""
|
||||
git_username = use_git ? `git config github.user`.chomp : ""
|
||||
git_user_email = use_git ? `git config user.email`.chomp : ""
|
||||
|
||||
github_username = if options[:github_username].nil?
|
||||
git_username
|
||||
elsif options[:github_username] == false
|
||||
""
|
||||
else
|
||||
options[:github_username]
|
||||
end
|
||||
|
||||
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: extension,
|
||||
exe: options[:exe],
|
||||
bundler_version: bundler_dependency_version,
|
||||
git: use_git,
|
||||
github_username: github_username.empty? ? "[USERNAME]" : github_username,
|
||||
required_ruby_version: required_ruby_version,
|
||||
rust_builder_required_rubygems_version: rust_builder_required_rubygems_version,
|
||||
minitest_constant_name: minitest_constant_name,
|
||||
}
|
||||
ensure_safe_gem_name(name, constant_array)
|
||||
|
||||
templates = {
|
||||
"#{Bundler.preferred_gemfile_name}.tt" => Bundler.preferred_gemfile_name,
|
||||
"lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
|
||||
"lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
|
||||
"sig/newgem.rbs.tt" => "sig/#{namespaced_path}.rbs",
|
||||
"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 use_git
|
||||
|
||||
if test_framework = ask_and_set_test_framework
|
||||
config[:test] = test_framework
|
||||
config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework]
|
||||
|
||||
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"
|
||||
)
|
||||
config[:test_task] = :spec
|
||||
when "minitest"
|
||||
# Generate path for minitest target file (FileList["test/**/test_*.rb"])
|
||||
# foo => test/test_foo.rb
|
||||
# foo-bar => test/foo/test_bar.rb
|
||||
# foo_bar => test/test_foo_bar.rb
|
||||
paths = namespaced_path.rpartition("/")
|
||||
paths[2] = "test_#{paths[2]}"
|
||||
minitest_namespaced_path = paths.join("")
|
||||
|
||||
templates.merge!(
|
||||
"test/minitest/test_helper.rb.tt" => "test/test_helper.rb",
|
||||
"test/minitest/test_newgem.rb.tt" => "test/#{minitest_namespaced_path}.rb"
|
||||
)
|
||||
config[:test_task] = :test
|
||||
when "test-unit"
|
||||
templates.merge!(
|
||||
"test/test-unit/test_helper.rb.tt" => "test/test_helper.rb",
|
||||
"test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb"
|
||||
)
|
||||
config[:test_task] = :test
|
||||
end
|
||||
end
|
||||
|
||||
config[:ci] = ask_and_set_ci
|
||||
case config[:ci]
|
||||
when "github"
|
||||
templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml")
|
||||
config[:ci_config_path] = ".github "
|
||||
when "gitlab"
|
||||
templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml")
|
||||
config[:ci_config_path] = ".gitlab-ci.yml "
|
||||
when "circle"
|
||||
templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml")
|
||||
config[:ci_config_path] = ".circleci "
|
||||
end
|
||||
|
||||
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 https://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 https://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
|
||||
|
||||
if ask_and_set(:changelog, "Do you want to include a changelog?",
|
||||
"A changelog is a file which contains a curated, chronologically ordered list of notable " \
|
||||
"changes for each version of a project. To make it easier for users and contributors to" \
|
||||
" see precisely what notable changes have been made between each release (or version) of" \
|
||||
" the project. Whether consumers or developers, the end users of software are" \
|
||||
" human beings who care about what's in the software. When the software changes, people " \
|
||||
"want to know why and how. see https://keepachangelog.com")
|
||||
config[:changelog] = true
|
||||
Bundler.ui.info "Changelog enabled in config"
|
||||
templates.merge!("CHANGELOG.md.tt" => "CHANGELOG.md")
|
||||
end
|
||||
|
||||
config[:linter] = ask_and_set_linter
|
||||
case config[:linter]
|
||||
when "rubocop"
|
||||
config[:linter_version] = rubocop_version
|
||||
Bundler.ui.info "RuboCop enabled in config"
|
||||
templates.merge!("rubocop.yml.tt" => ".rubocop.yml")
|
||||
when "standard"
|
||||
config[:linter_version] = standard_version
|
||||
Bundler.ui.info "Standard enabled in config"
|
||||
templates.merge!("standard.yml.tt" => ".standard.yml")
|
||||
end
|
||||
|
||||
templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe]
|
||||
|
||||
if extension == "c"
|
||||
templates.merge!(
|
||||
"ext/newgem/extconf-c.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
|
||||
|
||||
if extension == "rust"
|
||||
templates.merge!(
|
||||
"Cargo.toml.tt" => "Cargo.toml",
|
||||
"ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml",
|
||||
"ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb",
|
||||
"ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs",
|
||||
)
|
||||
end
|
||||
|
||||
if target.exist? && !target.directory?
|
||||
Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`."
|
||||
exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError]
|
||||
end
|
||||
|
||||
if use_git
|
||||
Bundler.ui.info "Initializing git repo in #{target}"
|
||||
require "shellwords"
|
||||
`git init #{target.to_s.shellescape}`
|
||||
|
||||
config[:git_default_branch] = File.read("#{target}/.git/HEAD").split("/").last.chomp
|
||||
end
|
||||
|
||||
templates.each do |src, dst|
|
||||
destination = target.join(dst)
|
||||
thor.template("newgem/#{src}", destination, config)
|
||||
end
|
||||
|
||||
executables.each do |file|
|
||||
path = target.join(file)
|
||||
executable = (path.stat.mode | 0o111)
|
||||
path.chmod(executable)
|
||||
end
|
||||
|
||||
if use_git
|
||||
IO.popen(%w[git add .], { chdir: target }, &:read)
|
||||
end
|
||||
|
||||
# Open gemspec in editor
|
||||
open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
|
||||
|
||||
Bundler.ui.info "Gem '#{name}' was successfully created. " \
|
||||
"For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html"
|
||||
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" \
|
||||
"https://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.to_s.empty?
|
||||
Bundler.ui.confirm "Do you want to generate tests with your gem?"
|
||||
Bundler.ui.info hint_text("test")
|
||||
|
||||
result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):"
|
||||
if /rspec|minitest|test-unit/.match?(result)
|
||||
test_framework = result
|
||||
else
|
||||
test_framework = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.test"].nil?
|
||||
Bundler.settings.set_global("gem.test", test_framework)
|
||||
end
|
||||
|
||||
if options[:test] == Bundler.settings["gem.test"]
|
||||
Bundler.ui.info "#{options[:test]} is already configured, ignoring --test flag."
|
||||
end
|
||||
|
||||
test_framework
|
||||
end
|
||||
|
||||
def hint_text(setting)
|
||||
if Bundler.settings["gem.#{setting}"] == false
|
||||
"Your choice will only be applied to this gem."
|
||||
else
|
||||
"Future `bundle gem` calls will use your choice. " \
|
||||
"This setting can be changed anytime with `bundle config gem.#{setting}`."
|
||||
end
|
||||
end
|
||||
|
||||
def ask_and_set_ci
|
||||
ci_template = options[:ci] || Bundler.settings["gem.ci"]
|
||||
|
||||
if ci_template.to_s.empty?
|
||||
Bundler.ui.confirm "Do you want to set up continuous integration for your gem? " \
|
||||
"Supported services:\n" \
|
||||
"* CircleCI: https://circleci.com/\n" \
|
||||
"* GitHub Actions: https://github.com/features/actions\n" \
|
||||
"* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \
|
||||
"\n"
|
||||
Bundler.ui.info hint_text("ci")
|
||||
|
||||
result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):"
|
||||
if /github|gitlab|circle/.match?(result)
|
||||
ci_template = result
|
||||
else
|
||||
ci_template = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.ci"].nil?
|
||||
Bundler.settings.set_global("gem.ci", ci_template)
|
||||
end
|
||||
|
||||
if options[:ci] == Bundler.settings["gem.ci"]
|
||||
Bundler.ui.info "#{options[:ci]} is already configured, ignoring --ci flag."
|
||||
end
|
||||
|
||||
ci_template
|
||||
end
|
||||
|
||||
def ask_and_set_linter
|
||||
linter_template = options[:linter] || Bundler.settings["gem.linter"]
|
||||
linter_template = deprecated_rubocop_option if linter_template.nil?
|
||||
|
||||
if linter_template.to_s.empty?
|
||||
Bundler.ui.confirm "Do you want to add a code linter and formatter to your gem? " \
|
||||
"Supported Linters:\n" \
|
||||
"* RuboCop: https://rubocop.org\n" \
|
||||
"* Standard: https://github.com/standardrb/standard\n" \
|
||||
"\n"
|
||||
Bundler.ui.info hint_text("linter")
|
||||
|
||||
result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):"
|
||||
if /rubocop|standard/.match?(result)
|
||||
linter_template = result
|
||||
else
|
||||
linter_template = false
|
||||
end
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.linter"].nil?
|
||||
Bundler.settings.set_global("gem.linter", linter_template)
|
||||
end
|
||||
|
||||
# Once gem.linter safely set, unset the deprecated gem.rubocop
|
||||
unless Bundler.settings["gem.rubocop"].nil?
|
||||
Bundler.settings.set_global("gem.rubocop", nil)
|
||||
end
|
||||
|
||||
if options[:linter] == Bundler.settings["gem.linter"]
|
||||
Bundler.ui.info "#{options[:linter]} is already configured, ignoring --linter flag."
|
||||
end
|
||||
|
||||
linter_template
|
||||
end
|
||||
|
||||
def deprecated_rubocop_option
|
||||
if !options[:rubocop].nil?
|
||||
if options[:rubocop]
|
||||
Bundler::SharedHelpers.major_deprecation 2,
|
||||
"--rubocop is deprecated, use --linter=rubocop",
|
||||
removed_message: "--rubocop has been removed, use --linter=rubocop"
|
||||
"rubocop"
|
||||
else
|
||||
Bundler::SharedHelpers.major_deprecation 2,
|
||||
"--no-rubocop is deprecated, use --linter",
|
||||
removed_message: "--no-rubocop has been removed, use --linter"
|
||||
false
|
||||
end
|
||||
elsif !Bundler.settings["gem.rubocop"].nil?
|
||||
Bundler::SharedHelpers.major_deprecation 2,
|
||||
"config gem.rubocop is deprecated; we've updated your config to use gem.linter instead",
|
||||
removed_message: "config gem.rubocop has been removed; we've updated your config to use gem.linter instead"
|
||||
Bundler.settings["gem.rubocop"] ? "rubocop" : false
|
||||
end
|
||||
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 /^\d/.match?(name)
|
||||
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
|
||||
|
||||
def rust_builder_required_rubygems_version
|
||||
"3.3.11"
|
||||
end
|
||||
|
||||
def required_ruby_version
|
||||
"3.0.0"
|
||||
end
|
||||
|
||||
def rubocop_version
|
||||
"1.21"
|
||||
end
|
||||
|
||||
def standard_version
|
||||
"1.3"
|
||||
end
|
||||
|
||||
# TODO: remove at next minor release
|
||||
def travis_removal_info
|
||||
if options[:ci] == "travis"
|
||||
Bundler.ui.error "Support for Travis CI was removed from gem skeleton generator."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if Bundler.settings["gem.ci"] == "travis"
|
||||
Bundler.ui.error "Support for Travis CI was removed from gem skeleton generator, but it is present in bundle config. Please configure another provider using `bundle config set gem.ci SERVICE` (where SERVICE is one of github/gitlab/circle) or unset configuration using `bundle config unset gem.ci`."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def validate_rust_builder_rubygems_version
|
||||
if Gem::Version.new(rust_builder_required_rubygems_version) > Gem.rubygems_version
|
||||
Bundler.ui.error "Your RubyGems version (#{Gem.rubygems_version}) is too old to build Rust extension. Please update your RubyGems using `gem update --system` or any other way and try again."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Info
|
||||
attr_reader :gem_name, :options
|
||||
def initialize(options, gem_name)
|
||||
@options = options
|
||||
@gem_name = gem_name
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.silence do
|
||||
Bundler.definition.validate_runtime!
|
||||
Bundler.load.lock
|
||||
end
|
||||
|
||||
spec = spec_for_gem(gem_name)
|
||||
|
||||
if spec
|
||||
return print_gem_path(spec) if @options[:path]
|
||||
return print_gem_version(spec) if @options[:version]
|
||||
print_gem_info(spec)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spec_for_gem(name)
|
||||
Bundler::CLI::Common.select_spec(name, :regex_match)
|
||||
end
|
||||
|
||||
def print_gem_version(spec)
|
||||
Bundler.ui.info spec.version.to_s
|
||||
end
|
||||
|
||||
def print_gem_path(spec)
|
||||
name = spec.name
|
||||
if name == "bundler"
|
||||
path = File.expand_path("../../..", __dir__)
|
||||
else
|
||||
path = spec.full_gem_path
|
||||
if spec.deleted_gem?
|
||||
return Bundler.ui.warn "The gem #{name} has been deleted. It was installed at: #{path}"
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.info path
|
||||
end
|
||||
|
||||
def print_gem_info(spec)
|
||||
metadata = spec.metadata
|
||||
name = spec.name
|
||||
gem_info = String.new
|
||||
gem_info << " * #{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 << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri")
|
||||
gem_info << "\tSource Code: #{metadata["source_code_uri"]}\n" if metadata.key?("source_code_uri")
|
||||
gem_info << "\tFunding: #{metadata["funding_uri"]}\n" if metadata.key?("funding_uri")
|
||||
gem_info << "\tWiki: #{metadata["wiki_uri"]}\n" if metadata.key?("wiki_uri")
|
||||
gem_info << "\tChangelog: #{metadata["changelog_uri"]}\n" if metadata.key?("changelog_uri")
|
||||
gem_info << "\tBug Tracker: #{metadata["bug_tracker_uri"]}\n" if metadata.key?("bug_tracker_uri")
|
||||
gem_info << "\tMailing List: #{metadata["mailing_list_uri"]}\n" if metadata.key?("mailing_list_uri")
|
||||
gem_info << "\tPath: #{spec.full_gem_path}\n"
|
||||
gem_info << "\tDefault Gem: yes\n" if spec.respond_to?(:default_gem?) && spec.default_gem?
|
||||
gem_info << "\tReverse Dependencies: \n\t\t#{gem_dependencies.join("\n\t\t")}" if gem_dependencies.any?
|
||||
|
||||
if name != "bundler" && spec.deleted_gem?
|
||||
return Bundler.ui.warn "The gem #{name} has been deleted. Gemspec information is still available though:\n#{gem_info}"
|
||||
end
|
||||
|
||||
Bundler.ui.info gem_info
|
||||
end
|
||||
|
||||
def gem_dependencies
|
||||
@gem_dependencies ||= Bundler.definition.specs.map do |spec|
|
||||
dependency = spec.dependencies.find {|dep| dep.name == gem_name }
|
||||
next unless dependency
|
||||
"#{spec.name} (#{spec.version}) depends on #{gem_name} (#{dependency.requirements_list.join(", ")})"
|
||||
end.compact.sort
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 #{File.expand_path(gemfile)}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless File.writable?(Dir.pwd)
|
||||
Bundler.ui.error "Can not create #{gemfile} as the current directory is not writable."
|
||||
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)
|
||||
|
||||
File.open(gemfile, "wb") do |file|
|
||||
file << "# Generated from #{gemspec}\n"
|
||||
file << spec.to_gemfile
|
||||
end
|
||||
else
|
||||
File.open(File.expand_path("../templates/Gemfile", __dir__), "r") do |template|
|
||||
File.open(gemfile, "wb") do |destination|
|
||||
IO.copy_stream(template, destination)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gemfile
|
||||
@gemfile ||= options[:gemfile] || Bundler.preferred_gemfile_name
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
# 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
|
||||
@@ -0,0 +1,190 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Install
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
|
||||
warn_if_root
|
||||
|
||||
Bundler.self_manager.install_locked_bundler_and_restart_with_it_if_needed
|
||||
|
||||
Bundler::SharedHelpers.set_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] || Bundler.frozen_bundle?
|
||||
unless Bundler.default_lockfile.exist?
|
||||
flag = "--deployment flag" if options[:deployment]
|
||||
flag ||= "--frozen flag" if options[:frozen]
|
||||
flag ||= "deployment setting"
|
||||
raise ProductionError, "The #{flag} requires a lockfile. Please make " \
|
||||
"sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \
|
||||
"before deploying."
|
||||
end
|
||||
|
||||
options[:local] = true if Bundler.app_cache.exist?
|
||||
|
||||
Bundler.settings.set_command_option :deployment, true if options[:deployment]
|
||||
Bundler.settings.set_command_option :frozen, true if options[:frozen]
|
||||
end
|
||||
|
||||
# When install is called with --no-deployment, disable deployment mode
|
||||
if options[:deployment] == false
|
||||
Bundler.settings.set_command_option :frozen, nil
|
||||
options[:system] = true
|
||||
end
|
||||
|
||||
normalize_settings
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
if options["binstubs"]
|
||||
Bundler::SharedHelpers.major_deprecation 2,
|
||||
"The --binstubs option will be removed in favor of `bundle binstubs --all`",
|
||||
removed_message: "The --binstubs option have been removed in favor of `bundle binstubs --all`"
|
||||
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.settings.temporary(cache_all_platforms: options[:local] ? false : Bundler.settings[:cache_all_platforms]) do
|
||||
Bundler.load.cache(nil, options[:local]) if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle?
|
||||
end
|
||||
|
||||
Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}."
|
||||
Bundler::CLI::Common.output_without_groups_message(:install)
|
||||
|
||||
if Bundler.use_system_gems?
|
||||
Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed."
|
||||
else
|
||||
relative_path = Bundler.configured_bundle_path.base_path_relative_to_pwd
|
||||
Bundler.ui.confirm "Bundled gems are installed into `#{relative_path}`"
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
|
||||
|
||||
warn_ambiguous_gems
|
||||
|
||||
if CLI::Common.clean_after_install?
|
||||
require_relative "clean"
|
||||
Bundler::CLI::Clean.new(options).run
|
||||
end
|
||||
|
||||
Bundler::CLI::Common.output_fund_metadata_summary
|
||||
rescue Gem::InvalidSpecificationException
|
||||
Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed."
|
||||
raise
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def warn_if_root
|
||||
return if Bundler.settings[:silence_root_warning] || Gem.win_platform? || !Process.uid.zero?
|
||||
Bundler.ui.warn "Don't run Bundler as root. 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_in_cli_options
|
||||
conflicting_groups = Array(options[:without]) & Array(options[:with])
|
||||
return if conflicting_groups.empty?
|
||||
raise InvalidOption, "You can't list a group in both with and without." \
|
||||
" The offending groups are: #{conflicting_groups.join(", ")}."
|
||||
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
|
||||
trust_policy = options["trust-policy"]
|
||||
unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy)
|
||||
raise InvalidOption, "RubyGems doesn't know about trust policy '#{trust_policy}'. " \
|
||||
"The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}."
|
||||
end
|
||||
Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy
|
||||
end
|
||||
|
||||
def normalize_groups
|
||||
check_for_group_conflicts_in_cli_options
|
||||
|
||||
# need to nil them out first to get around validation for backwards compatibility
|
||||
Bundler.settings.set_command_option :without, nil
|
||||
Bundler.settings.set_command_option :with, nil
|
||||
Bundler.settings.set_command_option :without, options[:without]
|
||||
Bundler.settings.set_command_option :with, options[:with]
|
||||
end
|
||||
|
||||
def normalize_settings
|
||||
Bundler.settings.set_command_option :path, nil if options[:system]
|
||||
Bundler.settings.set_command_option_if_given :path, options[:path]
|
||||
|
||||
if options["standalone"] && Bundler.settings[:path].nil? && !options["local"]
|
||||
Bundler.settings.temporary(path_relative_to_cwd: false) do
|
||||
Bundler.settings.set_command_option :path, "bundle"
|
||||
end
|
||||
end
|
||||
|
||||
bin_option = options["binstubs"]
|
||||
bin_option = nil if bin_option&.empty?
|
||||
Bundler.settings.set_command_option :bin, bin_option if options["binstubs"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :no_install, options["no-install"]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :clean, options["clean"]
|
||||
|
||||
normalize_groups if options[:without] || options[:with]
|
||||
|
||||
options[:force] = options[:redownload]
|
||||
end
|
||||
|
||||
def warn_ambiguous_gems
|
||||
# TODO: remove this when we drop Bundler 1.x support
|
||||
Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris|
|
||||
Bundler.ui.warn "Warning: the gem '#{name}' was found in multiple sources."
|
||||
Bundler.ui.warn "Installed from: #{installed_from_uri}"
|
||||
Bundler.ui.warn "Also found in:"
|
||||
also_found_in_uris.each {|uri| Bundler.ui.warn " * #{uri}" }
|
||||
Bundler.ui.warn "You should add a source requirement to restrict this gem to your preferred source."
|
||||
Bundler.ui.warn "For example:"
|
||||
Bundler.ui.warn " gem '#{name}', :source => '#{installed_from_uri}'"
|
||||
Bundler.ui.warn "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rbconfig"
|
||||
|
||||
module Bundler
|
||||
class CLI::Issue
|
||||
def run
|
||||
Bundler.ui.info <<~EOS
|
||||
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/rubygems/rubygems/blob/master/bundler/doc/TROUBLESHOOTING.md
|
||||
|
||||
2. Instructions for common Bundler uses can be found on the documentation
|
||||
site: https://bundler.io/
|
||||
|
||||
3. Information about each Bundler command can be found in the Bundler
|
||||
man pages: https://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, by filling
|
||||
in the new issue form located at
|
||||
https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md,
|
||||
and copy and pasting the information below.
|
||||
|
||||
EOS
|
||||
|
||||
Bundler.ui.info Bundler::Env.report
|
||||
|
||||
Bundler.ui.info "\n## Bundle Doctor"
|
||||
doctor
|
||||
end
|
||||
|
||||
def doctor
|
||||
require_relative "doctor"
|
||||
Bundler::CLI::Doctor.new({}).run
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::List
|
||||
def initialize(options)
|
||||
@options = options
|
||||
@without_group = options["without-group"].map(&:to_sym)
|
||||
@only_group = options["only-group"].map(&:to_sym)
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "The `--only-group` and `--without-group` options cannot be used together" if @only_group.any? && @without_group.any?
|
||||
|
||||
raise InvalidOption, "The `--name-only` and `--paths` options cannot be used together" if @options["name-only"] && @options[:paths]
|
||||
|
||||
specs = if @only_group.any? || @without_group.any?
|
||||
filtered_specs_by_groups
|
||||
else
|
||||
begin
|
||||
Bundler.load.specs
|
||||
rescue GemNotFound => e
|
||||
Bundler.ui.error e.message
|
||||
Bundler.ui.warn "Install missing gems with `bundle install`."
|
||||
exit 1
|
||||
end
|
||||
end.reject {|s| s.name == "bundler" }.sort_by(&:name)
|
||||
|
||||
return Bundler.ui.info "No gems in the Gemfile" if specs.empty?
|
||||
|
||||
return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"]
|
||||
return specs.each {|s| Bundler.ui.info s.full_gem_path } if @options["paths"]
|
||||
|
||||
Bundler.ui.info "Gems included by the bundle:"
|
||||
|
||||
specs.each {|s| Bundler.ui.info " * #{s.name} (#{s.version}#{s.git_version})" }
|
||||
|
||||
Bundler.ui.info "Use `bundle info` to print more detailed information about a gem"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_group_exists(groups)
|
||||
(@without_group + @only_group).each do |group|
|
||||
raise InvalidOption, "`#{group}` group could not be found." unless groups.include?(group)
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_specs_by_groups
|
||||
definition = Bundler.definition
|
||||
groups = definition.groups
|
||||
|
||||
verify_group_exists(groups)
|
||||
|
||||
show_groups =
|
||||
if @without_group.any?
|
||||
groups.reject {|g| @without_group.include?(g) }
|
||||
elsif @only_group.any?
|
||||
groups.select {|g| @only_group.include?(g) }
|
||||
else
|
||||
groups
|
||||
end.map(&:to_sym)
|
||||
|
||||
definition.specs_for(show_groups)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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]
|
||||
previous_ui_level = Bundler.ui.level
|
||||
Bundler.ui.level = "silent" if print
|
||||
|
||||
Bundler::Fetcher.disable_endpoint = options["full-index"]
|
||||
|
||||
update = options[:update]
|
||||
conservative = options[:conservative]
|
||||
bundler = options[:bundler]
|
||||
|
||||
if update.is_a?(Array) # unlocking specific gems
|
||||
Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update)
|
||||
update = { gems: update, conservative: conservative }
|
||||
elsif update && conservative
|
||||
update = { conservative: conservative }
|
||||
elsif update && bundler
|
||||
update = { bundler: bundler }
|
||||
end
|
||||
|
||||
file = options[:lockfile]
|
||||
file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile
|
||||
|
||||
Bundler.settings.temporary(frozen: false) do
|
||||
definition = Bundler.definition(update, file)
|
||||
|
||||
Bundler::CLI::Common.configure_gem_version_promoter(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
|
||||
puts "Writing lockfile to #{file}"
|
||||
definition.lock
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.level = previous_ui_level
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Open
|
||||
attr_reader :options, :name, :path
|
||||
def initialize(options, name)
|
||||
@options = options
|
||||
@name = name
|
||||
@path = options[:path] unless options[:path].nil?
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "Cannot specify `--path` option without a value" if !@path.nil? && @path.empty?
|
||||
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)
|
||||
if spec.default_gem?
|
||||
Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist."
|
||||
else
|
||||
root_path = spec.full_gem_path
|
||||
require "shellwords"
|
||||
command = Shellwords.split(editor) << File.join([root_path, path].compact)
|
||||
Bundler.with_original_env do
|
||||
system(*command, { chdir: root_path })
|
||||
end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,297 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Outdated
|
||||
attr_reader :options, :gems, :options_include_groups, :filter_options_patch, :sources, :strict
|
||||
attr_accessor :outdated_gems
|
||||
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
@sources = Array(options[:source])
|
||||
|
||||
@filter_options_patch = options.keys & %w[filter-major filter-minor filter-patch]
|
||||
|
||||
@outdated_gems = []
|
||||
|
||||
@options_include_groups = [:group, :groups].any? do |v|
|
||||
options.keys.include?(v.to_s)
|
||||
end
|
||||
|
||||
# the patch level options imply strict is also true. It wouldn't make
|
||||
# sense otherwise.
|
||||
@strict = options["filter-strict"] || Bundler::CLI::Common.patch_level_options(options).any?
|
||||
end
|
||||
|
||||
def run
|
||||
check_for_deployment_mode!
|
||||
|
||||
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.map {|dep| [dep.name, dep] }.to_h
|
||||
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.merge(strict: @strict)
|
||||
)
|
||||
|
||||
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 ""
|
||||
|
||||
# Loop through the current specs
|
||||
gemfile_specs, dependency_specs = current_specs.partition do |spec|
|
||||
current_dependencies.key? spec.name
|
||||
end
|
||||
|
||||
specs = if options["only-explicit"]
|
||||
gemfile_specs
|
||||
else
|
||||
gemfile_specs + dependency_specs
|
||||
end
|
||||
|
||||
specs.sort_by(&:name).uniq(&:name).each do |current_spec|
|
||||
next unless gems.empty? || gems.include?(current_spec.name)
|
||||
|
||||
active_spec = retrieve_active_spec(definition, current_spec)
|
||||
next unless active_spec
|
||||
|
||||
next unless filter_options_patch.empty? || update_present_via_semver_portions(current_spec, active_spec, options)
|
||||
|
||||
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)
|
||||
|
||||
dependency = current_dependencies[current_spec.name]
|
||||
groups = ""
|
||||
if dependency && !options[:parseable]
|
||||
groups = dependency.groups.join(", ")
|
||||
end
|
||||
|
||||
outdated_gems << {
|
||||
active_spec: active_spec,
|
||||
current_spec: current_spec,
|
||||
dependency: dependency,
|
||||
groups: groups,
|
||||
}
|
||||
end
|
||||
|
||||
if outdated_gems.empty?
|
||||
unless options[:parseable]
|
||||
Bundler.ui.info(nothing_outdated_message)
|
||||
end
|
||||
else
|
||||
if options_include_groups
|
||||
relevant_outdated_gems = outdated_gems.group_by {|g| g[:groups] }.sort.flat_map do |groups, gems|
|
||||
contains_group = groups.split(", ").include?(options[:group])
|
||||
next unless options[:groups] || contains_group
|
||||
|
||||
gems
|
||||
end.compact
|
||||
|
||||
if options[:parseable]
|
||||
print_gems(relevant_outdated_gems)
|
||||
else
|
||||
print_gems_table(relevant_outdated_gems)
|
||||
end
|
||||
elsif options[:parseable]
|
||||
print_gems(outdated_gems)
|
||||
else
|
||||
print_gems_table(outdated_gems)
|
||||
end
|
||||
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def loaded_from_for(spec)
|
||||
return unless spec.respond_to?(:loaded_from)
|
||||
|
||||
spec.loaded_from
|
||||
end
|
||||
|
||||
def groups_text(group_text, groups)
|
||||
"#{group_text}#{groups.split(",").size > 1 ? "s" : ""} \"#{groups}\""
|
||||
end
|
||||
|
||||
def nothing_outdated_message
|
||||
if filter_options_patch.any?
|
||||
display = filter_options_patch.map do |o|
|
||||
o.sub("filter-", "")
|
||||
end.join(" or ")
|
||||
|
||||
"No #{display} updates to display.\n"
|
||||
else
|
||||
"Bundle up to date!\n"
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_active_spec(definition, current_spec)
|
||||
active_spec = definition.resolve.find_by_name_and_platform(current_spec.name, current_spec.platform)
|
||||
return unless active_spec
|
||||
|
||||
return active_spec if strict
|
||||
|
||||
active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
|
||||
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_specs.last
|
||||
end
|
||||
|
||||
def print_gems(gems_list)
|
||||
gems_list.each do |gem|
|
||||
print_gem(
|
||||
gem[:current_spec],
|
||||
gem[:active_spec],
|
||||
gem[:dependency],
|
||||
gem[:groups],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def print_gems_table(gems_list)
|
||||
data = gems_list.map do |gem|
|
||||
gem_column_for(
|
||||
gem[:current_spec],
|
||||
gem[:active_spec],
|
||||
gem[:dependency],
|
||||
gem[:groups],
|
||||
)
|
||||
end
|
||||
|
||||
print_indented([table_header] + data)
|
||||
end
|
||||
|
||||
def print_gem(current_spec, active_spec, dependency, groups)
|
||||
spec_version = "#{active_spec.version}#{active_spec.git_version}"
|
||||
if Bundler.ui.debug?
|
||||
loaded_from = loaded_from_for(active_spec)
|
||||
spec_version += " (from #{loaded_from})" if loaded_from
|
||||
end
|
||||
current_version = "#{current_spec.version}#{current_spec.git_version}"
|
||||
|
||||
if 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.empty?
|
||||
" * #{spec_outdated_info}"
|
||||
else
|
||||
" * #{spec_outdated_info} in #{groups_text("group", groups)}"
|
||||
end
|
||||
|
||||
Bundler.ui.info output_message.rstrip
|
||||
end
|
||||
|
||||
def gem_column_for(current_spec, active_spec, dependency, groups)
|
||||
current_version = "#{current_spec.version}#{current_spec.git_version}"
|
||||
spec_version = "#{active_spec.version}#{active_spec.git_version}"
|
||||
dependency = dependency.requirement if dependency
|
||||
|
||||
ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s]
|
||||
ret_val << loaded_from_for(active_spec).to_s if Bundler.ui.debug?
|
||||
ret_val
|
||||
end
|
||||
|
||||
def check_for_deployment_mode!
|
||||
return unless Bundler.frozen_bundle?
|
||||
suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
|
||||
"bundle config unset frozen"
|
||||
elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
|
||||
"bundle config unset deployment"
|
||||
end
|
||||
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 `#{suggested_command}`."
|
||||
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.to_a[0].to_i
|
||||
end
|
||||
|
||||
def print_indented(matrix)
|
||||
header = matrix[0]
|
||||
data = matrix[1..-1]
|
||||
|
||||
column_sizes = Array.new(header.size) do |index|
|
||||
matrix.max_by {|row| row[index].length }[index].length
|
||||
end
|
||||
|
||||
Bundler.ui.info justify(header, column_sizes)
|
||||
|
||||
data.sort_by! {|row| row[0] }
|
||||
|
||||
data.each do |row|
|
||||
Bundler.ui.info justify(row, column_sizes)
|
||||
end
|
||||
end
|
||||
|
||||
def table_header
|
||||
header = ["Gem", "Current", "Latest", "Requested", "Groups"]
|
||||
header << "Path" if Bundler.ui.debug?
|
||||
header
|
||||
end
|
||||
|
||||
def justify(row, sizes)
|
||||
row.each_with_index.map do |element, index|
|
||||
element.ljust(sizes[index])
|
||||
end.join(" ").strip + "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Platform
|
||||
attr_reader :options
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
ruby_version = if Bundler.locked_gems
|
||||
Bundler.locked_gems.ruby_version&.gsub(/p\d+\Z/, "")
|
||||
else
|
||||
Bundler.definition.ruby_version&.single_version_string
|
||||
end
|
||||
|
||||
output = []
|
||||
|
||||
if options[:ruby]
|
||||
if ruby_version
|
||||
output << ruby_version
|
||||
else
|
||||
output << "No ruby version specified"
|
||||
end
|
||||
else
|
||||
platforms = Bundler.definition.platforms.map {|p| "* #{p}" }
|
||||
|
||||
output << "Your platform is: #{Gem::Platform.local}"
|
||||
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
|
||||
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../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), from a git source provided with --git, or a local path provided with --path. 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 "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)"
|
||||
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"
|
||||
method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use"
|
||||
def install(*plugins)
|
||||
Bundler::Plugin.install(plugins, options)
|
||||
end
|
||||
|
||||
desc "uninstall PLUGINS", "Uninstall the plugins"
|
||||
long_desc <<-D
|
||||
Uninstall given list of plugins. To uninstall all the plugins, use -all option.
|
||||
D
|
||||
method_option "all", type: :boolean, default: nil, banner: "Uninstall all the installed plugins. If no plugin is installed, then it does nothing."
|
||||
def uninstall(*plugins)
|
||||
Bundler::Plugin.uninstall(plugins, options)
|
||||
end
|
||||
|
||||
desc "list", "List the installed plugins and available commands"
|
||||
def list
|
||||
Bundler::Plugin.list
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Pristine
|
||||
def initialize(gems)
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
CLI::Common.ensure_all_gems_in_lockfile!(@gems)
|
||||
definition = Bundler.definition
|
||||
definition.validate_runtime!
|
||||
installer = Bundler::Installer.new(Bundler.root, definition)
|
||||
|
||||
ProcessLock.lock do
|
||||
installed_specs = definition.specs.reject do |spec|
|
||||
next if spec.name == "bundler" # Source::Rubygems doesn't install bundler
|
||||
next if !@gems.empty? && !@gems.include?(spec.name)
|
||||
|
||||
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
|
||||
|
||||
FileUtils.rm_rf spec.full_gem_path
|
||||
when Source::Git
|
||||
if source.local?
|
||||
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is locally overridden.")
|
||||
next
|
||||
end
|
||||
|
||||
source.remote!
|
||||
if extension_cache_path = source.extension_cache_path(spec)
|
||||
FileUtils.rm_rf extension_cache_path
|
||||
end
|
||||
FileUtils.rm_rf spec.extension_dir
|
||||
FileUtils.rm_rf spec.full_gem_path
|
||||
else
|
||||
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
|
||||
next
|
||||
end
|
||||
|
||||
true
|
||||
end.map(&:name)
|
||||
|
||||
jobs = installer.send(:installation_parallelization, {})
|
||||
pristine_count = definition.specs.count - installed_specs.count
|
||||
# allow a pristining a single gem to skip the parallel worker
|
||||
jobs = [jobs, pristine_count].min
|
||||
ParallelInstaller.call(installer, definition.specs, jobs, false, true, skip: installed_specs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Remove
|
||||
def initialize(gems, options)
|
||||
@gems = gems
|
||||
@options = options
|
||||
end
|
||||
|
||||
def run
|
||||
raise InvalidOption, "Please specify gems to remove." if @gems.empty?
|
||||
|
||||
Injector.remove(@gems, {})
|
||||
Installer.install(Bundler.root, Bundler.definition)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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("../../..", __dir__)
|
||||
else
|
||||
spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match)
|
||||
return unless spec
|
||||
path = spec.full_gem_path
|
||||
unless File.directory?(path)
|
||||
return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}"
|
||||
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
|
||||
#{desc.lstrip}
|
||||
\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
|
||||
@@ -0,0 +1,123 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bundler
|
||||
class CLI::Update
|
||||
attr_reader :options, :gems
|
||||
def initialize(options, gems)
|
||||
@options = options
|
||||
@gems = gems
|
||||
end
|
||||
|
||||
def run
|
||||
Bundler.ui.level = "warn" if options[:quiet]
|
||||
|
||||
update_bundler = options[:bundler]
|
||||
|
||||
Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler
|
||||
|
||||
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
|
||||
|
||||
sources = Array(options[:source])
|
||||
groups = Array(options[:group]).map(&:to_sym)
|
||||
|
||||
full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !update_bundler
|
||||
|
||||
if full_update && !options[:all]
|
||||
if Bundler.feature_flag.update_requires_all_flag?
|
||||
raise InvalidOption, "To update everything, pass the `--all` flag."
|
||||
end
|
||||
SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything"
|
||||
elsif !full_update && options[:all]
|
||||
raise InvalidOption, "Cannot specify --all along with specific options."
|
||||
end
|
||||
|
||||
conservative = options[:conservative]
|
||||
|
||||
if full_update
|
||||
if conservative
|
||||
Bundler.definition(conservative: conservative)
|
||||
else
|
||||
Bundler.definition(true)
|
||||
end
|
||||
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?
|
||||
deps = Bundler.definition.dependencies.select {|d| (d.groups & groups).any? }
|
||||
gems.concat(deps.map(&:name))
|
||||
end
|
||||
|
||||
Bundler.definition(gems: gems, sources: sources, ruby: options[:ruby],
|
||||
conservative: conservative,
|
||||
bundler: update_bundler)
|
||||
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]
|
||||
opts["force"] = options[:redownload]
|
||||
|
||||
Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
|
||||
|
||||
Bundler.definition.validate_runtime!
|
||||
|
||||
if locked_gems = Bundler.definition.locked_gems
|
||||
previous_locked_info = locked_gems.specs.reduce({}) do |h, s|
|
||||
h[s.name] = { spec: s, version: s.version, source: s.source.identifier }
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
installer = Installer.install Bundler.root, Bundler.definition, opts
|
||||
Bundler.load.cache if Bundler.app_cache.exist?
|
||||
|
||||
if CLI::Common.clean_after_install?
|
||||
require_relative "clean"
|
||||
Bundler::CLI::Clean.new(options).run
|
||||
end
|
||||
|
||||
if locked_gems
|
||||
gems.each do |name|
|
||||
locked_info = previous_locked_info[name]
|
||||
next unless locked_info
|
||||
|
||||
locked_spec = locked_info[:spec]
|
||||
new_spec = Bundler.definition.specs[name].first
|
||||
unless new_spec
|
||||
unless locked_spec.match_platform(Bundler.local_platform)
|
||||
Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one"
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
locked_source = locked_info[:source]
|
||||
new_source = new_spec.source.identifier
|
||||
next if locked_source != new_source
|
||||
|
||||
new_version = new_spec.version
|
||||
locked_version = locked_info[:version]
|
||||
if new_version < locked_version
|
||||
Bundler.ui.warn "Note: #{name} version regressed from #{locked_version} to #{new_version}"
|
||||
elsif new_version == locked_version
|
||||
Bundler.ui.warn "Bundler attempted to update #{name} but its version stayed the same"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Bundler.ui.confirm "Bundle updated!"
|
||||
Bundler::CLI::Common.output_without_groups_message(:update)
|
||||
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
|
||||
|
||||
Bundler::CLI::Common.output_fund_metadata_summary
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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.to_s.include?("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
|
||||
Reference in New Issue
Block a user