bundle_update_interactive 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|