Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* Your changes/patches go here.

- [FEATURE: `bundle_report compatibility --offline` lists the incompatible gems without looking up compatible versions on rubygems.org](https://github.com/fastruby/next_rails/pull/201)

# v1.7.0 / 2026-08-05 [(commits)](https://github.com/fastruby/next_rails/compare/v1.6.0...v1.7.0)

- [FEATURE: Add support for SimpleCov 1.0](https://github.com/fastruby/next_rails/pull/196)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ bundle_report outdated --json
# Show gems incompatible with Rails 7.2
bundle_report compatibility --rails-version=7.2

# Show gems incompatible with Rails 7.2 without looking up compatible versions on rubygems.org
bundle_report compatibility --rails-version=7.2 --offline

# Show gems incompatible with Ruby 3.3
bundle_report compatibility --ruby-version=3.3

Expand Down
4 changes: 2 additions & 2 deletions lib/next_rails/bundle_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ def ruby_compatibility(ruby_version: nil)
puts RubyVersionCompatibility.new(options: options).generate
end

def rails_compatibility(rails_version: nil, include_rails_gems: nil)
def rails_compatibility(rails_version: nil, include_rails_gems: nil, offline: false)
return unless rails_version

options = { rails_version: rails_version, include_rails_gems: include_rails_gems }
options = { rails_version: rails_version, include_rails_gems: include_rails_gems, offline: offline }
puts RailsVersionCompatibility.new(options: options).generate
end

Expand Down
22 changes: 21 additions & 1 deletion lib/next_rails/bundle_report/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ def validate_arguments(argv)
raise ArgumentError, "Invalid Ruby version format. Example: --ruby-version=3.3"
end
end

# The other reports have no offline mode: outdated and ruby_check always call rubygems.org
if option_given?(argv, "--offline") && (report_type != "compatibility" || option_given?(argv, "--ruby-version"))
raise ArgumentError, "--offline only applies to the compatibility report with --rails-version."
end
end

# OptionParser also accepts a unique prefix of a long option (--off for --offline),
# so an exact match would miss those.
def option_given?(argv, option)
argv.any? do |arg|
name = arg.split("=", 2).first
name.length > 2 && option.start_with?(name)
end
end

def run
Expand All @@ -49,6 +63,7 @@ def parse_options

Examples:
#{$0} compatibility --rails-version 5.0
#{$0} compatibility --rails-version 5.0 --offline
#{$0} compatibility --ruby-version 3.3
#{$0} outdated
#{$0} outdated --json
Expand Down Expand Up @@ -77,6 +92,10 @@ def parse_options
options[:include_rails_gems] = true
end

opts.on("--offline", "List incompatible gems without looking up compatible versions on rubygems.org (defaults to false)") do
options[:offline] = true
end

opts.on("--json", "Output JSON in outdated report (defaults to false)") do
options[:format] = "json"
end
Expand Down Expand Up @@ -110,7 +129,8 @@ def execute_report(report_type, options)
else
NextRails::BundleReport.rails_compatibility(
rails_version: options.fetch(:rails_version, "5.0"),
include_rails_gems: options.fetch(:include_rails_gems, false)
include_rails_gems: options.fetch(:include_rails_gems, false),
offline: options.fetch(:offline, false)
)
end
end
Expand Down
22 changes: 20 additions & 2 deletions lib/next_rails/bundle_report/rails_version_compatibility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,29 @@ def generate

def incompatible_gems_by_state
@incompatible_gems_by_state ||= begin
incompatible_gems.each { |gem| gem.find_latest_compatible(rails_version: rails_version) }
incompatible_gems.group_by { |gem| gem.state(rails_version) }
if offline
# No lookup ran, so GemInfo#state would call every gem :incompatible
incompatible_gems.empty? ? {} : { not_checked: incompatible_gems }
else
incompatible_gems.each { |gem| gem.find_latest_compatible(rails_version: rails_version) }
incompatible_gems.group_by { |gem| gem.state(rails_version) }
end
end
end

private

def erb_output
template = <<-ERB
<% if incompatible_gems_by_state[:not_checked] -%>
<%= NextRails::Tint("=> Incompatible with Rails #{rails_version} (compatible versions not looked up):").white.bold %>
<%= NextRails::Tint("These gems will need to be upgraded or replaced before upgrading to Rails #{rails_version}.").italic %>

<% incompatible_gems_by_state[:not_checked].each do |gem| -%>
<%= gem_header(gem) %>
<% end -%>

<% end -%>
<% if incompatible_gems_by_state[:found_compatible] -%>
<%= NextRails::Tint("=> Incompatible with Rails #{rails_version} (with new versions that are compatible):").white.bold %>
<%= NextRails::Tint("These gems will need to be upgraded before upgrading to Rails #{rails_version}.").italic %>
Expand Down Expand Up @@ -83,4 +97,8 @@ def rails_version
def include_rails_gems
@options[:include_rails_gems]
end

def offline
@options[:offline]
end
end
15 changes: 15 additions & 0 deletions spec/next_rails/bundle_report/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,24 @@

it 'calls rails_compatibility if called with compatibility with rails-version option' do
expect(NextRails::BundleReport).to receive(:rails_compatibility)
.with(rails_version: "8.0.0", include_rails_gems: false, offline: false)
described_class.new(["compatibility", "--rails-version=8.0.0"]).run
end

it 'passes offline to rails_compatibility if called with the offline option' do
expect(NextRails::BundleReport).to receive(:rails_compatibility)
.with(rails_version: "8.0.0", include_rails_gems: false, offline: true)
described_class.new(["compatibility", "--rails-version=8.0.0", "--offline"]).run
end

it 'raises if offline is used with a report that has no offline mode' do
[["outdated", "--offline"], ["ruby_check", "--rails-version=8.0.0", "--offline"],
["compatibility", "--ruby-version=3.4.0", "--offline"], ["outdated", "--off"]].each do |argv|
expect { described_class.new(argv) }
.to raise_error(ArgumentError, /--offline only applies to the compatibility report with --rails-version/)
end
end

it 'calls ruby_compatibility if called with compatibility with ruby-version option' do
expect(NextRails::BundleReport).to receive(:ruby_compatibility)
described_class.new(["compatibility", "--ruby-version=3.4.0"]).run
Expand Down
50 changes: 50 additions & 0 deletions spec/next_rails/bundle_report/rails_version_compatibility_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,56 @@
expect(output).to include("- upgrade to 5.8.0")
expect(output).to include("gems incompatible with Rails 7.1")
end

it "lists incompatible gems without looking up newer versions when offline" do
specification = Gem::Specification.new do |s|
s.name = "audited"
s.version = "5.1.0"
s.add_dependency "rails", ">= 5.0", "< 7.1"
end
audited = NextRails::GemInfo.new(specification)

expect(audited).not_to receive(:find_latest_compatible)

output =
NextRails::BundleReport::RailsVersionCompatibility.new(
gems: [audited],
options: { rails_version: "7.1", include_rails_gems: false, offline: true }
).generate

expect(output).to include("Incompatible with Rails 7.1 (compatible versions not looked up):")
expect(output).to include("audited 5.1.0")
expect(output).to include("gems incompatible with Rails 7.1")
end

it "prints only the count when offline and nothing is incompatible" do
output =
NextRails::BundleReport::RailsVersionCompatibility.new(
gems: [],
options: { rails_version: "7.1", include_rails_gems: false, offline: true }
).generate

expect(output).not_to include("=> Incompatible")
expect(output).to include("gems incompatible with Rails 7.1")
end
end

describe "#incompatible_gems_by_state" do
it "groups the gems under :not_checked when offline" do
specification = Gem::Specification.new do |s|
s.name = "audited"
s.version = "5.1.0"
s.add_dependency "rails", ">= 5.0", "< 7.1"
end
audited = NextRails::GemInfo.new(specification)

by_state = NextRails::BundleReport::RailsVersionCompatibility.new(
gems: [audited],
options: { rails_version: "7.1", offline: true }
).incompatible_gems_by_state

expect(by_state).to eq(not_checked: [audited])
end
end
end

Loading