Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,62 @@
# 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
Bundler.ui.level = "warn" if options[:quiet]
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
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 cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"]
unless options["git"] || options["github"]
raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"]
raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"]
end
raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"]
raise InvalidOption, "You cannot 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,32 @@
# 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 :cache_path, options["cache-path"]
install
Bundler.settings.temporary(cache_all_platforms: options["all-platforms"]) do
Bundler.load.cache
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
end
end
@@ -0,0 +1,38 @@
# 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.ensure_equivalent_gemfile_and_lockfile
definition.validate_runtime!
begin
definition.check!
not_installed = definition.missing_specs
rescue GemNotFound, GitError, 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
else
definition.lock(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,172 @@
# frozen_string_literal: true
module Bundler
module CLI::Common
def self.validate_cooldown!(value)
return if value.nil?
return if value.is_a?(Integer) && value >= 0
raise InvalidOption, "Expected `--cooldown` to be a non-negative integer, got #{value.inspect}"
end
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_cooldown_skipped_summary(definition = Bundler.definition)
skipped = definition.cooldown_skipped
return if skipped.empty?
Bundler.ui.info "The following gem versions were skipped by the cooldown setting:"
skipped.each do |entry|
days = entry[:available_in_days]
Bundler.ui.info " * #{entry[:name]} #{entry[:version]} (available in #{days} #{days == 1 ? "day" : "days"}), resolved #{entry[:resolved]} instead"
end
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)
message = "Could not find gem '#{missing_gem_name}'."
alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
if alternate_names.include?(missing_gem_name.downcase)
message += "\nDid you mean '#{missing_gem_name.downcase}'?"
elsif defined?(DidYouMean::SpellChecker)
suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name)
message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty?
end
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.bundler_5_mode? && Bundler.settings[:path].nil?
clean &&= !Bundler.use_system_gems?
clean
end
def self.word_list(words)
if words.empty?
return ""
end
words = words.map {|word| "'#{word}'" }
if words.length == 1
return words[0]
end
[words[0..-2].join(", "), words[-1]].join(" or ")
end
end
end
@@ -0,0 +1,208 @@
# 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."
SharedHelpers.feature_deprecated! 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"
current_value = Bundler.settings[name]
if options[:parseable]
if value = Bundler.settings[name]
Bundler.ui.info("#{name}=#{value}")
end
else
confirm(name)
end
if current_value.nil?
exit 1
else
return
end
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,47 @@
# frozen_string_literal: true
module Bundler
class CLI::Console
attr_reader :options, :group
def initialize(options, group)
@options = options
@group = group
end
def run
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
if name == "irb"
if defined?(Gem::BUNDLED_GEMS) && Gem::BUNDLED_GEMS.respond_to?(:force_activate)
Gem::BUNDLED_GEMS.force_activate "irb"
require name
return get_constant(name)
end
Bundler.ui.error "#{name} is not available"
exit 1
else
Bundler.ui.error "Couldn't load console #{name}, falling back to irb"
name = "irb"
retry
end
end
def get_constant(name)
const_name = {
"pry" => :Pry,
"ripl" => :Ripl,
"irb" => :IRB,
}[name]
Object.const_get(const_name)
end
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
module Bundler
class CLI::Doctor < Thor
default_command(:diagnose)
desc "diagnose [OPTIONS]", "Checks the bundle for common problems"
long_desc <<-D
Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If
missing dependencies are detected, Bundler prints them and exits status 1.
Otherwise, Bundler prints a success message and exits with a status of 0.
D
method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile"
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
method_option "ssl", type: :boolean, default: false, banner: "Diagnose SSL problems."
def diagnose
require_relative "doctor/diagnose"
Diagnose.new(options).run
end
desc "ssl [OPTIONS]", "Diagnose SSL problems"
long_desc <<-D
Diagnose SSL problems, especially related to certificates or TLS version while connecting to https://rubygems.org.
D
method_option "host", type: :string, banner: "The host to diagnose."
method_option "tls-version", type: :string, banner: "Specify the SSL/TLS version when running the diagnostic. Accepts either <1.1> or <1.2>"
method_option "verify-mode", type: :string, banner: "Specify the mode used for certification verification. Accepts either <peer> or <none>"
def ssl
require_relative "doctor/ssl"
SSL.new(options).run
end
end
end
@@ -0,0 +1,167 @@
# frozen_string_literal: true
require "rbconfig"
require "shellwords"
module Bundler
class CLI::Doctor::Diagnose
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].filter_map {|l| l.match(DARWIN_REGEX)&.match(1) }.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").filter_map do |l|
match = l.match(LDD_REGEX)
next if match.nil?
match.captures[0]
end
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)
SharedHelpers.glob_files_in_dir("**/*.bundle", spec.full_gem_path)
end
def lookup_with_fiddle(path)
require "fiddle"
Fiddle.dlopen(path)
false
rescue Fiddle::DLError
true
end
def check!
require_relative "../check"
Bundler::CLI::Check.new({}).run
end
def diagnose_ssl
require_relative "ssl"
Bundler::CLI::Doctor::SSL.new({}).run
end
def run
Bundler.ui.level = "warn" if options[:quiet]
Bundler.settings.validate!
check!
diagnose_ssl if options[:ssl]
definition = Bundler.definition
broken_links = {}
definition.specs.each do |spec|
bundles_for_gem(spec).each do |bundle|
bad_paths = dylibs(bundle).select do |f|
lookup_with_fiddle(f)
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.flat_map do |spec, paths|
paths.uniq.map do |path|
"\n * #{spec.name}: #{path}"
end
end.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 = []
files_not_readable_and_owned_by_different_user = []
files_not_owned_by_current_user_but_still_readable = []
broken_symlinks = []
Find.find(Bundler.bundle_path.to_s).each do |f|
if !File.exist?(f)
broken_symlinks << f
elsif !File.readable?(f)
if File.stat(f).uid != Process.uid
files_not_readable_and_owned_by_different_user << f
else
files_not_readable << f
end
elsif File.stat(f).uid != Process.uid
files_not_owned_by_current_user_but_still_readable << 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_readable.any?
Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
"user, but are still readable. These files are:\n - #{files_not_owned_by_current_user_but_still_readable.join("\n - ")}"
ok = false
end
if files_not_readable_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. These files are:\n - #{files_not_readable_and_owned_by_different_user.join("\n - ")}"
ok = false
end
if files_not_readable.any?
Bundler.ui.warn "Files exist in the Bundler home that are not " \
"readable by the current user. These files are:\n - #{files_not_readable.join("\n - ")}"
ok = false
end
ok
end
end
end
@@ -0,0 +1,249 @@
# frozen_string_literal: true
require "rubygems/remote_fetcher"
require "uri"
module Bundler
class CLI::Doctor::SSL
attr_reader :options
def initialize(options)
@options = options
end
def run
return unless openssl_installed?
output_ssl_environment
bundler_success = bundler_connection_successful?
rubygem_success = rubygem_connection_successful?
return unless net_http_connection_successful?
Explanation.summarize(bundler_success, rubygem_success, host)
end
private
def host
@options[:host] || "rubygems.org"
end
def tls_version
@options[:"tls-version"].then do |version|
"TLS#{version.sub(".", "_")}".to_sym if version
end
end
def verify_mode
mode = @options[:"verify-mode"] || :peer
@verify_mode ||= mode.then {|mod| OpenSSL::SSL.const_get("verify_#{mod}".upcase) }
end
def uri
@uri ||= URI("https://#{host}")
end
def openssl_installed?
require "openssl"
true
rescue LoadError
Bundler.ui.warn(<<~MSG)
Oh no! Your Ruby doesn't have OpenSSL, so it can't connect to #{host}.
You'll need to recompile or reinstall Ruby with OpenSSL support and try again.
MSG
false
end
def output_ssl_environment
Bundler.ui.info(<<~MESSAGE)
Here's your OpenSSL environment:
OpenSSL: #{OpenSSL::VERSION}
Compiled with: #{OpenSSL::OPENSSL_VERSION}
Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION}
MESSAGE
end
def bundler_connection_successful?
Bundler.ui.info("\nTrying connections to #{uri}:\n")
bundler_uri = Gem::URI(uri.to_s)
Bundler::Fetcher.new(
Bundler::Source::Rubygems::Remote.new(bundler_uri)
).send(:connection).request(bundler_uri)
Bundler.ui.info("Bundler: success")
true
rescue StandardError => error
Bundler.ui.warn("Bundler: failed (#{Explanation.explain_bundler_or_rubygems_error(error)})")
false
end
def rubygem_connection_successful?
Gem::RemoteFetcher.fetcher.fetch_path(uri)
Bundler.ui.info("RubyGems: success")
true
rescue StandardError => error
Bundler.ui.warn("RubyGems: failed (#{Explanation.explain_bundler_or_rubygems_error(error)})")
false
end
def net_http_connection_successful?
::Gem::Net::HTTP.new(uri.host, uri.port).tap do |http|
http.use_ssl = true
http.min_version = tls_version
http.max_version = tls_version
http.verify_mode = verify_mode
end.start
Bundler.ui.info("Ruby net/http: success")
warn_on_unsupported_tls12
true
rescue StandardError => error
Bundler.ui.warn(<<~MSG)
Ruby net/http: failed
Unfortunately, this Ruby can't connect to #{host}.
#{Explanation.explain_net_http_error(error, host, tls_version)}
MSG
false
end
def warn_on_unsupported_tls12
ctx = OpenSSL::SSL::SSLContext.new
supported = true
if ctx.respond_to?(:min_version=)
begin
ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
rescue OpenSSL::SSL::SSLError, NameError
supported = false
end
else
supported = OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_2) # rubocop:disable Naming/VariableNumber
end
Bundler.ui.warn(<<~EOM) unless supported
WARNING: Although your Ruby can connect to #{host} today, your OpenSSL is very old!
WARNING: You will need to upgrade OpenSSL to use #{host}.
EOM
end
module Explanation
extend self
def explain_bundler_or_rubygems_error(error)
case error.message
when /certificate verify failed/
"certificate verification"
when /read server hello A/
"SSL/TLS protocol version mismatch"
when /tlsv1 alert protocol version/
"requested TLS version is too old"
else
error.message
end
end
def explain_net_http_error(error, host, tls_version)
case error.message
# Check for certificate errors
when /certificate verify failed/
<<~MSG
#{show_ssl_certs}
Your Ruby can't connect to #{host} because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine #{host} servers.
MSG
# Check for TLS version errors
when /read server hello A/, /tlsv1 alert protocol version/
if tls_version.to_s == "TLS1_3"
"Your Ruby can't connect to #{host} because #{tls_version} isn't supported yet.\n"
else
<<~MSG
Your Ruby can't connect to #{host} because your version of OpenSSL is too old.
You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL.
MSG
end
# OpenSSL doesn't support TLS version specified by argument
when /unknown SSL method/
"Your Ruby can't connect because #{tls_version} isn't supported by your version of OpenSSL."
else
<<~MSG
Even worse, we're not sure why.
Here's the full error information:
#{error.class}: #{error.message}
#{error.backtrace.join("\n ")}
You might have more luck using Mislav's SSL doctor.rb script. You can get it here:
https://github.com/mislav/ssl-tools/blob/8b3dec4/doctor.rb
Read more about the script and how to use it in this blog post:
https://mislav.net/2013/07/ruby-openssl/
MSG
end
end
def summarize(bundler_success, rubygems_success, host)
guide_url = "http://ruby.to/ssl-check-failed"
message = if bundler_success && rubygems_success
<<~MSG
Hooray! This Ruby can connect to #{host}.
You are all set to use Bundler and RubyGems.
MSG
elsif !bundler_success && !rubygems_success
<<~MSG
For some reason, your Ruby installation can connect to #{host}, but neither RubyGems nor Bundler can.
The most likely fix is to manually upgrade RubyGems by following the instructions at #{guide_url}.
After you've done that, run `gem install bundler` to upgrade Bundler, and then run this script again to make sure everything worked. ❣
MSG
elsif !bundler_success
<<~MSG
Although your Ruby installation and RubyGems can both connect to #{host}, Bundler is having trouble.
The most likely way to fix this is to upgrade Bundler by running `gem install bundler`.
Run this script again after doing that to make sure everything is all set.
If you're still having trouble, check out the troubleshooting guide at #{guide_url}.
MSG
else
<<~MSG
It looks like Ruby and Bundler can connect to #{host}, but RubyGems itself cannot.
You can likely solve this by manually downloading and installing a RubyGems update.
Visit #{guide_url} for instructions on how to manually upgrade RubyGems.
MSG
end
Bundler.ui.info("\n#{message}")
end
private
def show_ssl_certs
ssl_cert_file = ENV["SSL_CERT_FILE"] || OpenSSL::X509::DEFAULT_CERT_FILE
ssl_cert_dir = ENV["SSL_CERT_DIR"] || OpenSSL::X509::DEFAULT_CERT_DIR
<<~MSG
Below affect only Ruby net/http connections:
SSL_CERT_FILE: #{File.exist?(ssl_cert_file) ? "exists #{ssl_cert_file}" : "is missing #{ssl_cert_file}"}
SSL_CERT_DIR: #{Dir.exist?(ssl_cert_dir) ? "exists #{ssl_cert_dir}" : "is missing #{ssl_cert_dir}"}
MSG
end
end
end
end
@@ -0,0 +1,128 @@
# 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] && directly_loadable?(bin_path)
bin_path.delete_suffix!(".bat") if Gem.win_platform?
kernel_load(bin_path, *args)
else
bin_path = explicit_path(bin_path)
kernel_exec(bin_path, *args)
end
else
# exec using the given command
kernel_exec(cmd, *args)
end
end
private
def validate_cmd!
return unless cmd.nil?
Bundler.ui.error "bundler: exec needs a command to run"
exit 128
end
def kernel_exec(*args)
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 explicit_path(path)
if File.absolute_path?(path) || explicit_relative_path?(path)
path
else
".#{File::SEPARATOR}#{path}"
end
end
def explicit_relative_path?(path)
[File::SEPARATOR, File::ALT_SEPARATOR].compact.any? do |separator|
path.start_with?(".#{separator}", "..#{separator}")
end
end
def directly_loadable?(file)
if Gem.win_platform?
script_wrapper?(file)
else
ruby_shebang?(file)
end
end
def script_wrapper?(file)
script_file = file.delete_suffix(".bat")
return false unless File.exist?(script_file)
if File.zero?(script_file)
Bundler.ui.warn "#{script_file} is empty"
return false
end
header = File.open(file, "r") {|f| f.read(32) }
ruby_exe = "#{RbConfig::CONFIG["RUBY_INSTALL_NAME"]}#{RbConfig::CONFIG["EXEEXT"]}"
ruby_exe = "ruby.exe" if ruby_exe.empty?
header.include?(ruby_exe)
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.requested_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,492 @@
# frozen_string_literal: true
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
DEFAULT_GITHUB_USERNAME = "[USERNAME]"
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 = Pathname.new(SharedHelpers.pwd).join(gem_name)
@extension = options[:ext]
validate_ext_name if @extension
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 = github_username(git_username)
if github_username.empty?
homepage_uri = "TODO: Put your gem's website or public repo URL here."
source_code_uri = "TODO: Put your gem's public repo URL here."
changelog_uri = "TODO: Put your gem's CHANGELOG.md URL here."
else
homepage_uri = "https://github.com/#{github_username}/#{name}"
source_code_uri = "https://github.com/#{github_username}/#{name}"
changelog_uri = "https://github.com/#{github_username}/#{name}/blob/main/CHANGELOG.md"
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],
bundle: options[:bundle],
bundler_version: bundler_dependency_version,
git: use_git,
github_username: github_username.empty? ? DEFAULT_GITHUB_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,
ignore_paths: %w[bin/],
homepage_uri: homepage_uri,
source_code_uri: source_code_uri,
changelog_uri: changelog_uri,
}
ensure_safe_gem_name(name, constant_array)
templates = {
"Gemfile.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
]
case Bundler.preferred_gemfile_name
when "Gemfile"
config[:ignore_paths] << "Gemfile"
when "gems.rb"
config[:ignore_paths] << "gems.rb"
config[:ignore_paths] << "gems.locked"
end
if use_git
templates.merge!("gitignore.tt" => ".gitignore")
config[:ignore_paths] << ".gitignore"
end
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
config[:ignore_paths] << ".rspec"
config[:ignore_paths] << "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
config[:ignore_paths] << "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
config[:ignore_paths] << "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")
if extension == "rust"
templates.merge!("github/workflows/build-gems.yml.tt" => ".github/workflows/build-gems.yml")
end
config[:ignore_paths] << ".github/"
when "gitlab"
templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml")
config[:ignore_paths] << ".gitlab-ci.yml"
when "circle"
templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml")
config[:ignore_paths] << ".circleci/"
end
if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?",
"Using a MIT license 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 safe, respectful, productive, and collaborative spaces. \n" \
"See https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md")
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")
config[:ignore_paths] << ".rubocop.yml"
when "standard"
config[:linter_version] = standard_version
Bundler.ui.info "Standard enabled in config"
templates.merge!("standard.yml.tt" => ".standard.yml")
config[:ignore_paths] << ".standard.yml"
end
if config[:exe]
templates.merge!("exe/newgem.tt" => "exe/#{name}")
executables.push("exe/#{name}")
end
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/build.rs.tt" => "ext/#{name}/build.rs",
"ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb",
"ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs",
)
end
if extension == "go"
templates.merge!(
"ext/newgem/go.mod.tt" => "ext/#{name}/go.mod",
"ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb",
"ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
"ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go",
"ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c",
)
config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username]
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 "\nInitializing git repo in #{target}"
IO.popen(["git", "init", target.to_s], &:read)
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
if config[:bundle]
Bundler.ui.info "Running bundle install in the new gem directory."
Dir.chdir(target) do
system("bundle install")
end
end
# Open gemspec in editor
open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
Bundler.ui.info "\nGem '#{name}' was successfully created. " \
"For more information on making a RubyGem visit https://guides.rubygems.org/make-your-own-gem/"
end
private
def resolve_name(name)
Pathname.new(SharedHelpers.pwd).join(name).basename.to_s
end
def ask_and_set(key, prompt, explanation)
choice = options[key]
choice = Bundler.settings["gem.#{key}"] if choice.nil?
if choice.nil?
Bundler.ui.info "\n#{explanation}"
choice = Bundler.ui.yes? "#{prompt} 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
return if skip?(:test)
test_framework = options[:test] || Bundler.settings["gem.test"]
if test_framework.to_s.empty?
Bundler.ui.info "\nDo 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 skip?(option)
options.key?(option) && options[option].nil?
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
return if skip?(:ci)
ci_template = options[:ci] || Bundler.settings["gem.ci"]
if ci_template.to_s.empty?
Bundler.ui.info "\nDo 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"
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
return if skip?(:linter)
linter_template = options[:linter] || Bundler.settings["gem.linter"]
if linter_template.to_s.empty?
Bundler.ui.info "\nDo 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"
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 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
if /[A-Z]/.match?(name)
Bundler.ui.warn "Gem names with capital letters are not recommended. Please use only lowercase letters, numbers, and hyphens."
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.2.0"
end
def rubocop_version
"1.21"
end
def standard_version
"1.3"
end
def github_username(git_username)
if options[:github_username].nil?
git_username
elsif options[:github_username] == false
""
else
options[:github_username]
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.installation_missing?
return Bundler.ui.warn "The gem #{name} is missing. It should be installed at #{path}, but was not found"
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.installation_missing?
return Bundler.ui.warn "The gem #{name} is missing. Gemspec information is still available though:\n#{gem_info}"
end
Bundler.ui.info gem_info
end
def gem_dependencies
@gem_dependencies ||= Bundler.definition.specs.filter_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.sort
end
end
end
@@ -0,0 +1,55 @@
# 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)
unless spec
Bundler.ui.error "Gem specification #{gemspec} did not produce a specification"
exit 1
end
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,128 @@
# 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
if options[:local]
Bundler.self_manager.restart_with_locked_bundler_if_needed
else
Bundler.self_manager.install_locked_bundler_and_restart_with_it_if_needed
end
Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Gem.freebsd_platform?
if target_rbconfig_path = options[:"target-rbconfig"]
Bundler.rubygems.set_target_rbconfig(target_rbconfig_path)
end
check_trust_policy
if Bundler.frozen_bundle? && !Bundler.default_lockfile.exist?
flag = "deployment setting" if Bundler.settings[:deployment]
flag = "frozen setting" if Bundler.settings[:frozen]
raise ProductionError, "The #{flag} requires a lockfile. Please make " \
"sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \
"before deploying."
end
normalize_settings
Bundler::Fetcher.disable_endpoint = options["full-index"]
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
# For install we want to enable strict validation
# (rather than some optimizations we perform at app runtime).
definition = Bundler.definition(strict: true)
definition.validate_runtime!
definition.lockfile = options["lockfile"] if options["lockfile"]
definition.lockfile = false if options["no-lock"]
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
Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
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 {|spec| spec.name != "bundler" }
"#{count} #{count == 1 ? "gem" : "gems"} now installed"
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_settings
if options["standalone"] && Bundler.settings[:path].nil? && !options["local"]
Bundler.settings.set_command_option :path, "bundle"
end
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
Bundler::CLI::Common.validate_cooldown!(options["cooldown"])
Bundler.settings.set_command_option_if_given :cooldown, options["cooldown"]
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"]
options[:force] = options[:redownload] if options[:redownload]
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/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md
2. Instructions for common Bundler uses can be found on the documentation
site: https://guides.rubygems.org/
3. Information about each Bundler command can be found in the Bundler
man pages: https://guides.rubygems.org/command-reference/bundle/
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/ruby/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/diagnose"
Bundler::CLI::Doctor::Diagnose.new({}).run
end
end
end
@@ -0,0 +1,97 @@
# frozen_string_literal: true
require "json"
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)
@format = options["format"]
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)
case @format
when "json"
print_json(specs: specs)
when nil
print_human(specs: specs)
else
raise InvalidOption, "Unknown option`--format=#{@format}`. Supported formats: `json`"
end
end
private
def print_json(specs:)
gems = if @options["name-only"]
specs.map {|s| { name: s.name } }
else
specs.map do |s|
{
name: s.name,
version: s.version.to_s,
git_version: s.git_version&.strip,
}.tap do |h|
h[:path] = s.full_gem_path if @options["paths"]
end
end
end
Bundler.ui.info({ gems: gems }.to_json)
end
def print_human(specs:)
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
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,102 @@
# 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
check_for_conflicting_options
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
print = options[:print]
previous_output_stream = Bundler.ui.output_stream
Bundler.ui.output_stream = :stderr if print
Bundler::Fetcher.disable_endpoint = options["full-index"]
update = options[:update]
# --update is repeatable, so it parses as an array with one entry per
# occurrence, where a bare `--update` produces a `true` entry
update = update.include?(true) ? true : update.flatten if update.is_a?(Array)
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
Bundler.settings.temporary(frozen: false) do
definition = Bundler.definition(update, Bundler.default_lockfile)
definition.add_checksums if options["add-checksums"]
Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update]
options["remove-platform"].flatten.each do |platform_string|
platform = Gem::Platform.new(platform_string)
definition.remove_platform(platform)
end
options["add-platform"].flatten.each do |platform_string|
platform = Gem::Platform.new(platform_string)
if platform.to_s == "unknown"
Bundler.ui.error "The platform `#{platform_string}` is unknown to RubyGems and can't be added to the lockfile."
exit 1
end
definition.add_platform(platform)
end
if definition.platforms.empty?
raise InvalidOption, "Removing all platforms from the bundle is not allowed"
end
definition.remotely! unless options[:local]
if options["normalize-platforms"]
definition.normalize_platforms
end
if print
puts definition.to_lock
else
file = options[:lockfile]
file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile
puts "Writing lockfile to #{file}"
definition.write_lock(file, false)
end
Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
end
Bundler.ui.output_stream = previous_output_stream
end
private
def check_for_conflicting_options
if options["normalize-platforms"] && options["add-platform"].any?
raise InvalidOption, "--normalize-platforms can't be used with --add-platform"
end
if options["normalize-platforms"] && options["remove-platform"].any?
raise InvalidOption, "--normalize-platforms can't be used with --remove-platform"
end
end
end
end
@@ -0,0 +1,39 @@
# 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
command = editor_command(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
def editor_command(editor)
# On Windows an editor is often configured with a full path such as
# C:\Program Files\Microsoft VS Code\Code.exe, which shell splitting
# would corrupt. Take a value that names an existing file as a
# single word.
return [editor] if Gem.win_platform? && File.file?(editor)
require "shellwords"
Shellwords.split(editor)
end
end
end
@@ -0,0 +1,367 @@
# 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!
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
Bundler.definition.validate_runtime!
current_specs = Bundler.ui.silence { Bundler.definition.resolve }
gems.each do |gem_name|
if current_specs[gem_name].empty?
raise GemNotFound, "Could not find gem '#{gem_name}'."
end
end
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.progress(&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
relevant_outdated_gems = if options_include_groups
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
else
outdated_gems
end
if relevant_outdated_gems.empty?
unless options[:parseable]
Bundler.ui.info(nothing_outdated_message)
end
else
if options[:parseable]
print_gems(relevant_outdated_gems)
else
print_gems_table(relevant_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
matching_specs(active_spec, current_spec).last
end
def matching_specs(active_spec, current_spec)
@matching_specs ||= {}
@matching_specs[[active_spec.source, current_spec.name, current_spec.platform]] ||= begin
active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_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
end
end
# The newest version the cooldown setting would let bundler adopt right
# now, when the newest overall version is still inside the window. Only a
# version strictly between the installed one and the newest one is an
# adoptable update worth showing.
def newest_out_of_cooldown(active_spec, current_spec)
newest = matching_specs(active_spec, current_spec).reverse_each.find {|spec| cooldown_days_remaining(spec).nil? }
return unless newest
return if newest.version >= active_spec.version || newest.version <= current_spec.version
newest
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}"
release_date = release_date_for(active_spec)
spec_outdated_info += ", released #{release_date}" unless release_date.empty?
remaining = cooldown_days_remaining(active_spec)
if remaining
spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}"
adoptable = newest_out_of_cooldown(active_spec, current_spec)
spec_outdated_info += ", newest out of cooldown #{adoptable.version}" if adoptable
end
spec_outdated_info += ")"
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}"
remaining = cooldown_days_remaining(active_spec)
if remaining
adoptable = newest_out_of_cooldown(active_spec, current_spec)
adoptable_note = adoptable ? ", #{adoptable.version} out of cooldown" : ""
spec_version += " (cooldown #{remaining}d#{adoptable_note})"
end
dependency = dependency.requirement if dependency
ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s]
ret_val << release_date_for(active_spec)
ret_val << loaded_from_for(active_spec).to_s if Bundler.ui.debug?
ret_val
end
def cooldown_days_remaining(spec, now = cooldown_now)
return nil unless spec.respond_to?(:created_at) && spec.created_at
return nil unless spec.respond_to?(:remote) && spec.remote
days = spec.remote.effective_cooldown
return nil if days.nil? || days <= 0
remaining = days - ((now - spec.created_at) / 86_400.0)
remaining > 0 ? remaining.ceil : nil
end
def cooldown_now
@cooldown_now ||= Time.now
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", "Release Date"]
header << "Path" if Bundler.ui.debug?
header
end
def release_date_for(spec)
return "" unless spec.respond_to?(:date)
date = spec.date
return "" unless date
return "" unless Gem.const_defined?(:DEFAULT_SOURCE_DATE_EPOCH)
default_date = Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc
default_date = Time.utc(default_date.year, default_date.month, default_date.day)
date = date.utc if date.respond_to?(:utc)
return "" if date == default_date
date.strftime("%Y-%m-%d")
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,39 @@
# 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 (removed)"
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)
if options.key?(:local_git)
raise InvalidOption, "--local_git has been removed, use --git"
end
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,64 @@
# 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)
git_sources = []
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
next if git_sources.include?(source)
git_sources << source
else
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
next
end
true
end.map(&:name)
jobs = Bundler.settings.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,71 @@
# 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]
@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} is missing. It should be installed at #{path}, but was not found"
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)
Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
Bundler.ui.silence { definition.remotely! }
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,126 @@
# 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, pre: options[:pre]) if update_bundler
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[: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.settings[:update_requires_all_flag]
raise InvalidOption, "To update everything, pass the `--all` flag."
end
SharedHelpers.feature_deprecated! "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] if options[:redownload]
Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
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.installable_on_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_cooldown_skipped_summary
Bundler::CLI::Common.output_fund_metadata_summary
end
end
end