Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [BUGFIX: example](https://github.com/fastruby/next_rails/pull/<number>)
- [FEATURE: Validate the DeprecationTracker mode at initialization, treating a blank mode as the default `save`](https://github.com/fastruby/next_rails/pull/186)
- [BUGFIX: `bundle_report outdated` no longer confuses a locally-sourced (`path:`) gem with a same-named public gem on rubygems; local gems are excluded from the out-of-date check and counted separately](https://github.com/fastruby/next_rails/pull/188)

* Your changes/patches go here.

Expand Down
21 changes: 14 additions & 7 deletions lib/next_rails/bundle_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,27 @@ def compatible_ruby_version(rails_version)

def outdated(format = nil)
gems = NextRails::GemInfo.all
out_of_date_gems = gems.reject(&:up_to_date?).sort_by(&:created_at)
sourced_locally = gems.select(&:sourced_locally?)
sourced_from_git = gems.select(&:sourced_from_git?)

# Locally-sourced gems (e.g. `path:` engines) are excluded from the
# out-of-date check: looking them up by name on rubygems can match an
# unrelated public gem with the same name and report a bogus upgrade.
out_of_date_gems = (gems - sourced_locally).reject(&:up_to_date?).sort_by(&:created_at)

if format == 'json'
output_to_json(out_of_date_gems, gems.count, sourced_from_git.count)
output_to_json(out_of_date_gems, gems.count, sourced_from_git.count, sourced_locally.count)
else
output_to_stdout(out_of_date_gems, gems.count, sourced_from_git.count)
output_to_stdout(out_of_date_gems, gems.count, sourced_from_git.count, sourced_locally.count)
end
end

def output_to_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
obj = build_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
def output_to_json(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
obj = build_json(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
puts JSON.pretty_generate(obj)
end

def build_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
def build_json(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
output = Hash.new { [] }
out_of_date_gems.each do |gem|
output[:outdated_gems] += [
Expand All @@ -95,12 +100,13 @@ def build_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
output.merge(
{
sourced_from_git_count: sourced_from_git_count,
sourced_locally_count: sourced_locally_count,
total_gem_count: total_gem_count
}
)
end

def output_to_stdout(out_of_date_gems, total_gem_count, sourced_from_git_count)
def output_to_stdout(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
out_of_date_gems.each do |gem|
header = "#{gem.name} #{gem.version}"

Expand All @@ -112,6 +118,7 @@ def output_to_stdout(out_of_date_gems, total_gem_count, sourced_from_git_count)
percentage_out_of_date = ((out_of_date_gems.count / total_gem_count.to_f) * 100).round
footer = <<-MESSAGE
#{NextRails::Tint(sourced_from_git_count.to_s).yellow} gems are sourced from git
#{NextRails::Tint(sourced_locally_count.to_s).yellow} gems are sourced from a local path
#{NextRails::Tint(out_of_date_gems.count.to_s).red} of the #{total_gem_count} gems are out-of-date (#{percentage_out_of_date}%)
MESSAGE

Expand Down
8 changes: 8 additions & 0 deletions lib/next_rails/gem_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def sourced_from_git?
!!gem_specification.git_version
end

def sourced_locally?
return false unless defined?(Bundler::Source::Path)

source = gem_specification.source
# Git sources subclass Path, so exclude them; they are reported via #sourced_from_git?.
source.is_a?(Bundler::Source::Path) && !source.is_a?(Bundler::Source::Git)
end

def created_at
@created_at ||= gem_specification.date
end
Expand Down
39 changes: 34 additions & 5 deletions spec/next_rails/bundle_report_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
RSpec.describe NextRails::BundleReport do
describe '.outdated' do
let(:mock_version) { Struct.new(:version, :age) }
let(:mock_gem) { Struct.new(:name, :version, :age, :latest_version, :up_to_date?, :created_at, :sourced_from_git?) }
let(:mock_gem) { Struct.new(:name, :version, :age, :latest_version, :up_to_date?, :created_at, :sourced_from_git?, :sourced_locally?) }
let(:format_str) { '%b %e, %Y' }
let(:alpha_date) { Date.parse('2022-01-01') }
let(:alpha_age) { alpha_date.strftime(format_str) }
Expand All @@ -18,8 +18,8 @@
before do
allow(NextRails::GemInfo).to receive(:all).and_return(
[
mock_gem.new('alpha', '0.0.1', alpha_age, mock_version.new('0.0.2', bravo_age), false, alpha_date, false),
mock_gem.new('bravo', '0.2.0', bravo_age, mock_version.new('0.2.2', charlie_age), false, bravo_date, true)
mock_gem.new('alpha', '0.0.1', alpha_age, mock_version.new('0.0.2', bravo_age), false, alpha_date, false, false),
mock_gem.new('bravo', '0.2.0', bravo_age, mock_version.new('0.2.2', charlie_age), false, bravo_date, true, false)
]
)
end
Expand All @@ -37,6 +37,7 @@
allow($stdout).to receive(:puts).with('')
allow($stdout).to receive(:puts).with(<<-EO_MULTLINE_STRING)
#{NextRails::Tint('1').yellow} gems are sourced from git
#{NextRails::Tint('0').yellow} gems are sourced from a local path
#{NextRails::Tint('2').red} of the 2 gems are out-of-date (100%)
EO_MULTLINE_STRING
end
Expand All @@ -45,10 +46,11 @@
context 'when writing JSON output' do
it 'JSON is correctly formatted' do
gems = NextRails::GemInfo.all
out_of_date_gems = gems.reject(&:up_to_date?).sort_by(&:created_at)
sourced_locally = gems.select(&:sourced_locally?)
out_of_date_gems = (gems - sourced_locally).reject(&:up_to_date?).sort_by(&:created_at)
sourced_from_git = gems.select(&:sourced_from_git?)

expect(NextRails::BundleReport.build_json(out_of_date_gems, gems.count, sourced_from_git.count)).to eq(
expect(NextRails::BundleReport.build_json(out_of_date_gems, gems.count, sourced_from_git.count, sourced_locally.count)).to eq(
{
outdated_gems: [
{ name: 'alpha', installed_version: '0.0.1', installed_age: alpha_age, latest_version: '0.0.2',
Expand All @@ -57,11 +59,38 @@
latest_age: charlie_age }
],
sourced_from_git_count: sourced_from_git.count,
sourced_locally_count: sourced_locally.count,
total_gem_count: gems.count
}
)
end
end

context 'when a gem is sourced from a local path' do
let(:delta_date) { Date.parse('2022-04-04') }
let(:delta_age) { delta_date.strftime(format_str) }

before do
allow(NextRails::GemInfo).to receive(:all).and_return(
[
mock_gem.new('alpha', '0.0.1', alpha_age, mock_version.new('0.0.2', bravo_age), false, alpha_date, false, false),
# same name as a public gem, but sourced locally and out-of-date
mock_gem.new('delta', '0.1.0', delta_age, mock_version.new('0.1.2', charlie_age), false, delta_date, false, true)
]
)
end

it 'excludes the local gem from the out-of-date list and counts it separately' do
gems = NextRails::GemInfo.all
sourced_locally = gems.select(&:sourced_locally?)
out_of_date_gems = (gems - sourced_locally).reject(&:up_to_date?).sort_by(&:created_at)

result = NextRails::BundleReport.build_json(out_of_date_gems, gems.count, 0, sourced_locally.count)

expect(result[:outdated_gems].map { |g| g[:name] }).to eq(['alpha'])
expect(result[:sourced_locally_count]).to eq(1)
end
end
end

describe ".rails_compatibility" do
Expand Down
34 changes: 34 additions & 0 deletions spec/next_rails/gem_info_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,40 @@
end
end

describe "#sourced_locally?" do
let(:source) { nil }
let(:spec) do
Gem::Specification.new do |s|
s.date = release_date
s.version = "1.0.0"
end.tap { |s| s.source = source }
end

context "when the gem is sourced from a local path" do
let(:source) { Bundler::Source::Path.new("path" => "engines/foo") }

it "is true" do
expect(subject.sourced_locally?).to be(true)
end
end

context "when the gem is sourced from git" do
let(:source) { Bundler::Source::Git.new("uri" => "https://example.com/foo.git") }

it "is false (git is reported separately)" do
expect(subject.sourced_locally?).to be(false)
end
end

context "when the gem is sourced from rubygems" do
let(:source) { Bundler::Source::Rubygems.new }

it "is false" do
expect(subject.sourced_locally?).to be(false)
end
end
end

describe "#find_latest_compatible" do
let(:mock_gem) { Struct.new(:name, :version) }

Expand Down
Loading