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 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