Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bundle fund command #3390

Merged
merged 2 commits into from Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Manifest.txt
Expand Up @@ -32,6 +32,7 @@ bundler/lib/bundler/cli/config.rb
bundler/lib/bundler/cli/console.rb
bundler/lib/bundler/cli/doctor.rb
bundler/lib/bundler/cli/exec.rb
bundler/lib/bundler/cli/fund.rb
bundler/lib/bundler/cli/gem.rb
bundler/lib/bundler/cli/info.rb
bundler/lib/bundler/cli/init.rb
Expand Down
8 changes: 8 additions & 0 deletions bundler/lib/bundler/cli.rb
Expand Up @@ -439,6 +439,14 @@ def outdated(*gems)
Outdated.new(options, gems).run
end

desc "fund [OPTIONS]", "Lists information about gems seeking funding assistance"
method_option "group", :aliases => "-g", :type => :array, :banner =>
"Fetch funding information for a specific group"
def fund
require_relative "cli/fund"
Fund.new(options).run
end

desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
method_option "all", :type => :boolean,
:default => Bundler.feature_flag.cache_all?,
Expand Down
14 changes: 14 additions & 0 deletions bundler/lib/bundler/cli/common.rb
Expand Up @@ -14,6 +14,20 @@ def self.print_post_install_message(name, msg)
Bundler.ui.info msg
end

def self.output_fund_metadata_summary
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
deivid-rodriguez marked this conversation as resolved.
Show resolved Hide resolved

def self.output_without_groups_message(command)
return if Bundler.settings[:without].empty?
Bundler.ui.confirm without_groups_message(command)
Expand Down
36 changes: 36 additions & 0 deletions bundler/lib/bundler/cli/fund.rb
@@ -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)
deivid-rodriguez marked this conversation as resolved.
Show resolved Hide resolved
else
Bundler.definition.current_dependencies
end

fund_info = deps.each_with_object([]) do |dep, arr|
spec = Bundler.definition.specs[dep.name].first
deivid-rodriguez marked this conversation as resolved.
Show resolved Hide resolved
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
1 change: 1 addition & 0 deletions bundler/lib/bundler/cli/info.rb
Expand Up @@ -60,6 +60,7 @@ def print_gem_info(spec)
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")
Expand Down
2 changes: 2 additions & 0 deletions bundler/lib/bundler/cli/install.rb
Expand Up @@ -82,6 +82,8 @@ def run
require_relative "clean"
Bundler::CLI::Clean.new(options).run
end

Bundler::CLI::Common.output_fund_metadata_summary
rescue GemNotFound, VersionConflict => e
if options[:local] && Bundler.app_cache.exist?
Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory."
Expand Down
2 changes: 2 additions & 0 deletions bundler/lib/bundler/cli/update.rb
Expand Up @@ -106,6 +106,8 @@ def run
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
24 changes: 12 additions & 12 deletions bundler/lib/bundler/definition.rb
Expand Up @@ -233,6 +233,12 @@ def requested_specs
end
end

def requested_dependencies
groups = requested_groups
groups.map!(&:to_sym)
dependencies_for(groups)
end

def current_dependencies
dependencies.select do |d|
d.should_include? && !d.gem_platforms(@platforms).empty?
Expand All @@ -244,6 +250,12 @@ def specs_for(groups)
specs.for(expand_dependencies(deps))
end

def dependencies_for(groups)
current_dependencies.reject do |d|
(d.groups & groups).empty?
end
end

# Resolve all the dependencies specified in Gemfile. It ensures that
# dependencies that have been already resolved via locked file and are fresh
# are reused when resolving dependencies
Expand Down Expand Up @@ -898,18 +910,6 @@ def expand_dependency_with_platforms(dep, platforms)
end
end

def dependencies_for(groups)
current_dependencies.reject do |d|
(d.groups & groups).empty?
end
end

def requested_dependencies
groups = requested_groups
groups.map!(&:to_sym)
dependencies_for(groups)
end

def source_requirements
# Load all specs from remote sources
index
Expand Down
55 changes: 55 additions & 0 deletions bundler/spec/commands/fund_spec.rb
@@ -0,0 +1,55 @@
# frozen_string_literal: true

RSpec.describe "bundle fund" do
it "prints fund information for all gems in the bundle" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding'
gem 'rack-obama'
G

bundle "fund"

expect(out).to include("* has_metadata (1.0)\n Funding: https://example.com/has_metadata/funding")
expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
expect(out).to_not include("rack-obama")
end

it "does not consider fund information for gem dependencies" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'gem_with_dependent_funding'
G

bundle "fund"

expect(out).to_not include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
expect(out).to_not include("gem_with_dependent_funding")
end

it "prints message if none of the gems have fund information" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'rack-obama'
G

bundle "fund"

expect(out).to include("None of the installed gems you directly depend on are looking for funding.")
end

describe "with --group option" do
it "prints fund message for only specified group gems" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata', :group => :development
gem 'has_funding'
G

bundle "fund --group development"
expect(out).to include("* has_metadata (1.0)\n Funding: https://example.com/has_metadata/funding")
expect(out).to_not include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
end
end
end
1 change: 1 addition & 0 deletions bundler/spec/commands/info_spec.rb
Expand Up @@ -66,6 +66,7 @@
\tHomepage: http://example.com
\tDocumentation: https://www.example.info/gems/bestgemever/0.0.1
\tSource Code: https://example.com/user/bestgemever
\tFunding: https://example.com/has_metadata/funding
\tWiki: https://example.com/user/bestgemever/wiki
\tChangelog: https://example.com/user/bestgemever/CHANGELOG.md
\tBug Tracker: https://example.com/user/bestgemever/issues
Expand Down
110 changes: 110 additions & 0 deletions bundler/spec/install/gems/fund_spec.rb
@@ -0,0 +1,110 @@
# frozen_string_literal: true

RSpec.describe "bundle install" do
context "with gem sources" do
context "when gems include a fund URI" do
it "displays the plural fund message after installing" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding'
gem 'rack-obama'
G

expect(out).to include("2 installed gems you directly depend on are looking for funding.")
end

it "displays the singular fund message after installing" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_funding'
gem 'rack-obama'
G

expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end
end

context "when gems do not include fund messages" do
it "does not display any fund messages" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem "activesupport"
G

expect(out).not_to include("gem you depend on")
end
end

context "when a dependency includes a fund message" do
it "does not display the fund message" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'gem_with_dependent_funding'
G

expect(out).not_to include("gem you depend on")
end
end
end

context "with git sources" do
context "when gems include fund URI" do
it "displays the fund message after installing" do
build_git "also_has_funding" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
G

expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end

it "displays the fund message if repo is updated" do
build_git "also_has_funding" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
G

build_git "also_has_funding", "1.1" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.1")}'
G

expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end

it "displays the fund message if repo is not updated" do
build_git "also_has_funding" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
G

bundle :install
expect(out).to include("1 installed gem you directly depend on is looking for funding.")

bundle :install
expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end
end
end
end
11 changes: 11 additions & 0 deletions bundler/spec/support/builders.rb
Expand Up @@ -322,10 +322,21 @@ def __pry__
"documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1",
"homepage_uri" => "https://bestgemever.example.io",
"mailing_list_uri" => "https://groups.example.com/bestgemever",
"funding_uri" => "https://example.com/has_metadata/funding",
"source_code_uri" => "https://example.com/user/bestgemever",
"wiki_uri" => "https://example.com/user/bestgemever/wiki",
}
end

build_gem "has_funding", "1.2.3" do |s|
s.metadata = {
"funding_uri" => "https://example.com/has_funding/funding",
}
end

build_gem "gem_with_dependent_funding", "1.0" do |s|
s.add_dependency "has_funding"
end
end
end

Expand Down
29 changes: 29 additions & 0 deletions bundler/spec/update/gems/fund_spec.rb
@@ -0,0 +1,29 @@
# frozen_string_literal: true

RSpec.describe "bundle update" do
before do
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding', '< 2.0'
G

bundle :install
end

context "when listed gems are updated" do
before do
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding'
G

bundle :update, :all => true
end

it "displays fund message" do
expect(out).to include("2 installed gems you directly depend on are looking for funding.")
end
end
end