bundle_update_interactive 0.6.0 → 0.7.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 +4 -4
- data/README.md +6 -0
- data/lib/bundle_update_interactive/bundler_commands.rb +8 -0
- data/lib/bundle_update_interactive/cli/multi_select.rb +1 -1
- data/lib/bundle_update_interactive/cli/row.rb +4 -11
- data/lib/bundle_update_interactive/cli/table.rb +36 -4
- data/lib/bundle_update_interactive/cli.rb +20 -13
- data/lib/bundle_update_interactive/outdated_gem.rb +1 -0
- data/lib/bundle_update_interactive/report.rb +10 -41
- data/lib/bundle_update_interactive/reporter.rb +66 -0
- data/lib/bundle_update_interactive/version.rb +1 -1
- data/lib/bundle_update_interactive.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58fa0e12f3322d018b1fcaa788dd3b8a2c3354042fe1e6ab21313d0bc0d7518b
|
4
|
+
data.tar.gz: a43866c8bad9bc0256b369cbf7e29db56c2b06293f2ae3a4a3e560ff75506e16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5325810c277c4a3f58d3308b9899c9097a2c723996e79030ccda96f79408c83734b2bcf9ae5920a1ee74f32837ebae91f4e946aa9ff08225db73bf6d06d69e91
|
7
|
+
data.tar.gz: d241d58cb408a0b6294a8b56e010b8ffa841bdbb5ccf28c794fb2db4ebcc562f7ec486742ec36720ed9522c9cf2ce1d27d8a68619fefbee59d28810b3044e7fb
|
data/README.md
CHANGED
@@ -63,6 +63,12 @@ Some gems, notably `rails`, are composed of smaller gems like `actionpack`, `act
|
|
63
63
|
|
64
64
|
Therefore, if any Rails component has a security vulnerability, `bundle update-interactive` will automatically roll up that information into a single `rails` line item, so you can select it and upgrade all of its components in one shot.
|
65
65
|
|
66
|
+
### Held back gems
|
67
|
+
|
68
|
+
When a newer version of a gem is available, but updating is not allowed due to a Gemfile requirement, `update-interactive` will report that the gem has been held back.
|
69
|
+
|
70
|
+
<img src="images/held-back.png" alt="Screenshot of rails and selenium-webdriver gems held back due to Gemfile requirements" width="717" />
|
71
|
+
|
66
72
|
### Changelogs
|
67
73
|
|
68
74
|
`bundle update-interactive` will do its best to find an appropriate changelog for each gem.
|
@@ -19,6 +19,14 @@ module BundleUpdateInteractive
|
|
19
19
|
`#{command.join(" ")}`.tap { raise "bundle lock command failed" unless Process.last_status.success? }
|
20
20
|
end
|
21
21
|
|
22
|
+
def parse_outdated(*gems)
|
23
|
+
command = ["#{bundle_bin.shellescape} outdated --parseable", *gems.flatten.map(&:shellescape)]
|
24
|
+
output = `#{command.join(" ")}`
|
25
|
+
raise "bundle outdated command failed" if output.empty? && !Process.last_status.success?
|
26
|
+
|
27
|
+
output.scan(/^(\S+) \(newest (\S+),/).to_h
|
28
|
+
end
|
29
|
+
|
22
30
|
private
|
23
31
|
|
24
32
|
def bundle_bin
|
@@ -46,7 +46,7 @@ class BundleUpdateInteractive::CLI
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.prompt_for_gems_to_update(outdated_gems, prompt: nil)
|
49
|
-
table = Table.
|
49
|
+
table = Table.updatable(outdated_gems)
|
50
50
|
title = "#{outdated_gems.length} gems can be updated."
|
51
51
|
opener = lambda do |gem|
|
52
52
|
url = outdated_gems[gem].changelog_uri
|
@@ -16,17 +16,6 @@ class BundleUpdateInteractive::CLI
|
|
16
16
|
@pastel = BundleUpdateInteractive.pastel
|
17
17
|
end
|
18
18
|
|
19
|
-
def to_a
|
20
|
-
[
|
21
|
-
formatted_gem_name,
|
22
|
-
formatted_current_version,
|
23
|
-
"→",
|
24
|
-
formatted_updated_version,
|
25
|
-
formatted_gemfile_groups,
|
26
|
-
formatted_changelog_uri
|
27
|
-
]
|
28
|
-
end
|
29
|
-
|
30
19
|
def formatted_gem_name
|
31
20
|
vulnerable? ? pastel.white.on_red(name) : apply_semver_highlight(name)
|
32
21
|
end
|
@@ -46,6 +35,10 @@ class BundleUpdateInteractive::CLI
|
|
46
35
|
gemfile_groups&.map(&:inspect)&.join(", ")
|
47
36
|
end
|
48
37
|
|
38
|
+
def formatted_gemfile_requirement
|
39
|
+
gemfile_requirement.to_s == ">= 0" ? "" : gemfile_requirement.to_s
|
40
|
+
end
|
41
|
+
|
49
42
|
def formatted_changelog_uri
|
50
43
|
pastel.blue(changelog_uri)
|
51
44
|
end
|
@@ -4,12 +4,44 @@ require "pastel"
|
|
4
4
|
|
5
5
|
class BundleUpdateInteractive::CLI
|
6
6
|
class Table
|
7
|
-
|
7
|
+
class << self
|
8
|
+
def withheld(gems)
|
9
|
+
columns = [
|
10
|
+
["name", :formatted_gem_name],
|
11
|
+
["requirement", :formatted_gemfile_requirement],
|
12
|
+
["current", :formatted_current_version],
|
13
|
+
["latest", :formatted_updated_version],
|
14
|
+
["group", :formatted_gemfile_groups],
|
15
|
+
["url", :formatted_changelog_uri]
|
16
|
+
]
|
17
|
+
new(gems, columns)
|
18
|
+
end
|
8
19
|
|
9
|
-
|
20
|
+
def updatable(gems)
|
21
|
+
columns = [
|
22
|
+
["name", :formatted_gem_name],
|
23
|
+
["from", :formatted_current_version],
|
24
|
+
[nil, "→"],
|
25
|
+
["to", :formatted_updated_version],
|
26
|
+
["group", :formatted_gemfile_groups],
|
27
|
+
["url", :formatted_changelog_uri]
|
28
|
+
]
|
29
|
+
new(gems, columns)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(gems, columns)
|
10
34
|
@pastel = BundleUpdateInteractive.pastel
|
11
|
-
@headers =
|
12
|
-
@rows =
|
35
|
+
@headers = columns.map { |header, _| pastel.dim.underline(header) }
|
36
|
+
@rows = gems.transform_values do |gem|
|
37
|
+
row = Row.new(gem)
|
38
|
+
columns.map do |_, col|
|
39
|
+
case col
|
40
|
+
when Symbol then row.public_send(col).to_s
|
41
|
+
when String then col
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
13
45
|
@column_widths = calculate_column_widths
|
14
46
|
end
|
15
47
|
|
@@ -11,19 +11,16 @@ module BundleUpdateInteractive
|
|
11
11
|
|
12
12
|
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
|
13
13
|
options = Options.parse(argv)
|
14
|
-
|
15
14
|
report = generate_report(options)
|
16
|
-
puts("No gems to update.").then { return } if report.updateable_gems.empty?
|
17
15
|
|
18
|
-
|
19
|
-
puts
|
20
|
-
|
21
|
-
selected_gems = MultiSelect.prompt_for_gems_to_update(report.
|
16
|
+
puts_legend_and_withheld_gems(report) unless report.empty?
|
17
|
+
puts("No gems to update.").then { return } if report.updatable_gems.empty?
|
18
|
+
|
19
|
+
selected_gems = MultiSelect.prompt_for_gems_to_update(report.updatable_gems)
|
22
20
|
puts("No gems to update.").then { return } if selected_gems.empty?
|
23
21
|
|
24
|
-
puts "
|
25
|
-
puts
|
26
|
-
puts Table.new(selected_gems).render
|
22
|
+
puts "Updating the following gems."
|
23
|
+
puts Table.updatable(selected_gems).render
|
27
24
|
puts
|
28
25
|
report.bundle_update!(*selected_gems.keys)
|
29
26
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
@@ -32,6 +29,17 @@ module BundleUpdateInteractive
|
|
32
29
|
|
33
30
|
private
|
34
31
|
|
32
|
+
def puts_legend_and_withheld_gems(report)
|
33
|
+
puts
|
34
|
+
puts legend
|
35
|
+
puts
|
36
|
+
return if report.withheld_gems.empty?
|
37
|
+
|
38
|
+
puts "The following gems are being held back and cannot be updated."
|
39
|
+
puts Table.withheld(report.withheld_gems).render
|
40
|
+
puts
|
41
|
+
end
|
42
|
+
|
35
43
|
def legend
|
36
44
|
pastel = BundleUpdateInteractive.pastel
|
37
45
|
<<~LEGEND
|
@@ -46,14 +54,13 @@ module BundleUpdateInteractive
|
|
46
54
|
|
47
55
|
def generate_report(options)
|
48
56
|
whisper "Resolving latest gem versions..."
|
49
|
-
report =
|
50
|
-
|
51
|
-
return report if updateable_gems.empty?
|
57
|
+
report = Reporter.new(groups: options.exclusively).generate_report
|
58
|
+
return report if report.empty?
|
52
59
|
|
53
60
|
whisper "Checking for security vulnerabilities..."
|
54
61
|
report.scan_for_vulnerabilities!
|
55
62
|
|
56
|
-
progress "Finding changelogs",
|
63
|
+
progress "Finding changelogs", report.all_gems.values, &:changelog_uri
|
57
64
|
report
|
58
65
|
end
|
59
66
|
|
@@ -7,38 +7,20 @@ require "set"
|
|
7
7
|
|
8
8
|
module BundleUpdateInteractive
|
9
9
|
class Report
|
10
|
-
|
11
|
-
def generate(groups: [])
|
12
|
-
gemfile = Gemfile.parse
|
13
|
-
current_lockfile = Lockfile.parse
|
14
|
-
gems = groups.any? ? current_lockfile.gems_exclusively_installed_by(gemfile: gemfile, groups: groups) : nil
|
10
|
+
attr_reader :withheld_gems, :updatable_gems
|
15
11
|
|
16
|
-
|
17
|
-
new(gemfile: gemfile, current_lockfile: current_lockfile, updated_lockfile: updated_lockfile)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
attr_reader :outdated_gems
|
22
|
-
|
23
|
-
def initialize(gemfile:, current_lockfile:, updated_lockfile:)
|
12
|
+
def initialize(current_lockfile:, withheld_gems:, updatable_gems:)
|
24
13
|
@current_lockfile = current_lockfile
|
25
|
-
@
|
26
|
-
|
27
|
-
updated_lockfile_entry = updated_lockfile && updated_lockfile[name]
|
28
|
-
next unless current_lockfile_entry.older_than?(updated_lockfile_entry)
|
29
|
-
|
30
|
-
hash[name] = build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile[name]&.groups)
|
31
|
-
end.freeze
|
14
|
+
@withheld_gems = withheld_gems.freeze
|
15
|
+
@updatable_gems = updatable_gems.freeze
|
32
16
|
end
|
33
17
|
|
34
|
-
def
|
35
|
-
|
18
|
+
def empty?
|
19
|
+
withheld_gems.empty? && updatable_gems.empty?
|
36
20
|
end
|
37
21
|
|
38
|
-
def
|
39
|
-
@
|
40
|
-
current_lockfile[name].exact_requirement?
|
41
|
-
end.freeze
|
22
|
+
def all_gems
|
23
|
+
@all_gems ||= withheld_gems.merge(updatable_gems)
|
42
24
|
end
|
43
25
|
|
44
26
|
def expand_gems_with_exact_dependencies(*gem_names)
|
@@ -47,13 +29,13 @@ module BundleUpdateInteractive
|
|
47
29
|
end
|
48
30
|
|
49
31
|
def scan_for_vulnerabilities!
|
50
|
-
return false if
|
32
|
+
return false if all_gems.empty?
|
51
33
|
|
52
34
|
Bundler::Audit::Database.update!(quiet: true)
|
53
35
|
audit_report = Bundler::Audit::Scanner.new.report
|
54
36
|
vulnerable_gem_names = Set.new(audit_report.vulnerable_gems.map(&:name))
|
55
37
|
|
56
|
-
|
38
|
+
all_gems.each do |name, gem|
|
57
39
|
gem.vulnerable = (vulnerable_gem_names & [name, *current_lockfile[name].exact_dependencies]).any?
|
58
40
|
end
|
59
41
|
true
|
@@ -67,18 +49,5 @@ module BundleUpdateInteractive
|
|
67
49
|
private
|
68
50
|
|
69
51
|
attr_reader :current_lockfile
|
70
|
-
|
71
|
-
def build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile_groups)
|
72
|
-
OutdatedGem.new(
|
73
|
-
name: current_lockfile_entry.name,
|
74
|
-
gemfile_groups: gemfile_groups,
|
75
|
-
rubygems_source: updated_lockfile_entry.rubygems_source?,
|
76
|
-
git_source_uri: updated_lockfile_entry.git_source_uri&.to_s,
|
77
|
-
current_version: current_lockfile_entry.version.to_s,
|
78
|
-
current_git_version: current_lockfile_entry.git_version&.strip,
|
79
|
-
updated_version: updated_lockfile_entry.version.to_s,
|
80
|
-
updated_git_version: updated_lockfile_entry.git_version&.strip
|
81
|
-
)
|
82
|
-
end
|
83
52
|
end
|
84
53
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BundleUpdateInteractive
|
4
|
+
class Reporter
|
5
|
+
def initialize(groups: [])
|
6
|
+
@gemfile = Gemfile.parse
|
7
|
+
@current_lockfile = Lockfile.parse
|
8
|
+
@candidate_gems = current_lockfile.gems_exclusively_installed_by(gemfile: gemfile, groups: groups) if groups.any?
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_report
|
12
|
+
updatable_gems = find_updatable_gems
|
13
|
+
withheld_gems = find_withheld_gems(exclude: updatable_gems.keys)
|
14
|
+
|
15
|
+
Report.new(current_lockfile: current_lockfile, updatable_gems: updatable_gems, withheld_gems: withheld_gems)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :gemfile, :current_lockfile, :candidate_gems
|
21
|
+
|
22
|
+
def find_updatable_gems
|
23
|
+
return {} if candidate_gems && candidate_gems.empty?
|
24
|
+
|
25
|
+
updated_lockfile = Lockfile.parse(BundlerCommands.read_updated_lockfile(*Array(candidate_gems)))
|
26
|
+
current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
|
27
|
+
name = current_lockfile_entry.name
|
28
|
+
updated_lockfile_entry = updated_lockfile && updated_lockfile[name]
|
29
|
+
next unless current_lockfile_entry.older_than?(updated_lockfile_entry)
|
30
|
+
next if current_lockfile_entry.exact_requirement?
|
31
|
+
|
32
|
+
hash[name] = build_outdated_gem(name, updated_lockfile_entry.version, updated_lockfile_entry.git_version)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_outdated_gem(name, updated_version, updated_git_version)
|
37
|
+
current_lockfile_entry = current_lockfile[name]
|
38
|
+
|
39
|
+
OutdatedGem.new(
|
40
|
+
name: name,
|
41
|
+
gemfile_groups: gemfile[name]&.groups,
|
42
|
+
gemfile_requirement: gemfile[name]&.requirement&.to_s,
|
43
|
+
rubygems_source: current_lockfile_entry.rubygems_source?,
|
44
|
+
git_source_uri: current_lockfile_entry.git_source_uri&.to_s,
|
45
|
+
current_version: current_lockfile_entry.version.to_s,
|
46
|
+
current_git_version: current_lockfile_entry.git_version&.strip,
|
47
|
+
updated_version: updated_version.to_s,
|
48
|
+
updated_git_version: updated_git_version&.strip
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_withheld_gems(exclude: [])
|
53
|
+
possibly_withheld = gemfile.dependencies.filter_map do |dep|
|
54
|
+
dep.name if dep.should_include? && !dep.requirement.none? # rubocop:disable Style/InverseMethods
|
55
|
+
end
|
56
|
+
possibly_withheld -= exclude
|
57
|
+
possibly_withheld &= candidate_gems unless candidate_gems.nil?
|
58
|
+
|
59
|
+
return {} if possibly_withheld.empty?
|
60
|
+
|
61
|
+
BundlerCommands.parse_outdated(*possibly_withheld).to_h do |name, newest|
|
62
|
+
[name, build_outdated_gem(name, newest, nil)]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -13,6 +13,7 @@ module BundleUpdateInteractive
|
|
13
13
|
autoload :LockfileEntry, "bundle_update_interactive/lockfile_entry"
|
14
14
|
autoload :OutdatedGem, "bundle_update_interactive/outdated_gem"
|
15
15
|
autoload :Report, "bundle_update_interactive/report"
|
16
|
+
autoload :Reporter, "bundle_update_interactive/reporter"
|
16
17
|
autoload :SemverChange, "bundle_update_interactive/semver_change"
|
17
18
|
autoload :VERSION, "bundle_update_interactive/version"
|
18
19
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundle_update_interactive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Brictson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- lib/bundle_update_interactive/lockfile_entry.rb
|
123
123
|
- lib/bundle_update_interactive/outdated_gem.rb
|
124
124
|
- lib/bundle_update_interactive/report.rb
|
125
|
+
- lib/bundle_update_interactive/reporter.rb
|
125
126
|
- lib/bundle_update_interactive/semver_change.rb
|
126
127
|
- lib/bundle_update_interactive/version.rb
|
127
128
|
homepage: https://github.com/mattbrictson/bundle_update_interactive
|