gemcompat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e14c0dd99cb943fbea995ea0070135c9635d405c5c7615f18d7bcc76437813b4
4
+ data.tar.gz: f6d158b81c92c6f866d2cfafbb671977bf2f2c1efb9211e8e150b90ae90ad185
5
+ SHA512:
6
+ metadata.gz: a27e17ba1ccd0f8cfa79d3e7053dbc124c1315fbe5ecb8954fe65466dc0ec6268c50d47de21a79e37bc0a7e84d05eed5c6673e82b97e16508c770c02c72409ba
7
+ data.tar.gz: 8d1a8eb3853905de9ac9b97db382f3fef71cf32685c0f4cbb7985c72425317e49f20bd03a93fc007a3e43982e051ebe2b6777241187c97328e70fd4d2a7a6fd6
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://docs.rubocop.org/rubocop/configuration
11
+
12
+ AllCops:
13
+ NewCops: enable
14
+ TargetRubyVersion: 2.7
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.8
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'bundler'
6
+
7
+ group(:development, :test) do
8
+ gem 'rspec'
9
+ gem 'rubocop'
10
+ end
11
+
12
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,61 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gemcompat (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ diff-lcs (1.5.1)
11
+ json (2.7.1)
12
+ language_server-protocol (3.17.0.3)
13
+ parallel (1.24.0)
14
+ parser (3.3.0.5)
15
+ ast (~> 2.4.1)
16
+ racc
17
+ racc (1.7.3)
18
+ rainbow (3.1.1)
19
+ regexp_parser (2.9.0)
20
+ rexml (3.2.6)
21
+ rspec (3.13.0)
22
+ rspec-core (~> 3.13.0)
23
+ rspec-expectations (~> 3.13.0)
24
+ rspec-mocks (~> 3.13.0)
25
+ rspec-core (3.13.0)
26
+ rspec-support (~> 3.13.0)
27
+ rspec-expectations (3.13.0)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.13.0)
30
+ rspec-mocks (3.13.0)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.13.0)
33
+ rspec-support (3.13.1)
34
+ rubocop (1.62.1)
35
+ json (~> 2.3)
36
+ language_server-protocol (>= 3.17.0)
37
+ parallel (~> 1.10)
38
+ parser (>= 3.3.0.2)
39
+ rainbow (>= 2.2.2, < 4.0)
40
+ regexp_parser (>= 1.8, < 3.0)
41
+ rexml (>= 3.2.5, < 4.0)
42
+ rubocop-ast (>= 1.31.1, < 2.0)
43
+ ruby-progressbar (~> 1.7)
44
+ unicode-display_width (>= 2.4.0, < 3.0)
45
+ rubocop-ast (1.31.2)
46
+ parser (>= 3.3.0.4)
47
+ ruby-progressbar (1.13.0)
48
+ unicode-display_width (2.5.0)
49
+
50
+ PLATFORMS
51
+ ruby
52
+ x86_64-linux
53
+
54
+ DEPENDENCIES
55
+ bundler
56
+ gemcompat!
57
+ rspec
58
+ rubocop
59
+
60
+ BUNDLED WITH
61
+ 2.3.22
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ This project is maintained by the team at [Infield](https://infield.ai), you can reach us at hello@infield.ai.
2
+
3
+ # What this project does
4
+
5
+ ## TLDR
6
+ Use this script to check whether your app relies on gems which are
7
+ silently incompatible with a package upgrade. For example, check if
8
+ you're compatible with Rails 7.1 like this:
9
+
10
+ ```
11
+ gemcompat --package rails --target-version 7.1 --lockfile Gemfile.lock
12
+ ```
13
+
14
+ ## Package support
15
+
16
+ gemcompat supports checking the following upgrades:
17
+
18
+ |package|target version|
19
+ |------|-------|
20
+ |rails|7.1|
21
+ |rails|6.1|
22
+
23
+ Please contribute to the database to support more packages and targets!
24
+
25
+ ## Motivation
26
+ Upgrading Rails means first upgrading other dependencies that block
27
+ the way. Some of these will have explicit incompatibilities documented
28
+ in their gemspecs. If you try to run `bundle update rails` without
29
+ upgrading these gems you'll see an error that bundler couldn't resolve
30
+ the upgrade.
31
+
32
+ Other gems leave an open-ended rails requirement in their
33
+ gemspec. This means bundler will allow a new version of Rails
34
+ alongside your current version of those gems, but there's no guarantee
35
+ from the maintainer that the two are compatible. This can lead to
36
+ subtle bugs that don't get caught until production.
37
+
38
+ For example, take the popular `data-migrate` gem. Its gemspec requires
39
+ activerecord >= 6.1 with no upper bound. Looking at the changelog,
40
+ though, you'll see that support for Rails 7.1 wasn't added until
41
+ version 9.2.0. Older versions will hit this exception when someone
42
+ tries to run migrations under the latest Rails, even though bundler
43
+ installs the package with no warning.
44
+
45
+ These "silent" incompatibilities are often documented in the
46
+ maintainer’s changelog even though they’re not available to
47
+ bundler. This project serves as a repository for storing these
48
+ incompatibilities as we discover them, and includes code to
49
+ automatically check your project against the database.
50
+
51
+ # Installation
52
+ ```
53
+ gem install gemcompat
54
+ ```
55
+
56
+ # Contributing
57
+
58
+ We welcome contributions, both to the dataset and to the code itself.
59
+
60
+ ## Adding an incompatibility
61
+ Incompatibilities are stored in the `data/` directory, with one folder
62
+ per package and one file per target version of that package.
63
+
64
+ For instance, gems which are incompatible with Rails 7.1 should be
65
+ documented in `data/rails/7_1.yaml`.
66
+
67
+ Here's an example from rails/7_1.yaml
68
+
69
+ ```yaml
70
+ activerecord-import:
71
+ :first_compatible_version: 1.5.0
72
+ ```
73
+
74
+ This means that the activerecord-import gem needs to be at least
75
+ version 1.5.0 in order to be compatible with Rails 7.1
76
+
77
+ ## Development
78
+
79
+ Getting started should be easy. You'll need to have Ruby 3.3 installed
80
+ and run bundle to install the developer dependencies. You can run specs with:
81
+
82
+ ```
83
+ bundle exec rspec
84
+ ```
@@ -0,0 +1,107 @@
1
+ ---
2
+ actionpack-page_caching:
3
+ :first_compatible_version: 1.2.4
4
+ active_record_extended:
5
+ :first_compatible_version: 2.0.3
6
+ activerecord-import:
7
+ :first_compatible_version: 1.0.3
8
+ acts_as_paranoid:
9
+ :first_compatible_version: 0.7.1
10
+ administrate:
11
+ :first_compatible_version: 0.18.0
12
+ airbrake:
13
+ :first_compatible_version: 11.0.3
14
+ ancestry:
15
+ :first_compatible_version: 4.0.0
16
+ audited:
17
+ :first_compatible_version: 5.0.0
18
+ blazer:
19
+ :first_compatible_version: 2.4.1
20
+ bootstrap_form:
21
+ :first_compatible_version: 5.0.0
22
+ brakeman:
23
+ :first_compatible_version: 5.1.0
24
+ bullet:
25
+ :first_compatible_version: 6.1.1
26
+ cancancan:
27
+ :first_compatible_version: 3.2.0
28
+ cypress-rails:
29
+ :first_compatible_version: 0.4.2
30
+ data_migrate:
31
+ :first_compatible_version: 6.7.0
32
+ ddtrace:
33
+ :first_compatible_version: 0.44.0
34
+ deep_pluck:
35
+ :first_compatible_version: 1.1.5
36
+ devise:
37
+ :first_compatible_version: 4.8.0
38
+ enumerize:
39
+ :first_compatible_version: 2.4.0
40
+ factory_bot:
41
+ :first_compatible_version: 6.4.5
42
+ factory_bot_rails:
43
+ :first_compatible_version: 6.4.3
44
+ formtastic:
45
+ :first_compatible_version: 4.0.0
46
+ globalize:
47
+ :first_compatible_version: 6.2.1
48
+ grape:
49
+ :first_compatible_version: 1.5.2
50
+ groupdate:
51
+ :first_compatible_version: 6.0.1
52
+ haml_coffee_assets:
53
+ :first_compatible_version: 1.20.0
54
+ has_scope:
55
+ :first_compatible_version: 0.8.0
56
+ honeybadger:
57
+ :first_compatible_version: 4.7.0
58
+ identity_cache:
59
+ :first_compatible_version: 1.1.0
60
+ kt-paperclip:
61
+ :first_compatible_version: 7.0.1
62
+ lockbox:
63
+ :first_compatible_version: 0.6.2
64
+ moneta:
65
+ :first_compatible_version: 1.6.0
66
+ money-rails:
67
+ :first_compatible_version: 1.13.4
68
+ newrelic_rpm:
69
+ :first_compatible_version: 6.15.0
70
+ occams-record:
71
+ :first_compatible_version: 1.2.1
72
+ packs-rails:
73
+ :first_compatible_version: 0.0.4
74
+ paper_trail:
75
+ :first_compatible_version: 11.1.0
76
+ parallel_tests:
77
+ :first_compatible_version: 3.5.1
78
+ protected_attributes_continued:
79
+ :first_compatible_version: 1.8.2
80
+ ransack:
81
+ :first_compatible_version: 2.4.0
82
+ responders:
83
+ :first_compatible_version: 3.1.0
84
+ rspec-rails:
85
+ :first_compatible_version: 4.1.0
86
+ rubocop-rails:
87
+ :first_compatible_version: 2.12.3
88
+ sequel:
89
+ :first_compatible_version: 5.40.0
90
+ shoulda-matchers:
91
+ :first_compatible_version: 5.0.0
92
+ simple_form:
93
+ :first_compatible_version: 5.1.0
94
+ sprockets-rails:
95
+ :first_compatible_version: 3.2.2
96
+ strong_migrations:
97
+ :first_compatible_version: 0.7.4
98
+ temple:
99
+ :first_compatible_version: 0.9.0
100
+ test-prof:
101
+ :first_compatible_version: 0.10.1
102
+ validates_timeliness:
103
+ :first_compatible_version: 5.0.0
104
+ view_component:
105
+ :first_compatible_version: 2.2.0
106
+ web-console:
107
+ :first_compatible_version: 4.1.0
@@ -0,0 +1,49 @@
1
+ ---
2
+ activerecord-import:
3
+ :first_compatible_version: 1.5.0
4
+ anycable-rails:
5
+ :first_compatible_version: 1.4.1
6
+ blazer:
7
+ :first_compatible_version: 3.0.1
8
+ bullet:
9
+ :first_compatible_version: 7.1.2
10
+ data_migrate:
11
+ :first_compatible_version: 9.2.0
12
+ database_cleaner-active_record:
13
+ :first_compatible_version: 2.1.0
14
+ devise:
15
+ :first_compatible_version: 4.9.3
16
+ formtastic:
17
+ :first_compatible_version: 5.0.0
18
+ grape:
19
+ :first_compatible_version: 2.0.0
20
+ has_scope:
21
+ :first_compatible_version: 0.8.2
22
+ honeybadger:
23
+ :first_compatible_version: 5.1.0
24
+ lockbox:
25
+ :first_compatible_version: 1.3.1
26
+ paper_trail:
27
+ :first_compatible_version: 15.1.0
28
+ public_activity:
29
+ :first_compatible_version: 3.0.0
30
+ ransack:
31
+ :first_compatible_version: 4.1.0
32
+ responders:
33
+ :first_compatible_version: 3.1.1
34
+ rollbar:
35
+ :first_compatible_version: 3.4.1
36
+ rspec-rails:
37
+ :first_compatible_version: 6.1.0
38
+ rubocop-rails:
39
+ :first_compatible_version: 2.22.2
40
+ shoulda-matchers:
41
+ :first_compatible_version: 6.0.0
42
+ sidekiq:
43
+ :first_compatible_version: 7.1.5
44
+ simple_form:
45
+ :first_compatible_version: 5.3.0
46
+ slim:
47
+ :first_compatible_version: 5.2.0
48
+ web-console:
49
+ :first_compatible_version: 4.2.1
data/exe/gemcompat ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gemcompat'
5
+ Gemcompat::Runner.new.run!
data/gemcompat.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'gemcompat/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'gemcompat'
9
+ spec.version = Gemcompat::VERSION
10
+ spec.authors = ['Steve Pike']
11
+ spec.email = ['steve@infield.ai']
12
+
13
+ spec.summary = 'Check your app for silent incompatibilities with new gem versions'
14
+ spec.description = 'Not all gem incompatibilities get reported in gemspecs. This project documents them so you can check ahead of an upgrade.'
15
+ spec.homepage = 'https://github.com/infieldai/gemcompat'
16
+ spec.license = 'MIT'
17
+
18
+ spec.required_ruby_version = '>= 2.7'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ end
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.metadata['rubygems_mfa_required'] = 'true'
28
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gemcompat
4
+ # Check a lockfile for compatibility
5
+ class CompatibilityChecker
6
+ attr_reader :package_incompatibilities, :package_name, :target_version, :found_incompatibilities
7
+ DATA_DIR = Pathname.new(__FILE__).join('../../../data')
8
+
9
+ def initialize(package_name:, target_version:)
10
+ @package_name = package_name
11
+ @target_version = target_version
12
+ load_package_data!(package_name: package_name, target_version: target_version)
13
+ end
14
+
15
+ def welcome(package_name:, target_version:)
16
+ puts "Checking for undocumented incompatibilities with #{package_name} v#{target_version}\n\n"
17
+ end
18
+
19
+ def package_version_to_path_part(version)
20
+ version&.gsub('.', '_')
21
+ end
22
+
23
+ def incompatibility_datafile(package_name:, target_version:)
24
+ DATA_DIR.join("#{package_name}/#{package_version_to_path_part(target_version)}.yaml")
25
+ end
26
+
27
+ def load_package_data!(package_name:, target_version:)
28
+ path = incompatibility_datafile(package_name: package_name, target_version: target_version)
29
+ @package_incompatibilities = YAML.load_file(path).then do |data|
30
+ data.transform_values { |entry| Gem::Version.new(entry[:first_compatible_version]) }
31
+ end
32
+ rescue Errno::ENOENT
33
+ puts "#{package_name} v#{target_version} not supported yet"
34
+ exit(1)
35
+ end
36
+
37
+ def report(found_incompatibilities: @found_incompatibilities)
38
+ welcome(package_name: package_name, target_version: target_version)
39
+
40
+ if found_incompatibilities.empty?
41
+ puts 'No incompatibilities found'
42
+ else
43
+ found_incompatibilities.each do |i|
44
+ puts "#{i[:name]}: Using #{i[:using_version]}. Upgrade to #{i[:required_version]}"
45
+ end
46
+ end
47
+ end
48
+
49
+ def parse_lockfile!(lockfile:)
50
+ @found_incompatibilities = []
51
+ Bundler::LockfileParser.new(lockfile).specs.each do |spec|
52
+ next unless (required_version = package_incompatibilities[spec.name]) && required_version > spec.version
53
+
54
+ found_incompatibilities << {
55
+ name: spec.name,
56
+ using_version: spec.version.to_s,
57
+ required_version: required_version.to_s,
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Gemcompat
6
+ # This class is the entrypoint for the gem. Initialized from ARGV.
7
+ class Runner
8
+ attr_reader :options
9
+
10
+ def run!(argv = ARGV)
11
+ parse_args!(argv)
12
+ checker = CompatibilityChecker.new(**options.slice(:package_name, :target_version))
13
+ checker.parse_lockfile!(lockfile: lockfile_contents)
14
+ checker.report
15
+ end
16
+
17
+ # rubocop:disable Metrics/MethodLength
18
+ # rubocop:disable Metrics/AbcSize
19
+ def parse_args!(argv = ARGV)
20
+ @options = {}
21
+
22
+ argv << '-h' if argv.empty?
23
+ OptionParser.new do |opts|
24
+ opts.banner = 'Usage: gemcompat --package rails --version 7.0 --lockfile ~/path/to/Gemfile.lock'
25
+
26
+ opts.on('-p', '--package PACKAGE_NAME', 'Package to check compatibility for') do |pkg|
27
+ @options[:package_name] = pkg
28
+ end
29
+
30
+ opts.on('-t', '--target-version VERSION_NUMBER', 'Version to check compatibility for') do |version|
31
+ @options[:target_version] = version
32
+ end
33
+
34
+ opts.on('-l', '--lockfile LOCKFILE', 'Path to Gemfile.lock') do |lockfile_path|
35
+ @options[:lockfile_path] = lockfile_path
36
+ end
37
+
38
+ opts.on_tail('-h', '--help', 'Show this message') do
39
+ puts opts
40
+ exit
41
+ end
42
+ end.parse!(argv)
43
+
44
+ validate_args!
45
+ end
46
+ # rubocop:enable Metrics/MethodLength
47
+
48
+ private
49
+
50
+ def fail_with(msg)
51
+ puts msg
52
+ exit(1)
53
+ end
54
+
55
+ def validate_args!
56
+ fail_with 'Must pass --package' unless options.include?(:package_name)
57
+
58
+ fail_with 'Must pass --target-version' unless options.include?(:target_version)
59
+
60
+ fail_with 'Must pass --lockfile' unless options.include?(:lockfile_path)
61
+
62
+ return if lockfile_exists?
63
+
64
+ fail_with "#{options[:lockfile_path]} does not exist"
65
+ end
66
+
67
+ def lockfile_exists?
68
+ File.exist?(options[:lockfile_path])
69
+ end
70
+
71
+ def lockfile_contents
72
+ File.read(options[:lockfile_path])
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gemcompat
4
+ VERSION = '0.1.0'
5
+ end
data/lib/gemcompat.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+ require 'yaml'
5
+
6
+ module Gemcompat
7
+ end
8
+
9
+ require_relative 'gemcompat/compatibility_checker'
10
+ require_relative 'gemcompat/runner'
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gemcompat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Steve Pike
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-04-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Not all gem incompatibilities get reported in gemspecs. This project
14
+ documents them so you can check ahead of an upgrade.
15
+ email:
16
+ - steve@infield.ai
17
+ executables:
18
+ - gemcompat
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".rspec"
23
+ - ".rubocop.yml"
24
+ - ".ruby-version"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - README.md
28
+ - data/rails/6_1.yaml
29
+ - data/rails/7_1.yaml
30
+ - exe/gemcompat
31
+ - gemcompat.gemspec
32
+ - lib/gemcompat.rb
33
+ - lib/gemcompat/compatibility_checker.rb
34
+ - lib/gemcompat/runner.rb
35
+ - lib/gemcompat/version.rb
36
+ homepage: https://github.com/infieldai/gemcompat
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ rubygems_mfa_required: 'true'
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '2.7'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.1.6
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Check your app for silent incompatibilities with new gem versions
60
+ test_files: []