bundle_update_interactive 0.3.0 → 0.5.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 +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
|