bundle_update_interactive 0.3.0 → 0.5.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 +30 -0
- data/exe/bundler-ui +2 -1
- data/exe/bundler-update-interactive +2 -1
- data/lib/bundle_update_interactive/bundler_commands.rb +17 -5
- data/lib/bundle_update_interactive/changelog_locator.rb +10 -11
- data/lib/bundle_update_interactive/cli/options.rb +92 -0
- data/lib/bundle_update_interactive/cli.rb +32 -27
- data/lib/bundle_update_interactive/error.rb +6 -0
- data/lib/bundle_update_interactive/gemfile.rb +4 -0
- data/lib/bundle_update_interactive/http.rb +34 -0
- data/lib/bundle_update_interactive/lockfile.rb +47 -31
- data/lib/bundle_update_interactive/lockfile_entry.rb +4 -4
- data/lib/bundle_update_interactive/report.rb +6 -5
- data/lib/bundle_update_interactive/version.rb +1 -1
- data/lib/bundle_update_interactive.rb +2 -0
- metadata +6 -32
- data/lib/bundle_update_interactive/cli/thor_ext.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32a7f01a5cd12b428ccbf01ae0dbe016fcea9d3a3ab9e390b602f9387ece19f8
|
4
|
+
data.tar.gz: 3fc3df0da19212ffa444e4d8b1d5ac6b4e33eff27704d7c0d18ea98c3aac1c91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a13fb74ddbd25a6570b0ec6655b539328edca5140cb8156a2d1b94c64d0c5d195089330a408498a79d881d7b7013fb31b98490163250d1d7e34ab68499b7f291
|
7
|
+
data.tar.gz: db26535a1961ad67e3932f94ca358a747e8e1209b83f8c44070ded841338cf052123960ae8d89f6664c4aac7c4fc987ce07fa72cc1e373f772c48ba0d9c0054e
|
data/README.md
CHANGED
@@ -87,6 +87,36 @@ https://github.com/rails/rails/compare/5a8d894...77dfa65
|
|
87
87
|
|
88
88
|
This feature currently works for GitHub, GitLab, and Bitbucket repos.
|
89
89
|
|
90
|
+
### Limit impact by Gemfile groups
|
91
|
+
|
92
|
+
The effects of `bundle update-interactive` can be limited to one or more Gemfile groups using the `--exclusively` option:
|
93
|
+
|
94
|
+
```sh
|
95
|
+
bundle update-interactive --exclusively=group1,group2
|
96
|
+
```
|
97
|
+
|
98
|
+
This is especially useful when you want to safely update a subset of your lock file without introducing any risk to your application in production. The best way to do this is with `--exclusively=development,test`, which can be abbreviated to simply `-D`:
|
99
|
+
|
100
|
+
```sh
|
101
|
+
# Update non-production dependencies.
|
102
|
+
# This is equivalent to `bundle update-interactive --exclusively=development,test`
|
103
|
+
bundle update-interactive -D
|
104
|
+
```
|
105
|
+
|
106
|
+
The `--exclusively` and `-D` options will cause `update-interactive` to only consider gems that are used _exclusively_ by the specified Gemfile groups. Indirect dependencies that are shared with other Gemfile groups will not be updated.
|
107
|
+
|
108
|
+
For example, given this Gemfile:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
gem "rails"
|
112
|
+
|
113
|
+
group :test do
|
114
|
+
gem "capybara"
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
If `--exclusively=test` is used, `capybara` and its indirect dependency `xpath` are both exclusively used in test and can therefore be updated. However, capybara's `nokogiri` indirect dependency, which is also used in production via `rails` → `actionpack` → `nokogiri`, would not be allowed to update.
|
119
|
+
|
90
120
|
### Conservative updates
|
91
121
|
|
92
122
|
`bundle update-interactive` updates the gems you select by running `bundle update --conservative [GEMS...]`. This means that only those specific gems will be updated. Indirect dependencies shared with other gems will not be affected.
|
data/exe/bundler-ui
CHANGED
@@ -1,18 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "bundler"
|
3
4
|
require "shellwords"
|
4
5
|
|
5
6
|
module BundleUpdateInteractive
|
6
7
|
module BundlerCommands
|
7
8
|
class << self
|
8
9
|
def update_gems_conservatively(*gems)
|
9
|
-
system "
|
10
|
+
system "#{bundle_bin.shellescape} update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
|
10
11
|
end
|
11
12
|
|
12
|
-
def read_updated_lockfile
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def read_updated_lockfile(*gems)
|
14
|
+
command = ["#{bundle_bin.shellescape} lock --print"]
|
15
|
+
command << "--conservative" if gems.any?
|
16
|
+
command << "--update"
|
17
|
+
command.push(*gems.flatten.map(&:shellescape))
|
18
|
+
|
19
|
+
`#{command.join(" ")}`.tap { raise "bundle lock command failed" unless Process.last_status.success? }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def bundle_bin
|
25
|
+
Gem.bin_path("bundler", "bundle", Bundler::VERSION)
|
26
|
+
rescue Gem::GemNotFoundException
|
27
|
+
"bundle"
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "faraday"
|
4
3
|
require "json"
|
5
4
|
|
6
|
-
GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
|
7
|
-
URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
|
8
|
-
FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
|
9
|
-
EXT_PATTERN = /md|txt|rdoc/i.freeze
|
10
|
-
|
11
5
|
module BundleUpdateInteractive
|
12
6
|
class ChangelogLocator
|
7
|
+
GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
|
8
|
+
URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
|
9
|
+
FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
|
10
|
+
EXT_PATTERN = /md|txt|rdoc/i.freeze
|
11
|
+
|
13
12
|
class GitHubRepo
|
14
13
|
def self.from_uris(*uris)
|
15
14
|
uris.flatten.each do |uri|
|
@@ -32,16 +31,16 @@ module BundleUpdateInteractive
|
|
32
31
|
return "https://github.com/#{changelog_path}" if changelog_path
|
33
32
|
|
34
33
|
releases_url = "https://github.com/#{path}/releases"
|
35
|
-
releases_url if
|
34
|
+
releases_url if HTTP.head("#{releases_url}/tag/v#{version}").success?
|
36
35
|
end
|
37
36
|
|
38
37
|
private
|
39
38
|
|
40
39
|
def fetch_repo_html(follow_redirect:)
|
41
|
-
response =
|
40
|
+
response = HTTP.get("https://github.com/#{path}")
|
42
41
|
|
43
|
-
if response.
|
44
|
-
@path = response
|
42
|
+
if response.code == "301" && follow_redirect
|
43
|
+
@path = response["Location"][GITHUB_PATTERN, 1]
|
45
44
|
return fetch_repo_html(follow_redirect: false)
|
46
45
|
end
|
47
46
|
|
@@ -69,7 +68,7 @@ module BundleUpdateInteractive
|
|
69
68
|
"https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}.json"
|
70
69
|
end
|
71
70
|
|
72
|
-
response =
|
71
|
+
response = HTTP.get(api_url)
|
73
72
|
|
74
73
|
# Try again without the version in case the version does not exist at rubygems for some reason.
|
75
74
|
# This can happen when using a pre-release Ruby that has a bundled gem newer than the published version.
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module BundleUpdateInteractive
|
6
|
+
class CLI::Options
|
7
|
+
class << self
|
8
|
+
def parse(argv=ARGV)
|
9
|
+
options = new
|
10
|
+
remaining = build_parser(options).parse!(argv.dup)
|
11
|
+
raise Error, "update-interactive does not accept arguments. See --help for available options." if remaining.any?
|
12
|
+
|
13
|
+
options.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def summary
|
17
|
+
build_parser(new).summarize.join.gsub(/^\s+-.*? /, pastel.yellow('\0'))
|
18
|
+
end
|
19
|
+
|
20
|
+
def help # rubocop:disable Metrics/AbcSize
|
21
|
+
<<~HELP
|
22
|
+
Provides an easy way to update gems to their latest versions.
|
23
|
+
|
24
|
+
#{pastel.bold.underline('USAGE')}
|
25
|
+
#{pastel.green('bundle update-interactive')} #{pastel.yellow('[options]')}
|
26
|
+
#{pastel.green('bundle ui')} #{pastel.yellow('[options]')}
|
27
|
+
|
28
|
+
#{pastel.bold.underline('OPTIONS')}
|
29
|
+
#{summary}
|
30
|
+
#{pastel.bold.underline('DESCRIPTION')}
|
31
|
+
Displays the list of gems that would be updated by `bundle update`, allowing you
|
32
|
+
to navigate them by keyboard and pick which ones to update. A changelog URL,
|
33
|
+
when available, is displayed alongside each update. Gems with known security
|
34
|
+
vulnerabilities are also highlighted.
|
35
|
+
|
36
|
+
Your Gemfile.lock will be updated conservatively based on the gems you select.
|
37
|
+
Transitive dependencies are not affected.
|
38
|
+
|
39
|
+
More information: #{pastel.blue('https://github.com/mattbrictson/bundle_update_interactive')}
|
40
|
+
|
41
|
+
#{pastel.bold.underline('EXAMPLES')}
|
42
|
+
Show all gems that can be updated.
|
43
|
+
#{pastel.green('bundle update-interactive')}
|
44
|
+
|
45
|
+
The "ui" command alias can also be used.
|
46
|
+
#{pastel.green('bundle ui')}
|
47
|
+
|
48
|
+
Show updates for development and test gems only, leaving production gems untouched.
|
49
|
+
#{pastel.green('bundle update-interactive')} #{pastel.yellow('-D')}
|
50
|
+
|
51
|
+
HELP
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def pastel
|
57
|
+
BundleUpdateInteractive.pastel
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_parser(options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
61
|
+
OptionParser.new do |parser|
|
62
|
+
parser.summary_indent = " "
|
63
|
+
parser.summary_width = 24
|
64
|
+
parser.on(
|
65
|
+
"--exclusively=GROUP",
|
66
|
+
"Update gems exclusively belonging to the specified Gemfile GROUP(s)"
|
67
|
+
) do |value|
|
68
|
+
options.exclusively = value.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
|
69
|
+
end
|
70
|
+
parser.on("-D", "Shorthand for --exclusively=development,test") do
|
71
|
+
options.exclusively = %i[development test]
|
72
|
+
end
|
73
|
+
parser.on("-v", "--version", "Display version") do
|
74
|
+
require "bundler"
|
75
|
+
puts "bundle_update_interactive/#{VERSION} bundler/#{Bundler::VERSION} #{RUBY_DESCRIPTION}"
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
parser.on("-h", "--help", "Show this help") do
|
79
|
+
puts help
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_accessor :exclusively
|
87
|
+
|
88
|
+
def initialize
|
89
|
+
@exclusively = []
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,40 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "bundler"
|
4
4
|
|
5
5
|
module BundleUpdateInteractive
|
6
|
-
class CLI
|
6
|
+
class CLI
|
7
7
|
autoload :MultiSelect, "bundle_update_interactive/cli/multi_select"
|
8
|
+
autoload :Options, "bundle_update_interactive/cli/options"
|
8
9
|
autoload :Row, "bundle_update_interactive/cli/row"
|
9
10
|
autoload :Table, "bundle_update_interactive/cli/table"
|
10
|
-
autoload :ThorExt, "bundle_update_interactive/cli/thor_ext"
|
11
11
|
|
12
|
-
|
12
|
+
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
|
13
|
+
options = Options.parse(argv)
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
report = generate_report(options)
|
16
|
+
puts("No gems to update.").then { return } if report.updateable_gems.empty?
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
desc "ui", "Update Gemfile.lock interactively", hide: true
|
23
|
-
def ui # rubocop:disable Metrics/AbcSize
|
24
|
-
report = generate_report
|
25
|
-
say("No gems to update.") && return if report.updateable_gems.empty?
|
26
|
-
|
27
|
-
say
|
28
|
-
say legend
|
29
|
-
say
|
18
|
+
puts
|
19
|
+
puts legend
|
20
|
+
puts
|
30
21
|
selected_gems = MultiSelect.prompt_for_gems_to_update(report.updateable_gems)
|
31
|
-
|
22
|
+
puts("No gems to update.").then { return } if selected_gems.empty?
|
32
23
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
puts "\nUpdating the following gems."
|
25
|
+
puts
|
26
|
+
puts Table.new(selected_gems).render
|
27
|
+
puts
|
37
28
|
report.bundle_update!(*selected_gems.keys)
|
29
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
30
|
+
handle_exception(e)
|
38
31
|
end
|
39
32
|
|
40
33
|
private
|
@@ -51,9 +44,9 @@ module BundleUpdateInteractive
|
|
51
44
|
LEGEND
|
52
45
|
end
|
53
46
|
|
54
|
-
def generate_report
|
47
|
+
def generate_report(options)
|
55
48
|
whisper "Resolving latest gem versions..."
|
56
|
-
report = Report.generate
|
49
|
+
report = Report.generate(groups: options.exclusively)
|
57
50
|
updateable_gems = report.updateable_gems
|
58
51
|
return report if updateable_gems.empty?
|
59
52
|
|
@@ -65,7 +58,7 @@ module BundleUpdateInteractive
|
|
65
58
|
end
|
66
59
|
|
67
60
|
def whisper(message)
|
68
|
-
$stderr.puts(message)
|
61
|
+
$stderr.puts(message)
|
69
62
|
end
|
70
63
|
|
71
64
|
def progress(message, items, &block)
|
@@ -76,5 +69,17 @@ module BundleUpdateInteractive
|
|
76
69
|
end
|
77
70
|
$stderr.print("\n")
|
78
71
|
end
|
72
|
+
|
73
|
+
def handle_exception(error)
|
74
|
+
case error
|
75
|
+
when Errno::EPIPE
|
76
|
+
# Ignore
|
77
|
+
when BundleUpdateInteractive::Error, OptionParser::ParseError, Interrupt, Bundler::Dsl::DSLError
|
78
|
+
$stderr.puts BundleUpdateInteractive.pastel.red(error.message)
|
79
|
+
exit false
|
80
|
+
else
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|
79
84
|
end
|
80
85
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module BundleUpdateInteractive
|
7
|
+
module HTTP
|
8
|
+
module Success
|
9
|
+
def success?
|
10
|
+
code.start_with?("2")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def get(url)
|
16
|
+
http(:get, url)
|
17
|
+
end
|
18
|
+
|
19
|
+
def head(url)
|
20
|
+
http(:head, url)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def http(method, url_string)
|
26
|
+
uri = URI(url_string)
|
27
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme.end_with?("s")) do |http|
|
28
|
+
http.public_send(method, uri.request_uri)
|
29
|
+
end
|
30
|
+
response.extend(Success)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,54 +1,70 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler"
|
4
|
+
require "set"
|
4
5
|
|
5
6
|
module BundleUpdateInteractive
|
6
7
|
class Lockfile
|
7
|
-
|
8
|
-
def self.parse(lockfile_contents=File.read("Gemfile.lock")) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
8
|
+
def self.parse(lockfile_contents=File.read("Gemfile.lock"))
|
9
9
|
parser = Bundler::LockfileParser.new(lockfile_contents)
|
10
|
-
|
11
|
-
|
12
|
-
exact_children = {}
|
10
|
+
new(parser.specs)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
def initialize(specs)
|
14
|
+
@specs_by_name = {}
|
15
|
+
required_exactly = Set.new
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
specs.each do |spec|
|
18
|
+
specs_by_name[spec.name] = spec
|
19
|
+
spec.dependencies.each { |dep| required_exactly << dep.name if dep.requirement.exact? }
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
22
|
+
@entries_by_name = specs_by_name.transform_values do |spec|
|
23
|
+
build_entry(spec, required_exactly.include?(spec.name))
|
23
24
|
end
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
until traversal.empty?
|
29
|
-
name = traversal.pop
|
30
|
-
next if exact_dependencies.include?(name)
|
27
|
+
def entries
|
28
|
+
entries_by_name.values
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def [](gem_name)
|
32
|
+
entries_by_name[gem_name]
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
def gems_exclusively_installed_by(gemfile:, groups:)
|
36
|
+
return [] if groups.empty?
|
38
37
|
|
39
|
-
|
40
|
-
|
38
|
+
other_group_gems = gemfile.dependencies.filter_map { |gem| gem.name unless (gem.groups & groups).any? }
|
39
|
+
other_group_gems &= entries_by_name.keys
|
40
|
+
gems_installed_by_other_groups = other_group_gems + traverse_transient_dependencies(*other_group_gems)
|
41
41
|
|
42
|
-
|
43
|
-
@entries = entries.freeze
|
42
|
+
entries_by_name.keys - gems_installed_by_other_groups
|
44
43
|
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :entries_by_name, :specs_by_name
|
48
|
+
|
49
|
+
def build_entry(spec, exact)
|
50
|
+
exact_dependencies = traverse_transient_dependencies(spec.name) { |dep| dep.requirement.exact? }
|
51
|
+
LockfileEntry.new(spec, exact_dependencies, exact)
|
48
52
|
end
|
49
53
|
|
50
|
-
def
|
51
|
-
|
54
|
+
def traverse_transient_dependencies(*gem_names) # rubocop:disable Metrics/AbcSize
|
55
|
+
traversal = Set.new
|
56
|
+
stack = gem_names.flatten
|
57
|
+
until stack.empty?
|
58
|
+
specs_by_name[stack.pop].dependencies.each do |dep|
|
59
|
+
next if traversal.include?(dep.name)
|
60
|
+
next unless specs_by_name.key?(dep.name)
|
61
|
+
next if block_given? && !yield(dep)
|
62
|
+
|
63
|
+
traversal << dep.name
|
64
|
+
stack << dep.name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
traversal.to_a
|
52
68
|
end
|
53
69
|
end
|
54
70
|
end
|
@@ -4,10 +4,10 @@ module BundleUpdateInteractive
|
|
4
4
|
class LockfileEntry
|
5
5
|
attr_reader :spec, :exact_dependencies
|
6
6
|
|
7
|
-
def initialize(spec, exact_dependencies,
|
7
|
+
def initialize(spec, exact_dependencies, exact_requirement)
|
8
8
|
@spec = spec
|
9
9
|
@exact_dependencies = exact_dependencies
|
10
|
-
@
|
10
|
+
@exact_requirement = exact_requirement
|
11
11
|
end
|
12
12
|
|
13
13
|
def name
|
@@ -28,8 +28,8 @@ module BundleUpdateInteractive
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
@
|
31
|
+
def exact_requirement?
|
32
|
+
@exact_requirement
|
33
33
|
end
|
34
34
|
|
35
35
|
def git_version
|
@@ -8,11 +8,12 @@ require "set"
|
|
8
8
|
module BundleUpdateInteractive
|
9
9
|
class Report
|
10
10
|
class << self
|
11
|
-
def generate
|
11
|
+
def generate(groups: [])
|
12
12
|
gemfile = Gemfile.parse
|
13
13
|
current_lockfile = Lockfile.parse
|
14
|
-
|
14
|
+
gems = groups.any? ? current_lockfile.gems_exclusively_installed_by(gemfile: gemfile, groups: groups) : nil
|
15
15
|
|
16
|
+
updated_lockfile = gems&.none? ? nil : Lockfile.parse(BundlerCommands.read_updated_lockfile(*Array(gems)))
|
16
17
|
new(gemfile: gemfile, current_lockfile: current_lockfile, updated_lockfile: updated_lockfile)
|
17
18
|
end
|
18
19
|
end
|
@@ -21,9 +22,9 @@ module BundleUpdateInteractive
|
|
21
22
|
|
22
23
|
def initialize(gemfile:, current_lockfile:, updated_lockfile:)
|
23
24
|
@current_lockfile = current_lockfile
|
24
|
-
@outdated_gems
|
25
|
+
@outdated_gems = current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
|
25
26
|
name = current_lockfile_entry.name
|
26
|
-
updated_lockfile_entry = updated_lockfile[name]
|
27
|
+
updated_lockfile_entry = updated_lockfile && updated_lockfile[name]
|
27
28
|
next unless current_lockfile_entry.older_than?(updated_lockfile_entry)
|
28
29
|
|
29
30
|
hash[name] = build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile[name]&.groups)
|
@@ -36,7 +37,7 @@ module BundleUpdateInteractive
|
|
36
37
|
|
37
38
|
def updateable_gems
|
38
39
|
@updateable_gems ||= outdated_gems.reject do |name, _|
|
39
|
-
current_lockfile[name].
|
40
|
+
current_lockfile[name].exact_requirement?
|
40
41
|
end.freeze
|
41
42
|
end
|
42
43
|
|
@@ -6,7 +6,9 @@ module BundleUpdateInteractive
|
|
6
6
|
autoload :BundlerCommands, "bundle_update_interactive/bundler_commands"
|
7
7
|
autoload :ChangelogLocator, "bundle_update_interactive/changelog_locator"
|
8
8
|
autoload :CLI, "bundle_update_interactive/cli"
|
9
|
+
autoload :Error, "bundle_update_interactive/error"
|
9
10
|
autoload :Gemfile, "bundle_update_interactive/gemfile"
|
11
|
+
autoload :HTTP, "bundle_update_interactive/http"
|
10
12
|
autoload :Lockfile, "bundle_update_interactive/lockfile"
|
11
13
|
autoload :LockfileEntry, "bundle_update_interactive/lockfile_entry"
|
12
14
|
autoload :OutdatedGem, "bundle_update_interactive/outdated_gem"
|
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.5.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-
|
11
|
+
date: 2024-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.9.1
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: faraday
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 2.8.0
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 2.8.0
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: pastel
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,20 +52,6 @@ dependencies:
|
|
66
52
|
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: 0.8.0
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: thor
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.2'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.2'
|
83
55
|
- !ruby/object:Gem::Dependency
|
84
56
|
name: tty-prompt
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,10 +98,12 @@ files:
|
|
126
98
|
- lib/bundle_update_interactive/changelog_locator.rb
|
127
99
|
- lib/bundle_update_interactive/cli.rb
|
128
100
|
- lib/bundle_update_interactive/cli/multi_select.rb
|
101
|
+
- lib/bundle_update_interactive/cli/options.rb
|
129
102
|
- lib/bundle_update_interactive/cli/row.rb
|
130
103
|
- lib/bundle_update_interactive/cli/table.rb
|
131
|
-
- lib/bundle_update_interactive/
|
104
|
+
- lib/bundle_update_interactive/error.rb
|
132
105
|
- lib/bundle_update_interactive/gemfile.rb
|
106
|
+
- lib/bundle_update_interactive/http.rb
|
133
107
|
- lib/bundle_update_interactive/lockfile.rb
|
134
108
|
- lib/bundle_update_interactive/lockfile_entry.rb
|
135
109
|
- lib/bundle_update_interactive/outdated_gem.rb
|
@@ -160,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
134
|
- !ruby/object:Gem::Version
|
161
135
|
version: '0'
|
162
136
|
requirements: []
|
163
|
-
rubygems_version: 3.5.
|
137
|
+
rubygems_version: 3.5.16
|
164
138
|
signing_key:
|
165
139
|
specification_version: 4
|
166
140
|
summary: Adds an update-interactive command to Bundler
|
@@ -1,76 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler"
|
4
|
-
|
5
|
-
class BundleUpdateInteractive::CLI
|
6
|
-
module ThorExt
|
7
|
-
# Configures Thor to behave more like a typical CLI, with better help and error handling.
|
8
|
-
#
|
9
|
-
# - Passing -h or --help to a command will show help for that command.
|
10
|
-
# - Unrecognized options will be treated as errors (instead of being silently ignored).
|
11
|
-
# - Error messages will be printed in red to stderr, without stack trace.
|
12
|
-
# - Full stack traces can be enabled by setting the VERBOSE environment variable.
|
13
|
-
# - Errors will cause Thor to exit with a non-zero status.
|
14
|
-
#
|
15
|
-
# To take advantage of this behavior, your CLI should subclass Thor and extend this module.
|
16
|
-
#
|
17
|
-
# class CLI < Thor
|
18
|
-
# extend ThorExt::Start
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# Start your CLI with:
|
22
|
-
#
|
23
|
-
# CLI.start
|
24
|
-
#
|
25
|
-
# In tests, prevent Kernel.exit from being called when an error occurs, like this:
|
26
|
-
#
|
27
|
-
# CLI.start(args, exit_on_failure: false)
|
28
|
-
#
|
29
|
-
module Start
|
30
|
-
def self.extended(base)
|
31
|
-
super
|
32
|
-
base.check_unknown_options!
|
33
|
-
end
|
34
|
-
|
35
|
-
def start(given_args=ARGV, config={})
|
36
|
-
config[:shell] ||= Thor::Base.shell.new
|
37
|
-
handle_help_switches(given_args) do |args|
|
38
|
-
dispatch(nil, args, nil, config)
|
39
|
-
end
|
40
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
41
|
-
handle_exception_on_start(e, config)
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def handle_help_switches(given_args)
|
47
|
-
yield(given_args.dup)
|
48
|
-
rescue Thor::UnknownArgumentError => e
|
49
|
-
retry_with_args = []
|
50
|
-
|
51
|
-
if given_args.first == "help"
|
52
|
-
retry_with_args = ["help"] if given_args.length > 1
|
53
|
-
elsif (e.unknown & %w[-h --help]).any?
|
54
|
-
retry_with_args = ["help", (given_args - e.unknown).first]
|
55
|
-
end
|
56
|
-
raise unless retry_with_args.any?
|
57
|
-
|
58
|
-
yield(retry_with_args)
|
59
|
-
end
|
60
|
-
|
61
|
-
def handle_exception_on_start(error, config)
|
62
|
-
case error
|
63
|
-
when Errno::EPIPE
|
64
|
-
# Ignore
|
65
|
-
when Thor::Error, Interrupt, Bundler::Dsl::DSLError
|
66
|
-
raise unless config.fetch(:exit_on_failure, true)
|
67
|
-
|
68
|
-
config[:shell]&.say_error(error.message, :red)
|
69
|
-
exit(false)
|
70
|
-
else
|
71
|
-
raise
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|