rails_preflight 0.1.0 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +2 -2
- data/bin/rails-preflight +3 -1
- data/lib/rails_preflight/deprecation_analyzer.rb +14 -6
- data/lib/rails_preflight/report_generator.rb +4 -1
- data/lib/rails_preflight/upgrade_analyzer.rb +52 -16
- data/lib/rails_preflight/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18c100a721d1de1301b776d720f280062ca59598fdd5c54e07dde7719669e9d4
|
|
4
|
+
data.tar.gz: ad762983e63966629008b296c177b004ae2fea2782da1640046f4ae3d97a8f10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ad46f48a8c223d95e8e130ad05a85d8f00ccb5601633f347c41e0e5fb09a4ebb6f1644faa19a9bb3be11d21bf8cc17fe4946c9dd5b37797d8e2c0e294cbb880
|
|
7
|
+
data.tar.gz: 9ff337a9512221c2720d637b6444f880a2dc4250ad08344383eec45ac7d4148e13a0723aed05d6cf133b796b22e1c1a235cdc76c6d6067b73ddc9d89d610101f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (2026-10-13)
|
|
4
|
+
|
|
5
|
+
- Code inside Packwerk packs (`packs/<name>/app`, `lib`, `spec`, ...) is scanned like the app root, and rules scoped to `app/helpers` or `spec` apply inside packs too (#22, thanks @JuanVqz)
|
|
6
|
+
- `BUNDLE_GEMFILE=Gemfile.next rails-preflight` reads that Gemfile and its lockfile, so a dual-boot app can audit its next boot; one outside the app, as `bundle exec` sets it, is ignored (#23, #24, thanks @JuanVqz)
|
|
7
|
+
- A compiled binary under `bin/` no longer aborts the run, and scanned files are read as UTF-8 whatever the locale, so a file with non-ASCII characters no longer crashes the scan under `LANG=C` (#21, thanks @JuanVqz)
|
|
8
|
+
|
|
9
|
+
## 0.1.1 (2026-10-09)
|
|
10
|
+
|
|
11
|
+
- rubygems.org lookups also trust the certificates RubyGems ships for rubygems.org, so they work wherever `gem install` does, even when Ruby's OpenSSL can't find the system certificates
|
|
12
|
+
- Failed lookups are reported once per distinct error, under their own "rubygems.org lookup" entry with the gem names listed once, instead of the same error repeated for every gem under a second "Private Gems" card
|
|
13
|
+
- A certificate failure says what to do: behind a VPN or proxy that inspects HTTPS, point `SSL_CERT_FILE` at a bundle that includes its root certificate, or pass `--offline`
|
|
14
|
+
- The terminal warns when lookups fail
|
|
15
|
+
|
|
3
16
|
## 0.1.0 (2026-10-09)
|
|
4
17
|
|
|
5
18
|
First release. Point it at a Rails 5.0 – 8.1 app and a target version: it writes a report into the app as HTML, Markdown or JSON, plus a short terminal summary. Read-only and static.
|
data/README.md
CHANGED
|
@@ -97,12 +97,12 @@ It knows Rails 5.0 to 8.1 (`database/compatibility.yml`); the default target is
|
|
|
97
97
|
|
|
98
98
|
The tool will analyze:
|
|
99
99
|
|
|
100
|
-
- `Gemfile` and `Gemfile.lock`
|
|
100
|
+
- `Gemfile` and `Gemfile.lock`, or the Gemfile `BUNDLE_GEMFILE` names and its lockfile (`BUNDLE_GEMFILE=Gemfile.next` for a dual-boot app)
|
|
101
101
|
- `.ruby-version`
|
|
102
102
|
- `Dockerfile` (if present)
|
|
103
103
|
- `db/schema.rb` (if present; an app on `db/structure.sql` gets a note that the schema checks were skipped)
|
|
104
104
|
- `config/application.rb` and `config/**/*.yml`
|
|
105
|
-
- `.rb`, `.erb`, `.haml`, `.slim` and `.rake` files under `app/`, `config/`, `db/`, `lib/`, `test/` and `spec
|
|
105
|
+
- `.rb`, `.erb`, `.haml`, `.slim` and `.rake` files under `app/`, `config/`, `db/`, `lib/`, `test/` and `spec/`, at the root and in each Packwerk pack (`packs/<name>/app/`, ...)
|
|
106
106
|
- Scripts and CI config (`bin/`, `script/`, Procfiles, Makefiles, CI workflows) for removed rake tasks
|
|
107
107
|
|
|
108
108
|
And generate:
|
data/bin/rails-preflight
CHANGED
|
@@ -16,6 +16,7 @@ if %w[-h --help].include?(ARGV[0])
|
|
|
16
16
|
puts "--format markdown|json: write rails_preflight_report.md or .json into the app instead of the HTML report."
|
|
17
17
|
puts "--stdout: print the report instead of writing it (progress goes to stderr), for pipes and coding agents."
|
|
18
18
|
puts "--fail-on blockers: exit 1 when anything blocks the upgrade or is already broken. --fail-on broken: only when something is already broken."
|
|
19
|
+
puts "BUNDLE_GEMFILE=Gemfile.next: read that Gemfile and its lockfile instead, for a dual-boot app. Ignored when it's outside APP_PATH."
|
|
19
20
|
exit
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -38,7 +39,8 @@ abort "Target must be a Rails version such as 7.2 (got #{target.inspect})." if t
|
|
|
38
39
|
project_path = ARGV[0] || Dir.pwd
|
|
39
40
|
|
|
40
41
|
begin
|
|
41
|
-
summary = RailsPreflight::UpgradeAnalyzer.new(target, project_path, offline: offline, format: format, stdout: stdout
|
|
42
|
+
summary = RailsPreflight::UpgradeAnalyzer.new(target, project_path, offline: offline, format: format, stdout: stdout,
|
|
43
|
+
gemfile: ENV["BUNDLE_GEMFILE"]).run
|
|
42
44
|
rescue RailsPreflight::Error => e
|
|
43
45
|
abort e.message
|
|
44
46
|
end
|
|
@@ -4,6 +4,8 @@ module RailsPreflight
|
|
|
4
4
|
DATA_PATH = File.expand_path('../../database/deprecations.yml', __dir__)
|
|
5
5
|
SCAN_DIRS = %w[app config db lib test spec].freeze
|
|
6
6
|
SCAN_EXTS = %w[.rb .erb .rake .haml .slim].freeze
|
|
7
|
+
# Packwerk packs (`packs/<name>/app`, `packs/<name>/spec`, ...) hold app code outside the root's SCAN_DIRS.
|
|
8
|
+
PACKS_DIR = 'packs'.freeze
|
|
7
9
|
# Lines that never run, by file type: [line comment, silent comment]. A silent comment hides the lines indented
|
|
8
10
|
# under it too (HAML `-#`, Slim `/`); an HTML comment (HAML `/`, Slim `/!` or `/[if IE]`) is text, but the lines
|
|
9
11
|
# under it still render and run. In templates `#main` is an element id, not a comment.
|
|
@@ -22,8 +24,9 @@ module RailsPreflight
|
|
|
22
24
|
# Snippets go into a report people print and share: on a line that names a secret, values are hidden.
|
|
23
25
|
SECRET_NAME = /secret|token|passw(?:or)?d|api_?key|access_?key|private_?key|credential/i
|
|
24
26
|
|
|
25
|
-
def initialize(root_path = Dir.pwd, database_path = DATA_PATH, target_rails: nil, current_rails: nil)
|
|
27
|
+
def initialize(root_path = Dir.pwd, database_path = DATA_PATH, target_rails: nil, current_rails: nil, lockfile_path: nil)
|
|
26
28
|
@root_path = root_path
|
|
29
|
+
@lockfile_path = lockfile_path || File.join(root_path, "Gemfile.lock")
|
|
27
30
|
@rules = YAML.load_file(database_path)
|
|
28
31
|
@target_rails = target_rails
|
|
29
32
|
@current_rails = current_rails
|
|
@@ -87,7 +90,8 @@ module RailsPreflight
|
|
|
87
90
|
found = []
|
|
88
91
|
compiled = rules.map { |rule| [Regexp.new(rule['pattern']), rule] }
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
code_globs = ["", "#{PACKS_DIR}/*/"].map { |prefix| File.join(@root_path, "#{prefix}{#{SCAN_DIRS.join(',')}}/**/*") }
|
|
94
|
+
files = Dir.glob(code_globs).select { |file| SCAN_EXTS.include?(File.extname(file)) }.to_h { |file| [file, 'code'] }
|
|
91
95
|
FILE_GLOBS.each do |kind, globs|
|
|
92
96
|
Dir.glob(globs.map { |glob| File.join(@root_path, glob) }).each { |file| files[file] ||= kind }
|
|
93
97
|
end
|
|
@@ -96,15 +100,19 @@ module RailsPreflight
|
|
|
96
100
|
next if File.directory?(file)
|
|
97
101
|
|
|
98
102
|
relative_path = Pathname.new(file).relative_path_from(Pathname.new(@root_path)).to_s
|
|
99
|
-
|
|
103
|
+
# A pack is laid out like the app root, so rules see `packs/billing/app/models/x.rb` as `app/models/x.rb`.
|
|
104
|
+
app_path = relative_path.sub(%r{\A#{PACKS_DIR}/[^/]+/}, '')
|
|
105
|
+
test = app_path.start_with?('test/', 'spec/')
|
|
100
106
|
|
|
101
|
-
under = ->(paths) { paths.any? { |path|
|
|
107
|
+
under = ->(paths) { paths.any? { |path| app_path == path || app_path.start_with?("#{path}/") } }
|
|
102
108
|
# A rule with `paths:` only applies under those directories or to those files.
|
|
103
109
|
applicable = compiled.select do |_regex, rule|
|
|
104
110
|
rule.fetch('files', ['code']).include?(kind) && (rule['paths'].nil? || under.(rule['paths']))
|
|
105
111
|
end
|
|
106
112
|
next if applicable.empty?
|
|
107
|
-
|
|
113
|
+
# UTF-8 whatever the locale (LANG=C reads US-ASCII); a compiled binary under bin/ (e.g. a Rust CLI) isn't text to scan.
|
|
114
|
+
content = File.read(file, encoding: "UTF-8")
|
|
115
|
+
next unless content.valid_encoding?
|
|
108
116
|
# Only `x.errors` counts under `needs_receiver_in:` (a bare `errors` in a helper or view is a local), and in a
|
|
109
117
|
# file that assigns `errors` or takes it as a parameter (`skip_bare_if_local:`), where the bare name is that local.
|
|
110
118
|
# ponytail: file-wide, so a model with an `errors` local in one method loses bare hits in the others.
|
|
@@ -207,7 +215,7 @@ module RailsPreflight
|
|
|
207
215
|
end
|
|
208
216
|
|
|
209
217
|
def lockfile
|
|
210
|
-
@lockfile ||= File.exist?(
|
|
218
|
+
@lockfile ||= File.exist?(@lockfile_path) ? File.read(@lockfile_path) : ""
|
|
211
219
|
end
|
|
212
220
|
|
|
213
221
|
end
|
|
@@ -280,7 +280,7 @@ module RailsPreflight
|
|
|
280
280
|
<div class="limits">
|
|
281
281
|
<% @summary.fetch(:cant_see, []).each do |entry| %>
|
|
282
282
|
<div class="limit">
|
|
283
|
-
<h3><%= h(entry[:section]) %></h3>
|
|
283
|
+
<h3><%= h(entry[:title] || entry[:section]) %></h3>
|
|
284
284
|
<p><%= code_html(entry[:message]) %></p>
|
|
285
285
|
<% names = Array(entry[:names]) %>
|
|
286
286
|
<% if names.any? %><p class="names"><%= h(names.join(" · ")) %></p><% end %>
|
|
@@ -477,7 +477,10 @@ module RailsPreflight
|
|
|
477
477
|
h(text).gsub(/(?<![\w&;])'(.+?)'(?![\w&])/) { "<code>#{$1}</code>" }
|
|
478
478
|
end
|
|
479
479
|
|
|
480
|
+
# A titled entry (the rubygems.org lookup) says what to do in its own message.
|
|
480
481
|
def cant_see_next(entry)
|
|
482
|
+
return if entry[:title]
|
|
483
|
+
|
|
481
484
|
case entry[:section]
|
|
482
485
|
when "Private Gems" then "Check each gemspec's Rails dependency, and bump them in the same step."
|
|
483
486
|
when "Gem Compatibility"
|
|
@@ -16,14 +16,19 @@ module RailsPreflight
|
|
|
16
16
|
# format: :html, :markdown or :json, written into the app as rails_preflight_report.<ext>.
|
|
17
17
|
# stdout: print the report instead of writing it, with progress on stderr, for pipes and coding agents.
|
|
18
18
|
# run returns the summary (nil when there's nothing to upgrade to), so the caller can set the exit code.
|
|
19
|
-
def initialize(target_rails = nil, project_path = Dir.pwd, offline: false, format: :html, stdout: false)
|
|
19
|
+
def initialize(target_rails = nil, project_path = Dir.pwd, offline: false, format: :html, stdout: false, gemfile: nil)
|
|
20
20
|
@target_rails = target_rails
|
|
21
21
|
@offline = offline
|
|
22
22
|
@format = format
|
|
23
23
|
@stdout = stdout
|
|
24
24
|
@out = stdout ? $stderr : $stdout
|
|
25
25
|
@project_path = project_path
|
|
26
|
-
|
|
26
|
+
# gemfile: another Gemfile, as BUNDLE_GEMFILE names it (Gemfile.next for dual boot); its lockfile sits next to it.
|
|
27
|
+
# Only one inside the app counts: `bundle exec` sets BUNDLE_GEMFILE to the Gemfile of wherever it was run.
|
|
28
|
+
gemfile = File.expand_path(gemfile, project_path) if gemfile
|
|
29
|
+
inside = gemfile&.start_with?(File.join(File.expand_path(project_path), ""))
|
|
30
|
+
@gemfile_path = inside ? gemfile : File.expand_path("Gemfile", project_path)
|
|
31
|
+
@lockfile_path = "#{@gemfile_path}.lock"
|
|
27
32
|
@rules = YAML.load_file(DATA_PATH)
|
|
28
33
|
end
|
|
29
34
|
|
|
@@ -52,7 +57,8 @@ module RailsPreflight
|
|
|
52
57
|
results << DockerAnalyzer.new(@project_path, checked_ruby: @eol_checked_ruby).run
|
|
53
58
|
results << GemAnalyzer.new(@project_path, lockfile_specs, hops: upgrade_hops.map { |hop| hop[:version] }, direct: lockfile&.dependencies&.keys || [],
|
|
54
59
|
last_releases: (@last_releases || {} unless @offline), target_released: target_rails_rules&.dig('released')).run
|
|
55
|
-
results << DeprecationAnalyzer.new(@project_path, target_rails: @target_rails, current_rails: current_rails&.to_s
|
|
60
|
+
results << DeprecationAnalyzer.new(@project_path, target_rails: @target_rails, current_rails: current_rails&.to_s,
|
|
61
|
+
lockfile_path: @lockfile_path).run
|
|
56
62
|
results << ConfigAnalyzer.new(@project_path, current_rails).run
|
|
57
63
|
results << DatabaseAnalyzer.new(@project_path, database_rules).run
|
|
58
64
|
|
|
@@ -100,12 +106,12 @@ module RailsPreflight
|
|
|
100
106
|
if current_rails.nil?
|
|
101
107
|
result[:status] = :warning
|
|
102
108
|
result[:confidence] = :low
|
|
103
|
-
result[:checks] << { message: "Could not read current Rails version from
|
|
109
|
+
result[:checks] << { message: "Could not read current Rails version from #{lockfile_name}.", status: :warning, kind: :unknown, fix_effort: :low }
|
|
104
110
|
elsif Gem::Version.new(current_rails.segments.first(2).join(".")) >= Gem::Version.new(@target_rails)
|
|
105
111
|
result[:status] = :warning
|
|
106
112
|
result[:checks] << { message: "Already on Rails #{current_rails}: #{@target_rails} is not an upgrade.", status: :passed }
|
|
107
113
|
else
|
|
108
|
-
result[:checks] << { message: "Rails #{current_rails} (
|
|
114
|
+
result[:checks] << { message: "Rails #{current_rails} (#{lockfile_name}) → #{@target_rails}", status: :passed, fix_effort: :low }
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
result
|
|
@@ -169,7 +175,7 @@ module RailsPreflight
|
|
|
169
175
|
return [File.read(ruby_version_file), ".ruby-version"] if File.exist?(ruby_version_file)
|
|
170
176
|
|
|
171
177
|
lock_ruby = File.exist?(@lockfile_path) && File.read(@lockfile_path)[/^RUBY VERSION\s+ruby (\S+)/, 1]
|
|
172
|
-
return [lock_ruby,
|
|
178
|
+
return [lock_ruby, lockfile_name] if lock_ruby
|
|
173
179
|
|
|
174
180
|
docker_ruby = DockerAnalyzer.new(@project_path).detect_ruby_version
|
|
175
181
|
[docker_ruby, "Dockerfile"] if docker_ruby
|
|
@@ -204,7 +210,7 @@ module RailsPreflight
|
|
|
204
210
|
result[:status] = :warning
|
|
205
211
|
result[:confidence] = :medium
|
|
206
212
|
result[:checks] << {
|
|
207
|
-
message: "No
|
|
213
|
+
message: "No #{lockfile_name} found. Skipping gem compatibility checks.",
|
|
208
214
|
status: :warning,
|
|
209
215
|
kind: :unknown,
|
|
210
216
|
fix_effort: :low
|
|
@@ -231,7 +237,7 @@ module RailsPreflight
|
|
|
231
237
|
end
|
|
232
238
|
|
|
233
239
|
if mixed_gems.any? && @offline
|
|
234
|
-
message = "#{mixed_gems.size} gems come from one
|
|
240
|
+
message = "#{mixed_gems.size} gems come from one #{lockfile_name} section that lists rubygems.org and #{mixed_remotes.join(', ')}; can't tell which are private with --offline"
|
|
235
241
|
result[:checks] << { message: message, status: :warning, kind: :unknown, names: mixed_gems, fix_effort: :unknown }
|
|
236
242
|
result[:status] = :warning
|
|
237
243
|
end
|
|
@@ -244,11 +250,20 @@ module RailsPreflight
|
|
|
244
250
|
private_gems.concat(mixed_gems.select { |name| found[name] == false })
|
|
245
251
|
@last_releases = dated_gems.filter_map { |name| [name, found[name]['version_created_at']] if found[name].is_a?(Hash) && found[name]['version_created_at'] }.to_h
|
|
246
252
|
if mixed_gems.any?
|
|
247
|
-
result[:checks] << { message: "Looked up #{mixed_gems.size} gems on rubygems.org, since
|
|
253
|
+
result[:checks] << { message: "Looked up #{mixed_gems.size} gems on rubygems.org, since #{lockfile_name} lists them under both rubygems.org and #{mixed_remotes.join(', ')}. Pass --offline to skip.", status: :passed, fix_effort: :low }
|
|
248
254
|
end
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
255
|
+
# One entry per distinct error: a missing certificate fails every lookup the same way.
|
|
256
|
+
inconclusive.group_by { |_, error| error }.each do |error, failed|
|
|
257
|
+
gems = failed.map(&:first).sort
|
|
258
|
+
@out.puts "Couldn't reach rubygems.org for #{gems.size} gems (#{error}). See \"Couldn't check\" in the report."
|
|
259
|
+
# A VPN or proxy that inspects HTTPS re-signs certificates with a root the OS trusts but Ruby's OpenSSL doesn't read.
|
|
260
|
+
advice = if error.include?("certificate verify failed")
|
|
261
|
+
"Behind a VPN or proxy that inspects HTTPS, point SSL_CERT_FILE at a certificate bundle that includes its root certificate, or pass --offline."
|
|
262
|
+
else
|
|
263
|
+
"Run it again once rubygems.org is reachable, or pass --offline."
|
|
264
|
+
end
|
|
265
|
+
result[:checks] << { message: "Couldn't reach rubygems.org for #{gems.size} #{gems.size == 1 ? 'gem' : 'gems'} (#{error}), so whether #{gems.size == 1 ? "it's private and when it was" : "they're private and when they were"} last released is unknown. #{advice}",
|
|
266
|
+
title: "rubygems.org lookup", status: :warning, kind: :unknown, names: gems, fix_effort: :unknown }
|
|
252
267
|
result[:status] = :warning
|
|
253
268
|
end
|
|
254
269
|
end
|
|
@@ -272,6 +287,7 @@ module RailsPreflight
|
|
|
272
287
|
found = {}
|
|
273
288
|
inconclusive = {}
|
|
274
289
|
lock = Mutex.new
|
|
290
|
+
store = rubygems_cert_store
|
|
275
291
|
|
|
276
292
|
Array.new([names.size, 8].min) do
|
|
277
293
|
Thread.new do
|
|
@@ -279,14 +295,16 @@ module RailsPreflight
|
|
|
279
295
|
begin
|
|
280
296
|
url = URI("https://rubygems.org/api/v1/gems/#{name}.json")
|
|
281
297
|
# Certificate errors are reported as inconclusive, never silently trusted.
|
|
282
|
-
response = Net::HTTP.start(url.host, url.port, use_ssl: true, open_timeout: 5, read_timeout: 5) do |http|
|
|
298
|
+
response = Net::HTTP.start(url.host, url.port, use_ssl: true, cert_store: store, open_timeout: 5, read_timeout: 5) do |http|
|
|
283
299
|
http.request(Net::HTTP::Get.new(url))
|
|
284
300
|
end
|
|
285
301
|
raise "HTTP #{response.code}" unless %w[200 404].include?(response.code) # e.g. 429 when rate limited
|
|
286
302
|
info = response.code == "200" && (JSON.parse(response.body.to_s) rescue {})
|
|
287
303
|
lock.synchronize { found[name] = info }
|
|
288
304
|
rescue StandardError => e
|
|
289
|
-
|
|
305
|
+
# The peer address changes per connection; without it, the same failure reads the same for every gem.
|
|
306
|
+
# The peer address changes per connection; without it, the same failure reads the same for every gem.
|
|
307
|
+
lock.synchronize { inconclusive[name] = "#{e.class.name}: #{e.message.sub(/ peeraddr=\S+/, '')}" }
|
|
290
308
|
end
|
|
291
309
|
end
|
|
292
310
|
end
|
|
@@ -295,6 +313,20 @@ module RailsPreflight
|
|
|
295
313
|
[found, inconclusive]
|
|
296
314
|
end
|
|
297
315
|
|
|
316
|
+
# The system's certificates plus the ones RubyGems ships for rubygems.org, which is why `gem install` works on a
|
|
317
|
+
# machine whose OpenSSL can't find the system store. Verification stays on.
|
|
318
|
+
def rubygems_cert_store
|
|
319
|
+
require 'rubygems/request'
|
|
320
|
+
OpenSSL::X509::Store.new.tap do |store|
|
|
321
|
+
store.set_default_paths
|
|
322
|
+
Gem::Request.get_cert_files.each do |file|
|
|
323
|
+
store.add_file(file)
|
|
324
|
+
rescue OpenSSL::X509::StoreError
|
|
325
|
+
# already in the system store
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
298
330
|
# From the lockfile: :public, :private (git, path, or only non-rubygems.org remotes), or, when one GEM
|
|
299
331
|
# section lists rubygems.org next to another remote (Bundler before 2.2), whatever the Gemfile says; :mixed if it doesn't.
|
|
300
332
|
def gem_origin(spec)
|
|
@@ -333,7 +365,7 @@ module RailsPreflight
|
|
|
333
365
|
global = []
|
|
334
366
|
scoped = {}
|
|
335
367
|
blocks = [] # source for a `source … do` block, nil for any other block
|
|
336
|
-
gemfile_lines(
|
|
368
|
+
gemfile_lines(@gemfile_path).each do |line|
|
|
337
369
|
source = line[/^\s*source\b\s*\(?\s*(.+?)\s*\)?\s*(?:do\b.*)?$/, 1]&.delete_prefix("\"")&.delete_suffix("\"")&.delete("'")
|
|
338
370
|
name = line[/^\s*gem\s*\(?\s*["']([^"']+)["']/, 1]
|
|
339
371
|
if name
|
|
@@ -380,6 +412,10 @@ module RailsPreflight
|
|
|
380
412
|
@lockfile = File.exist?(@lockfile_path) ? Bundler::LockfileParser.new(File.read(@lockfile_path)) : nil
|
|
381
413
|
end
|
|
382
414
|
|
|
415
|
+
def lockfile_name
|
|
416
|
+
File.basename(@lockfile_path)
|
|
417
|
+
end
|
|
418
|
+
|
|
383
419
|
def known_rails_versions
|
|
384
420
|
@rules.fetch('rails_versions', {}).keys.sort_by { |v| Gem::Version.new(v) }
|
|
385
421
|
end
|
|
@@ -391,7 +427,7 @@ module RailsPreflight
|
|
|
391
427
|
# Next known minor after the current Rails (7.1.3 → 7.2), or nil when already on the newest.
|
|
392
428
|
def next_rails
|
|
393
429
|
unless current_rails
|
|
394
|
-
reason = File.exist?(@lockfile_path) ? "No Rails in #{@lockfile_path}" : "No
|
|
430
|
+
reason = File.exist?(@lockfile_path) ? "No Rails in #{@lockfile_path}" : "No #{lockfile_name} in #{@project_path}"
|
|
395
431
|
raise Error, "#{reason}, so the current Rails version is unknown. Pass a target, e.g. `rails-preflight #{latest_known_rails}`."
|
|
396
432
|
end
|
|
397
433
|
current_minor = Gem::Version.new(current_rails.segments.first(2).join("."))
|