diff --git a/CHANGELOG.md b/CHANGELOG.md index 0620505..30058d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # main [(unreleased)](https://github.com/fastruby/next_rails/compare/v1.0.4...main) +* [FEATURE: Try to find the latest **compatible** version of a gem if the latest version is not compatible with the desired Rails version when checking compatibility](https://github.com/fastruby/next_rails/pull/49) + # v1.0.5 / 2022-03-29 [(commits)](https://github.com/fastruby/next_rails/compare/v1.0.4...v1.0.5) * [FEATURE: Initialize the Gemfile.next.lock to avoid major version jumps when used without an initial Gemfile.next.lock](https://github.com/fastruby/next_rails/pull/25) @@ -14,6 +16,7 @@ * [BUGFIX: Update README.md to better document this `ten_years_rails` fork](https://github.com/fastruby/next_rails/pull/11) * [BUGFIX: Make ActionView an optional dependency](https://github.com/fastruby/next_rails/pull/6) + # v1.0.2 / 2020-01-20 # v1.0.1 / 2019-07-26 diff --git a/lib/next_rails/bundle_report.rb b/lib/next_rails/bundle_report.rb index 11c89d4..27bcb13 100644 --- a/lib/next_rails/bundle_report.rb +++ b/lib/next_rails/bundle_report.rb @@ -8,22 +8,19 @@ class BundleReport def self.compatibility(rails_version:, include_rails_gems:) incompatible_gems = NextRails::GemInfo.all.reject do |gem| gem.compatible_with_rails?(rails_version: rails_version) || (!include_rails_gems && gem.from_rails?) - end.sort_by do |gem| - [ - gem.latest_version.compatible_with_rails?(rails_version: rails_version) ? 0 : 1, - gem.name - ].join("-") - end + end.sort_by { |gem| gem.name } + + incompatible_gems.each { |gem| gem.find_latest_compatible(rails_version: rails_version) } incompatible_gems_by_state = incompatible_gems.group_by { |gem| gem.state(rails_version) } template = <<~ERB - <% if incompatible_gems_by_state[:latest_compatible] -%> + <% if incompatible_gems_by_state[:found_compatible] -%> <%= "=> Incompatible with Rails #{rails_version} (with new versions that are compatible):".white.bold %> <%= "These gems will need to be upgraded before upgrading to Rails #{rails_version}.".italic %> - <% incompatible_gems_by_state[:latest_compatible].each do |gem| -%> - <%= gem_header(gem) %> - upgrade to <%= gem.latest_version.version %> + <% incompatible_gems_by_state[:found_compatible].each do |gem| -%> + <%= gem_header(gem) %> - upgrade to <%= gem.latest_compatible_version.version %> <% end -%> <% end -%> diff --git a/lib/next_rails/gem_info.rb b/lib/next_rails/gem_info.rb index 0a2433d..dc8b9f2 100644 --- a/lib/next_rails/gem_info.rb +++ b/lib/next_rails/gem_info.rb @@ -28,13 +28,28 @@ def state(_) end end + RAILS_GEMS = [ + "rails", + "activemodel", + "activerecord", + "actionmailer", + "actioncable", + "actionpack", + "actionview", + "activejob", + "activestorage", + "activesupport", + "railties", + ].freeze + def self.all Gem::Specification.each.map do |gem_specification| new(gem_specification) end end - attr_reader :gem_specification, :version, :name + attr_reader :gem_specification, :version, :name, :latest_compatible_version + def initialize(gem_specification) @gem_specification = gem_specification @version = gem_specification.version @@ -57,59 +72,69 @@ def up_to_date? version == latest_version.version end + def from_rails? + RAILS_GEMS.include?(name) + end + def state(rails_version) if compatible_with_rails?(rails_version: rails_version) :compatible - elsif latest_version.compatible_with_rails?(rails_version: rails_version) - :latest_compatible - elsif latest_version.version == "NOT FOUND" + elsif latest_compatible_version.version == "NOT FOUND" :no_new_version + elsif latest_compatible_version + :found_compatible else :incompatible end end - def latest_version - @latest_version ||= begin - latest_gem_specification = Gem.latest_spec_for(name) - if latest_gem_specification - GemInfo.new(latest_gem_specification) - else - NullGemInfo.new - end - end - end - - def compatible_with_rails?(rails_version: Gem::Version.new("5.0")) + def compatible_with_rails?(rails_version:) unsatisfied_rails_dependencies(rails_version: rails_version).empty? end def unsatisfied_rails_dependencies(rails_version:) - rails_dependencies = gem_specification.runtime_dependencies.select {|dependency| rails_gems.include?(dependency.name) } + spec_compatible_with_rails?(specification: gem_specification, rails_version: rails_version) + end - rails_dependencies.reject do |rails_dependency| - rails_dependency.requirement.satisfied_by?(Gem::Version.new(rails_version)) + def find_latest_compatible(rails_version:) + dependency = Gem::Dependency.new(@name) + fetcher = Gem::SpecFetcher.new + + # list all available data for released gems + list, errors = fetcher.available_specs(:released) + + specs = [] + # filter only specs for the current gem and older versions + list.each do |source, gem_tuples| + gem_tuples.each do |gem_tuple| + if gem_tuple.name == @name && gem_tuple.version > @version + specs << source.fetch_spec(gem_tuple) + end + end end - end - def from_rails? - rails_gems.include?(name) + # if nothing is found, consider gem incompatible + if specs.empty? + @latest_compatible_version = NullGemInfo.new + return + end + + # if specs are found, look for the first one from that is compatible + # with the desired rails version starting from the end + specs.reverse.each do |spec| + if spec_compatible_with_rails?(specification: spec, rails_version: rails_version).empty? + @latest_compatible_version = spec + break + end + end end - private def rails_gems - [ - "rails", - "activemodel", - "activerecord", - "actionmailer", - "actioncable", - "actionpack", - "actionview", - "activejob", - "activestorage", - "activesupport", - "railties", - ] + def spec_compatible_with_rails?(specification:, rails_version:) + rails_dependencies = specification.runtime_dependencies.select {|dependency| RAILS_GEMS.include?(dependency.name) } + + rails_dependencies.reject do |rails_dependency| + rails_dependency.requirement.satisfied_by?(Gem::Version.new(rails_version)) + end end end end