bundle_update_interactive 0.4.0 → 0.6.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: 5eb0324c92b1a60c3611ce47993022fef9c3d69aeba735a752252cc2cffe8158
4
- data.tar.gz: f68c74d7fc286abf743796b3323bed579736bf548ccc2a405b8a2433bf3eddf1
3
+ metadata.gz: e71278ee6c0ffbfb39537712a8d97e956c57062dad9723d358d97474cf63b69c
4
+ data.tar.gz: 13491cad6f990d97fb3a2c918913fb4bc5eb5048829a18dd8c5400f6e5f9fadf
5
5
  SHA512:
6
- metadata.gz: 874b63b22b4ff2ad9101f9a5a51b2947cf27009ff37096ae0ff3f00e3f5de5ba2ea32a4e2a2adae0d46bf4a523868deb5f8f7ec45599f988719a983e1bb9e16f
7
- data.tar.gz: 7f134fec1b5304ed93921506a4e763a1f7ef7954f812b679cb63e271c99fc76bd291f68f1e9fd51a532a3d63a55e74ad01657767250924cd8a2a0d0d7baa69ce
6
+ metadata.gz: 9bf5c295484ae3aa19d12d5891afd7883873204d0a9314babcf111b832cab60b89be13afcb6f33e8a8d2cbc7b702df9b0c800ca0639a2c8148e56adb6964cde4
7
+ data.tar.gz: 0caebaf4d1bbc2a2d3cd6a5820ed031491a41a43286d7ba6f3e7bf419c28c5627ac58b8bed0407658d46efdc01d105f75857c66784b61b98eb581c00fe0ec575
@@ -1,22 +1,31 @@
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
13
  def read_updated_lockfile(*gems)
13
- command = ["bundle lock --print"]
14
+ command = ["#{bundle_bin.shellescape} lock --print"]
14
15
  command << "--conservative" if gems.any?
15
16
  command << "--update"
16
17
  command.push(*gems.flatten.map(&:shellescape))
17
18
 
18
19
  `#{command.join(" ")}`.tap { raise "bundle lock command failed" unless Process.last_status.success? }
19
20
  end
21
+
22
+ private
23
+
24
+ def bundle_bin
25
+ Gem.bin_path("bundler", "bundle", Bundler::VERSION)
26
+ rescue Gem::GemNotFoundException
27
+ "bundle"
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "faraday"
4
3
  require "json"
5
4
 
6
5
  module BundleUpdateInteractive
@@ -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.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "launchy"
3
4
  require "pastel"
4
5
  require "tty/prompt"
5
6
  require "tty/screen"
@@ -8,6 +9,7 @@ class BundleUpdateInteractive::CLI
8
9
  class MultiSelect
9
10
  class List < TTY::Prompt::MultiList
10
11
  def initialize(prompt, **options)
12
+ @opener = options.delete(:opener)
11
13
  defaults = {
12
14
  cycle: true,
13
15
  help_color: :itself.to_proc,
@@ -21,48 +23,64 @@ class BundleUpdateInteractive::CLI
21
23
  def selected_names
22
24
  ""
23
25
  end
26
+
27
+ # Unregister tty-prompt's default ctrl-a and ctrl-r bindings
28
+ alias select_all keyctrl_a
29
+ alias reverse_selection keyctrl_r
30
+ def keyctrl_a(*); end
31
+ def keyctrl_r(*); end
32
+
33
+ def keypress(event)
34
+ case event.value
35
+ when "k", "p" then keyup
36
+ when "j", "n" then keydown
37
+ when "a" then select_all
38
+ when "r" then reverse_selection
39
+ when "o" then opener&.call(choices[@active - 1].value)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :opener
24
46
  end
25
47
 
26
- def self.prompt_for_gems_to_update(outdated_gems)
48
+ def self.prompt_for_gems_to_update(outdated_gems, prompt: nil)
27
49
  table = Table.new(outdated_gems)
28
50
  title = "#{outdated_gems.length} gems can be updated."
29
- chosen = new(title: title, table: table).prompt
51
+ opener = lambda do |gem|
52
+ url = outdated_gems[gem].changelog_uri
53
+ Launchy.open(url) unless url.nil?
54
+ end
55
+ chosen = new(title: title, table: table, prompt: prompt, opener: opener).prompt
30
56
  outdated_gems.slice(*chosen)
31
57
  end
32
58
 
33
- def initialize(title:, table:)
59
+ def initialize(title:, table:, opener: nil, prompt: nil)
34
60
  @title = title
35
61
  @table = table
36
- @tty_prompt = TTY::Prompt.new(
62
+ @opener = opener
63
+ @tty_prompt = prompt || TTY::Prompt.new(
37
64
  interrupt: lambda {
38
65
  puts
39
66
  exit(130)
40
67
  }
41
68
  )
42
- add_keybindings
43
-
44
69
  @pastel = BundleUpdateInteractive.pastel
45
70
  end
46
71
 
47
72
  def prompt
48
73
  choices = table.gem_names.to_h { |name| [table.render_gem(name), name] }
49
- tty_prompt.invoke_select(List, title, choices, help: help)
74
+ tty_prompt.invoke_select(List, title, choices, help: help, opener: opener)
50
75
  end
51
76
 
52
77
  private
53
78
 
54
- attr_reader :pastel, :table, :tty_prompt, :title
55
-
56
- def add_keybindings
57
- tty_prompt.on(:keypress) do |event|
58
- tty_prompt.trigger(:keyup) if %w[k p].include?(event.value)
59
- tty_prompt.trigger(:keydown) if %w[j n].include?(event.value)
60
- end
61
- end
79
+ attr_reader :pastel, :table, :opener, :tty_prompt, :title
62
80
 
63
81
  def help
64
82
  [
65
- pastel.dim("\nPress <space> to select, ↑/↓ move, <ctrl-a> all, <ctrl-r> reverse, <enter> to finish."),
83
+ pastel.dim("\nPress <space> to select, ↑/↓ move, <a> all, <r> reverse, <o> open url, <enter> to finish."),
66
84
  "\n ",
67
85
  table.render_header
68
86
  ].join
@@ -13,30 +13,70 @@ module BundleUpdateInteractive
13
13
  options.freeze
14
14
  end
15
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
+
16
54
  private
17
55
 
18
- def build_parser(options) # rubocop:disable Metrics/MethodLength
56
+ def pastel
57
+ BundleUpdateInteractive.pastel
58
+ end
59
+
60
+ def build_parser(options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
19
61
  OptionParser.new do |parser|
20
- parser.banner = "Usage: bundle update-interactive"
62
+ parser.summary_indent = " "
63
+ parser.summary_width = 24
21
64
  parser.on(
22
65
  "--exclusively=GROUP",
23
- "Update gems that exclusively belong to the specified Gemfile GROUP(s) (comma-separated)"
66
+ "Update gems exclusively belonging to the specified Gemfile GROUP(s)"
24
67
  ) do |value|
25
68
  options.exclusively = value.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
26
69
  end
27
- parser.on(
28
- "-D",
29
- "Update development and test gems only; short for --exclusively=development,test"
30
- ) do
70
+ parser.on("-D", "Shorthand for --exclusively=development,test") do
31
71
  options.exclusively = %i[development test]
32
72
  end
33
- parser.on("-v", "--version", "Display bundle_update_interactive version") do
73
+ parser.on("-v", "--version", "Display version") do
34
74
  require "bundler"
35
75
  puts "bundle_update_interactive/#{VERSION} bundler/#{Bundler::VERSION} #{RUBY_DESCRIPTION}"
36
76
  exit
37
77
  end
38
78
  parser.on("-h", "--help", "Show this help") do
39
- puts parser
79
+ puts help
40
80
  exit
41
81
  end
42
82
  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
@@ -10,7 +10,7 @@ module BundleUpdateInteractive
10
10
  :updated_version,
11
11
  :updated_git_version
12
12
 
13
- attr_writer :rubygems_source, :vulnerable
13
+ attr_writer :changelog_uri, :rubygems_source, :vulnerable
14
14
 
15
15
  def initialize(**attrs)
16
16
  @vulnerable = nil
@@ -10,10 +10,12 @@ module BundleUpdateInteractive
10
10
 
11
11
  @same_segments = new_segments.take_while.with_index { |seg, i| seg == old_segments[i] }
12
12
  @diff_segments = new_segments[same_segments.length..]
13
+
14
+ @changed = diff_segments.any? || old_segments.length != new_segments.length
13
15
  end
14
16
 
15
17
  def severity
16
- return nil if diff_segments.empty?
18
+ return nil unless @changed
17
19
 
18
20
  SEVERITIES[same_segments.length] || :patch
19
21
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BundleUpdateInteractive
4
- VERSION = "0.4.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -8,6 +8,7 @@ module BundleUpdateInteractive
8
8
  autoload :CLI, "bundle_update_interactive/cli"
9
9
  autoload :Error, "bundle_update_interactive/error"
10
10
  autoload :Gemfile, "bundle_update_interactive/gemfile"
11
+ autoload :HTTP, "bundle_update_interactive/http"
11
12
  autoload :Lockfile, "bundle_update_interactive/lockfile"
12
13
  autoload :LockfileEntry, "bundle_update_interactive/lockfile_entry"
13
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.0
4
+ version: 0.6.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-30 00:00:00.000000000 Z
11
+ date: 2024-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.9.1
41
41
  - !ruby/object:Gem::Dependency
42
- name: faraday
42
+ name: launchy
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 2.8.0
47
+ version: 2.5.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 2.8.0
54
+ version: 2.5.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: pastel
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -117,6 +117,7 @@ files:
117
117
  - lib/bundle_update_interactive/cli/table.rb
118
118
  - lib/bundle_update_interactive/error.rb
119
119
  - lib/bundle_update_interactive/gemfile.rb
120
+ - lib/bundle_update_interactive/http.rb
120
121
  - lib/bundle_update_interactive/lockfile.rb
121
122
  - lib/bundle_update_interactive/lockfile_entry.rb
122
123
  - lib/bundle_update_interactive/outdated_gem.rb