bundle_update_interactive 0.4.0 → 0.6.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: 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