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 +4 -4
- data/lib/bundle_update_interactive/bundler_commands.rb +11 -2
- data/lib/bundle_update_interactive/changelog_locator.rb +5 -6
- data/lib/bundle_update_interactive/cli/multi_select.rb +34 -16
- data/lib/bundle_update_interactive/cli/options.rb +49 -9
- data/lib/bundle_update_interactive/http.rb +34 -0
- data/lib/bundle_update_interactive/outdated_gem.rb +1 -1
- data/lib/bundle_update_interactive/semver_change.rb +3 -1
- data/lib/bundle_update_interactive/version.rb +1 -1
- data/lib/bundle_update_interactive.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e71278ee6c0ffbfb39537712a8d97e956c57062dad9723d358d97474cf63b69c
|
4
|
+
data.tar.gz: 13491cad6f990d97fb3a2c918913fb4bc5eb5048829a18dd8c5400f6e5f9fadf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 "
|
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 = ["
|
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
|
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.
|
@@ -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
|
-
|
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
|
-
@
|
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, <
|
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
|
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.
|
62
|
+
parser.summary_indent = " "
|
63
|
+
parser.summary_width = 24
|
21
64
|
parser.on(
|
22
65
|
"--exclusively=GROUP",
|
23
|
-
"Update gems
|
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
|
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
|
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,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
|
18
|
+
return nil unless @changed
|
17
19
|
|
18
20
|
SEVERITIES[same_segments.length] || :patch
|
19
21
|
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
|
+
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-
|
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:
|
42
|
+
name: launchy
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 2.
|
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.
|
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
|