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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bdc9b0322d61da7334ee24e12d415948f8f1117f6824ebb4afe5eec6eadd57e
4
- data.tar.gz: bdc4cf0e7a740c4f33f9211b269257cc854ba49f9d190f956dcdb2513cbfef6b
3
+ metadata.gz: 32a7f01a5cd12b428ccbf01ae0dbe016fcea9d3a3ab9e390b602f9387ece19f8
4
+ data.tar.gz: 3fc3df0da19212ffa444e4d8b1d5ac6b4e33eff27704d7c0d18ea98c3aac1c91
5
5
  SHA512:
6
- metadata.gz: 6120695a0dcd0f21cc652e3e37a7b3f2a1573bfc042aba435d97896522cfd92a169c7d66cc73f7bbd2b4e5d8244b37321acda4e42ca61092201f80c584c65552
7
- data.tar.gz: 37b171778494c7e5e8b89f16f6c9b0c6e47a22d2c3dc174c7d80e0324dac1a36260a485cc220aa7746d43db757917a8eb11df8dac312e6bee2048f5d0fb56371
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
@@ -2,4 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundle_update_interactive"
5
- BundleUpdateInteractive::CLI.start(ARGV)
5
+
6
+ BundleUpdateInteractive::CLI.new.run
@@ -2,4 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundle_update_interactive"
5
- BundleUpdateInteractive::CLI.start(ARGV)
5
+
6
+ BundleUpdateInteractive::CLI.new.run
@@ -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 "bundle update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
10
+ system "#{bundle_bin.shellescape} update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
10
11
  end
11
12
 
12
- def read_updated_lockfile
13
- `bundle lock --print --update`.tap do
14
- raise "bundle lock command failed" unless Process.last_status.success?
15
- end
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 Faraday.head("#{releases_url}/tag/v#{version}").success?
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 = Faraday.get("https://github.com/#{path}")
40
+ response = HTTP.get("https://github.com/#{path}")
42
41
 
43
- if response.status == 301 && follow_redirect
44
- @path = response.headers["Location"][GITHUB_PATTERN, 1]
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 = Faraday.get(api_url)
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 "thor"
3
+ require "bundler"
4
4
 
5
5
  module BundleUpdateInteractive
6
- class CLI < Thor
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
- extend ThorExt::Start
12
+ def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
13
+ options = Options.parse(argv)
13
14
 
14
- default_command :ui
15
- map %w[-v --version] => "version"
15
+ report = generate_report(options)
16
+ puts("No gems to update.").then { return } if report.updateable_gems.empty?
16
17
 
17
- desc "version", "Display bundle_update_interactive version", hide: true
18
- def version
19
- say "bundle_update_interactive/#{VERSION} #{RUBY_DESCRIPTION}"
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
- say("No gems to update.") && return if selected_gems.empty?
22
+ puts("No gems to update.").then { return } if selected_gems.empty?
32
23
 
33
- say "\nUpdating the following gems."
34
- say
35
- say Table.new(selected_gems).render
36
- say
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) # rubocop:disable Style/StderrPuts
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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleUpdateInteractive
4
+ class Error < StandardError
5
+ end
6
+ end
@@ -18,5 +18,9 @@ module BundleUpdateInteractive
18
18
  def [](name)
19
19
  @dependencies[name]
20
20
  end
21
+
22
+ def dependencies
23
+ @dependencies.values
24
+ end
21
25
  end
22
26
  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
- # TODO: refactor
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
- specs_by_name = {}
11
- exact = Set.new
12
- exact_children = {}
10
+ new(parser.specs)
11
+ end
13
12
 
14
- parser.specs.each do |spec|
15
- specs_by_name[spec.name] = spec
13
+ def initialize(specs)
14
+ @specs_by_name = {}
15
+ required_exactly = Set.new
16
16
 
17
- spec.dependencies.each do |dep|
18
- next unless dep.requirement.exact?
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
- exact << dep.name
21
- (exact_children[spec.name] ||= []) << dep.name
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
- entries = specs_by_name.transform_values do |spec|
26
- exact_dependencies = Set.new
27
- traversal = exact_children[spec.name]&.dup || []
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
- exact_dependencies << name
33
- traversal.push(*exact_children.fetch(name, []))
34
- end
31
+ def [](gem_name)
32
+ entries_by_name[gem_name]
33
+ end
35
34
 
36
- LockfileEntry.new(spec, exact_dependencies, exact.include?(spec.name))
37
- end
35
+ def gems_exclusively_installed_by(gemfile:, groups:)
36
+ return [] if groups.empty?
38
37
 
39
- new(entries)
40
- end
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
- def initialize(entries)
43
- @entries = entries.freeze
42
+ entries_by_name.keys - gems_installed_by_other_groups
44
43
  end
45
44
 
46
- def entries
47
- @entries.values
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 [](gem_name)
51
- @entries[gem_name]
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, exact_dependency)
7
+ def initialize(spec, exact_dependencies, exact_requirement)
8
8
  @spec = spec
9
9
  @exact_dependencies = exact_dependencies
10
- @exact_dependency = exact_dependency
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 exact_dependency?
32
- @exact_dependency
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
- updated_lockfile = Lockfile.parse(BundlerCommands.read_updated_lockfile)
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 ||= current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
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].exact_dependency?
40
+ current_lockfile[name].exact_requirement?
40
41
  end.freeze
41
42
  end
42
43
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BundleUpdateInteractive
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -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.3.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-07-19 00:00:00.000000000 Z
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/cli/thor_ext.rb
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.11
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