bundleup 1.3.0 → 2.0.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/README.md +2 -2
- data/exe/bundleup +7 -1
- data/lib/bundleup.rb +19 -6
- data/lib/bundleup/backup.rb +26 -0
- data/lib/bundleup/cli.rb +96 -62
- data/lib/bundleup/colors.rb +56 -0
- data/lib/bundleup/commands.rb +40 -0
- data/lib/bundleup/gemfile.rb +9 -2
- data/lib/bundleup/logger.rb +64 -0
- data/lib/bundleup/pin_report.rb +37 -0
- data/lib/bundleup/report.rb +34 -0
- data/lib/bundleup/shell.rb +35 -0
- data/lib/bundleup/update_report.rb +63 -0
- data/lib/bundleup/version.rb +1 -1
- metadata +10 -7
- data/lib/bundleup/bundle_commands.rb +0 -52
- data/lib/bundleup/console.rb +0 -100
- data/lib/bundleup/gem_status.rb +0 -59
- data/lib/bundleup/outdated_parser.rb +0 -17
- data/lib/bundleup/upgrade.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82ad26069759aeaa4516bb5305da2d0d75a38e1c30d8efd275b3fd09df6f2697
|
4
|
+
data.tar.gz: 1cec30ec08cf62b3332c6b8b6539c4231fbf6b1bc29f2c8a4069b5afff2d0b83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 388e5e6c1ca95b073319c7cd5954348190c2ff1a2e63836659a966eebc7c4da8583474434c33a979d0f70bc69e02686d8a0c4829fcba4123e25b675665af2ee4
|
7
|
+
data.tar.gz: 651c38cc47925f3bafbbc30b50d777c58dfc2918ca3a1329c182ae38735d6e33fb287cb6ba9096b878c4c137746cdc6baa18726362a02eb036ab7a63db9e77a7
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ You might like bundleup because it:
|
|
14
14
|
|
15
15
|
Here it is in action:
|
16
16
|
|
17
|
-
<img src="
|
17
|
+
<img src="./demo.gif" width="682" height="351" alt="Sample output">
|
18
18
|
|
19
19
|
## Requirements
|
20
20
|
|
@@ -54,7 +54,7 @@ After displaying its findings, bundleup gives you the option of keeping the chan
|
|
54
54
|
|
55
55
|
## Roadmap
|
56
56
|
|
57
|
-
bundleup is
|
57
|
+
bundleup is very simple at this point, but it could be more. Some possibilities:
|
58
58
|
|
59
59
|
- Automatically commit the Gemfile.lock changes with a nice commit message
|
60
60
|
- Integrate with bundler-audit to mark upgrades that have important security fixes
|
data/exe/bundleup
CHANGED
data/lib/bundleup.rb
CHANGED
@@ -1,8 +1,21 @@
|
|
1
1
|
require "bundleup/version"
|
2
|
-
require "bundleup/
|
3
|
-
require "bundleup/
|
4
|
-
require "bundleup/gem_status"
|
5
|
-
require "bundleup/gemfile"
|
6
|
-
require "bundleup/outdated_parser"
|
7
|
-
require "bundleup/upgrade"
|
2
|
+
require "bundleup/backup"
|
3
|
+
require "bundleup/colors"
|
8
4
|
require "bundleup/cli"
|
5
|
+
require "bundleup/commands"
|
6
|
+
require "bundleup/gemfile"
|
7
|
+
require "bundleup/logger"
|
8
|
+
require "bundleup/report"
|
9
|
+
require "bundleup/shell"
|
10
|
+
require "bundleup/pin_report"
|
11
|
+
require "bundleup/update_report"
|
12
|
+
|
13
|
+
module Bundleup
|
14
|
+
class << self
|
15
|
+
attr_accessor :commands, :logger, :shell
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Bundleup.commands = Bundleup::Commands.new
|
20
|
+
Bundleup.logger = Bundleup::Logger.new
|
21
|
+
Bundleup.shell = Bundleup::Shell.new
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bundleup
|
2
|
+
class Backup
|
3
|
+
def self.restore_on_error(path)
|
4
|
+
backup = new(path)
|
5
|
+
begin
|
6
|
+
yield(backup)
|
7
|
+
rescue StandardError, Interrupt
|
8
|
+
backup.restore
|
9
|
+
raise
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(path)
|
14
|
+
@path = path
|
15
|
+
@original_contents = IO.read(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def restore
|
19
|
+
IO.write(path, original_contents)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :path, :original_contents
|
25
|
+
end
|
26
|
+
end
|
data/lib/bundleup/cli.rb
CHANGED
@@ -1,86 +1,120 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module Bundleup
|
2
4
|
class CLI
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
review_pins
|
12
|
-
committed = upgrades.any? && confirm_commit
|
13
|
-
puts "Done!" if committed
|
14
|
-
ensure
|
15
|
-
restore_lockfile unless committed
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
|
7
|
+
include Colors
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators :Bundleup, :commands, :logger
|
10
|
+
|
11
|
+
def initialize(args)
|
12
|
+
@args = args
|
16
13
|
end
|
17
14
|
|
18
|
-
|
15
|
+
def run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
16
|
+
print_usage && return if (args & %w[-h --help]).any?
|
17
|
+
|
18
|
+
assert_gemfile_and_lock_exist!
|
19
|
+
|
20
|
+
logger.puts "Please wait a moment while I upgrade your Gemfile.lock..."
|
21
|
+
Backup.restore_on_error("Gemfile.lock") do |backup|
|
22
|
+
update_report, pin_report = perform_analysis
|
23
|
+
|
24
|
+
if update_report.empty?
|
25
|
+
logger.ok "Nothing to update."
|
26
|
+
logger.puts "\n#{pin_report}" unless pin_report.empty?
|
27
|
+
break
|
28
|
+
end
|
29
|
+
|
30
|
+
logger.puts
|
31
|
+
logger.puts update_report
|
32
|
+
logger.puts pin_report unless pin_report.empty?
|
19
33
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
34
|
+
if logger.confirm?("Do you want to apply these changes?")
|
35
|
+
logger.ok "Done!"
|
36
|
+
else
|
37
|
+
backup.restore
|
38
|
+
logger.puts "Your original Gemfile.lock has been restored."
|
39
|
+
end
|
26
40
|
end
|
27
41
|
end
|
28
42
|
|
29
|
-
|
30
|
-
return if pins.empty?
|
43
|
+
private
|
31
44
|
|
32
|
-
|
33
|
-
print_pins_table
|
34
|
-
end
|
45
|
+
attr_reader :args
|
35
46
|
|
36
|
-
def
|
37
|
-
|
38
|
-
end
|
47
|
+
def print_usage # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
48
|
+
logger.puts(<<~USAGE.gsub(/^/, " "))
|
39
49
|
|
40
|
-
|
41
|
-
return unless defined?(@upgrade)
|
42
|
-
return unless upgrade.lockfile_changed?
|
50
|
+
Usage: #{green('bundleup')} #{yellow('[GEMS...] [OPTIONS]')}
|
43
51
|
|
44
|
-
|
45
|
-
|
46
|
-
|
52
|
+
Use #{blue('bundleup')} in place of #{blue('bundle update')} to interactively update your project
|
53
|
+
Gemfile.lock to the latest gem versions. Bundleup will show what gems will
|
54
|
+
be updated, color-code them based on semver, and ask you to confirm the
|
55
|
+
updates before finalizing them. For example:
|
47
56
|
|
48
|
-
|
49
|
-
@upgrade ||= Upgrade.new(ARGV)
|
50
|
-
end
|
57
|
+
The following gems will be updated:
|
51
58
|
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
#{yellow('bundler-audit 0.6.1 → 0.7.0.1')}
|
60
|
+
i18n 1.8.2 → 1.8.5
|
61
|
+
#{red('json 2.2.0 → (removed)')}
|
62
|
+
parser 2.7.1.1 → 2.7.1.4
|
63
|
+
#{red('rails e063bef → 57a4ead')}
|
64
|
+
#{blue('rubocop-ast (new) → 0.3.0')}
|
65
|
+
#{red('thor 0.20.3 → 1.0.1')}
|
66
|
+
#{yellow('zeitwerk 2.3.0 → 2.4.0')}
|
55
67
|
|
56
|
-
|
57
|
-
upgrade.pins
|
58
|
-
end
|
68
|
+
#{yellow('Do you want to apply these changes [Yn]?')}
|
59
69
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
upgrades.zip(rows).each do |g, row|
|
65
|
-
puts color(g.color, row)
|
66
|
-
end
|
67
|
-
end
|
70
|
+
Bundleup will also let you know if there are gems that can't be updated
|
71
|
+
because they are pinned in the Gemfile. Any relevant comments from the
|
72
|
+
Gemfile will also be included, explaining the pins:
|
68
73
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
+
Note that the following gems are being held back:
|
75
|
+
|
76
|
+
rake 12.3.3 → 13.0.1 : pinned at ~> 12.0 #{gray('# Not ready for 13 yet')}
|
77
|
+
rubocop 0.89.0 → 0.89.1 : pinned at = 0.89.0
|
78
|
+
|
79
|
+
You may optionally specify one or more #{yellow('GEMS')} or pass #{yellow('OPTIONS')} to bundleup;
|
80
|
+
these will be passed through to bundler. See #{blue('bundle update --help')} for the
|
81
|
+
full list of the options that bundler supports.
|
82
|
+
|
83
|
+
Examples:
|
84
|
+
|
85
|
+
#{gray('# Update all gems')}
|
86
|
+
#{blue('$ bundleup')}
|
87
|
+
|
88
|
+
#{gray('# Only update gems in the development group')}
|
89
|
+
#{blue('$ bundleup --group=development')}
|
90
|
+
|
91
|
+
#{gray('# Only update the rake gem')}
|
92
|
+
#{blue('$ bundleup rake')}
|
93
|
+
|
94
|
+
USAGE
|
95
|
+
true
|
74
96
|
end
|
75
97
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
98
|
+
def assert_gemfile_and_lock_exist!
|
99
|
+
return if File.exist?("Gemfile") && File.exist?("Gemfile.lock")
|
100
|
+
|
101
|
+
raise Error, "Gemfile and Gemfile.lock must both be present."
|
80
102
|
end
|
81
103
|
|
82
|
-
def
|
83
|
-
|
104
|
+
def perform_analysis # rubocop:disable Metrics/AbcSize
|
105
|
+
gem_comments = Gemfile.new.gem_comments
|
106
|
+
commands.check? || commands.install
|
107
|
+
old_versions = commands.list
|
108
|
+
commands.update(args)
|
109
|
+
new_versions = commands.list
|
110
|
+
outdated_gems = commands.outdated
|
111
|
+
|
112
|
+
logger.clear_line
|
113
|
+
|
114
|
+
update_report = UpdateReport.new(old_versions: old_versions, new_versions: new_versions)
|
115
|
+
pin_report = PinReport.new(gem_versions: new_versions, outdated_gems: outdated_gems, gem_comments: gem_comments)
|
116
|
+
|
117
|
+
[update_report, pin_report]
|
84
118
|
end
|
85
119
|
end
|
86
120
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Bundleup
|
2
|
+
module Colors
|
3
|
+
ANSI_CODES = {
|
4
|
+
red: 31,
|
5
|
+
green: 32,
|
6
|
+
yellow: 33,
|
7
|
+
blue: 34,
|
8
|
+
gray: 90
|
9
|
+
}.freeze
|
10
|
+
private_constant :ANSI_CODES
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_writer :enabled
|
14
|
+
|
15
|
+
def enabled?
|
16
|
+
return @enabled if defined?(@enabled)
|
17
|
+
|
18
|
+
@enabled = determine_color_support
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def determine_color_support
|
24
|
+
if ENV["CLICOLOR_FORCE"] == "1"
|
25
|
+
true
|
26
|
+
elsif ENV["TERM"] == "dumb"
|
27
|
+
false
|
28
|
+
else
|
29
|
+
tty?($stdout) && tty?($stderr)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def tty?(io)
|
34
|
+
io.respond_to?(:tty?) && io.tty?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module_function
|
39
|
+
|
40
|
+
def plain(str)
|
41
|
+
str
|
42
|
+
end
|
43
|
+
|
44
|
+
def strip(str)
|
45
|
+
str.gsub(/\033\[[0-9;]*m/, "")
|
46
|
+
end
|
47
|
+
|
48
|
+
ANSI_CODES.each do |name, code|
|
49
|
+
define_method(name) do |str|
|
50
|
+
return str if str.to_s.empty?
|
51
|
+
|
52
|
+
Colors.enabled? ? "\e[0;#{code};49m#{str}\e[0m" : str
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Bundleup
|
4
|
+
class Commands
|
5
|
+
GEMFILE_ENTRY_REGEXP = /\* (\S+) \((\S+)(?: (\S+))?\)/.freeze
|
6
|
+
OUTDATED_2_1_REGEXP = /\* (\S+) \(newest (\S+),.* requested (.*)\)/.freeze
|
7
|
+
OUTDATED_2_2_REGEXP = /^(\S+)\s\s+\S+\s\s+(\d\S+)\s\s+(\S.*?)(?:$|\s\s)/.freeze
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :Bundleup, :shell
|
11
|
+
|
12
|
+
def check?
|
13
|
+
shell.run?(%w[bundle check])
|
14
|
+
end
|
15
|
+
|
16
|
+
def install
|
17
|
+
shell.run(%w[bundle install])
|
18
|
+
end
|
19
|
+
|
20
|
+
def list
|
21
|
+
output = shell.capture(%w[bundle list])
|
22
|
+
output.scan(GEMFILE_ENTRY_REGEXP).each_with_object({}) do |(name, ver, sha), gems|
|
23
|
+
gems[name] = sha || ver
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def outdated
|
28
|
+
output = shell.capture(%w[bundle outdated], raise_on_error: false)
|
29
|
+
expr = output.match?(/^Gem\s+Current\s+Latest/) ? OUTDATED_2_2_REGEXP : OUTDATED_2_1_REGEXP
|
30
|
+
|
31
|
+
output.scan(expr).each_with_object({}) do |(name, newest, pin), gems|
|
32
|
+
gems[name] = { newest: newest, pin: pin }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def update(args=[])
|
37
|
+
shell.run(%w[bundle update] + args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/bundleup/gemfile.rb
CHANGED
@@ -4,14 +4,21 @@ module Bundleup
|
|
4
4
|
@contents = IO.read(path)
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def gem_comments
|
8
|
+
gem_names.each_with_object({}) do |gem, hash|
|
9
|
+
comment = inline_comment(gem) || prefix_comment(gem)
|
10
|
+
hash[gem] = comment unless comment.nil?
|
11
|
+
end
|
9
12
|
end
|
10
13
|
|
11
14
|
private
|
12
15
|
|
13
16
|
attr_reader :contents
|
14
17
|
|
18
|
+
def gem_names
|
19
|
+
contents.scan(/^\s*gem\s+["'](.+?)["']/).flatten.uniq
|
20
|
+
end
|
21
|
+
|
15
22
|
def inline_comment(gem_name)
|
16
23
|
contents[/#{gem_declaration_re(gem_name)}.*(#\s*\S+.*)/, 1]
|
17
24
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "io/console"
|
2
|
+
|
3
|
+
module Bundleup
|
4
|
+
class Logger
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@stdout, :print, :puts, :tty?
|
7
|
+
def_delegators :@stdin, :gets
|
8
|
+
|
9
|
+
def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr)
|
10
|
+
@stdin = stdin
|
11
|
+
@stdout = stdout
|
12
|
+
@stderr = stderr
|
13
|
+
@spinner = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].cycle
|
14
|
+
end
|
15
|
+
|
16
|
+
def ok(message)
|
17
|
+
puts Colors.green("✔ #{message}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(message)
|
21
|
+
stderr.puts Colors.red("ERROR: #{message}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def attention(message)
|
25
|
+
puts Colors.yellow(message)
|
26
|
+
end
|
27
|
+
|
28
|
+
def confirm?(question)
|
29
|
+
print Colors.yellow(question.sub(/\??\z/, " [Yn]? "))
|
30
|
+
gets =~ /^($|y)/i
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_line
|
34
|
+
print "\r".ljust(console_width - 1)
|
35
|
+
print "\r"
|
36
|
+
end
|
37
|
+
|
38
|
+
def while_spinning(message, &block)
|
39
|
+
thread = Thread.new(&block)
|
40
|
+
thread.report_on_exception = false
|
41
|
+
message = message.ljust(console_width - 2)
|
42
|
+
print "\r#{Colors.blue([spinner.next, message].join(' '))}" until wait_for_exit(thread, 0.1)
|
43
|
+
thread.value
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :spinner, :stderr
|
49
|
+
|
50
|
+
def console_width
|
51
|
+
width = IO.console.winsize.last if tty?
|
52
|
+
width.to_i.positive? ? width : 80
|
53
|
+
end
|
54
|
+
|
55
|
+
def wait_for_exit(thread, seconds)
|
56
|
+
thread.join(seconds)
|
57
|
+
rescue StandardError
|
58
|
+
# Sanity check. If we get an exception, the thread should be dead.
|
59
|
+
raise if thread.alive?
|
60
|
+
|
61
|
+
thread
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Bundleup
|
2
|
+
class PinReport < Report
|
3
|
+
def initialize(gem_versions:, outdated_gems:, gem_comments:)
|
4
|
+
super()
|
5
|
+
@gem_versions = gem_versions
|
6
|
+
@outdated_gems = outdated_gems
|
7
|
+
@gem_comments = gem_comments
|
8
|
+
end
|
9
|
+
|
10
|
+
def title
|
11
|
+
return "Note that this gem is being held back:" if rows.count == 1
|
12
|
+
|
13
|
+
"Note that the following gems are being held back:"
|
14
|
+
end
|
15
|
+
|
16
|
+
def rows
|
17
|
+
outdated_gems.keys.sort.map do |gem|
|
18
|
+
meta = outdated_gems[gem]
|
19
|
+
current_version = gem_versions[gem]
|
20
|
+
newest_version = meta[:newest]
|
21
|
+
pin = meta[:pin]
|
22
|
+
|
23
|
+
[gem, current_version, "→", newest_version, *pin_reason(gem, pin)]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :gem_versions, :outdated_gems, :gem_comments
|
30
|
+
|
31
|
+
def pin_reason(gem, pin)
|
32
|
+
notes = Colors.gray(gem_comments[gem].to_s)
|
33
|
+
pin_operator, pin_version = pin.split(" ", 2)
|
34
|
+
[":", "pinned at", pin_operator.rjust(2), pin_version, notes]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Bundleup
|
4
|
+
class Report
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :rows, :empty?
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
[
|
10
|
+
title,
|
11
|
+
tableize(rows).map { |row| row.join(" ").rstrip }.join("\n"),
|
12
|
+
""
|
13
|
+
].join("\n\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def tableize(rows)
|
19
|
+
widths = max_length_of_each_column(rows)
|
20
|
+
rows.map do |row|
|
21
|
+
row.zip(widths).map do |value, width|
|
22
|
+
padding = " " * (width - Colors.strip(value).length)
|
23
|
+
"#{value}#{padding}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def max_length_of_each_column(rows)
|
29
|
+
Array.new(rows.first.count) do |i|
|
30
|
+
rows.map { |values| Colors.strip(values[i]).length }.max
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "open3"
|
3
|
+
|
4
|
+
module Bundleup
|
5
|
+
class Shell
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :Bundleup, :logger
|
8
|
+
|
9
|
+
def capture(command, raise_on_error: true)
|
10
|
+
stdout, stderr, status = capture3(command)
|
11
|
+
raise ["Failed to execute: #{command}", stdout, stderr].compact.join("\n") if raise_on_error && !status.success?
|
12
|
+
|
13
|
+
stdout
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(command)
|
17
|
+
capture(command)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def run?(command)
|
22
|
+
_, _, status = capture3(command)
|
23
|
+
status.success?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def capture3(command)
|
29
|
+
command = Array(command)
|
30
|
+
logger.while_spinning("running: #{command.join(' ')}") do
|
31
|
+
Open3.capture3(*command)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Bundleup
|
2
|
+
class UpdateReport < Report
|
3
|
+
def initialize(old_versions:, new_versions:)
|
4
|
+
super()
|
5
|
+
@old_versions = old_versions
|
6
|
+
@new_versions = new_versions
|
7
|
+
end
|
8
|
+
|
9
|
+
def title
|
10
|
+
return "This gem will be updated:" if rows.count == 1
|
11
|
+
|
12
|
+
"The following gems will be updated:"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rows
|
16
|
+
gem_names.each_with_object([]) do |gem, rows|
|
17
|
+
old = old_versions[gem]
|
18
|
+
new = new_versions[gem]
|
19
|
+
next if old == new
|
20
|
+
|
21
|
+
row = [gem, old || "(new)", "→", new || "(removed)"]
|
22
|
+
|
23
|
+
color = color_for_gem(gem)
|
24
|
+
rows << row.map { |col| Colors.public_send(color, col) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :old_versions, :new_versions
|
31
|
+
|
32
|
+
def gem_names
|
33
|
+
(old_versions.keys | new_versions.keys).sort
|
34
|
+
end
|
35
|
+
|
36
|
+
def color_for_gem(gem)
|
37
|
+
old_version = old_versions[gem]
|
38
|
+
new_version = new_versions[gem]
|
39
|
+
|
40
|
+
return :blue if old_version.nil?
|
41
|
+
return :red if new_version.nil? || major_upgrade?(old_version, new_version)
|
42
|
+
return :yellow if minor_upgrade?(old_version, new_version)
|
43
|
+
|
44
|
+
:plain
|
45
|
+
end
|
46
|
+
|
47
|
+
def major_upgrade?(old_version, new_version)
|
48
|
+
major(new_version) != major(old_version)
|
49
|
+
end
|
50
|
+
|
51
|
+
def minor_upgrade?(old_version, new_version)
|
52
|
+
minor(new_version) != minor(old_version)
|
53
|
+
end
|
54
|
+
|
55
|
+
def major(version)
|
56
|
+
version.split(".", 2)[0]
|
57
|
+
end
|
58
|
+
|
59
|
+
def minor(version)
|
60
|
+
version.split(".", 3)[1]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/bundleup/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundleup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.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: 2020-
|
11
|
+
date: 2020-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Use `bundleup` whenever you want to update the locked Gemfile dependencies
|
14
14
|
of a Ruby project. It shows exactly what gems will be updated with color output
|
@@ -27,13 +27,16 @@ files:
|
|
27
27
|
- README.md
|
28
28
|
- exe/bundleup
|
29
29
|
- lib/bundleup.rb
|
30
|
-
- lib/bundleup/
|
30
|
+
- lib/bundleup/backup.rb
|
31
31
|
- lib/bundleup/cli.rb
|
32
|
-
- lib/bundleup/
|
33
|
-
- lib/bundleup/
|
32
|
+
- lib/bundleup/colors.rb
|
33
|
+
- lib/bundleup/commands.rb
|
34
34
|
- lib/bundleup/gemfile.rb
|
35
|
-
- lib/bundleup/
|
36
|
-
- lib/bundleup/
|
35
|
+
- lib/bundleup/logger.rb
|
36
|
+
- lib/bundleup/pin_report.rb
|
37
|
+
- lib/bundleup/report.rb
|
38
|
+
- lib/bundleup/shell.rb
|
39
|
+
- lib/bundleup/update_report.rb
|
37
40
|
- lib/bundleup/version.rb
|
38
41
|
homepage: https://github.com/mattbrictson/bundleup
|
39
42
|
licenses:
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require "open3"
|
2
|
-
|
3
|
-
module Bundleup
|
4
|
-
class BundleCommands
|
5
|
-
class Result
|
6
|
-
attr_reader :output
|
7
|
-
|
8
|
-
def initialize(output, success)
|
9
|
-
@output = output
|
10
|
-
@success = success
|
11
|
-
end
|
12
|
-
|
13
|
-
def success?
|
14
|
-
@success
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
include Console
|
19
|
-
|
20
|
-
def check
|
21
|
-
run(%w[bundle check], fail_silently: true).success?
|
22
|
-
end
|
23
|
-
|
24
|
-
def install
|
25
|
-
run(%w[bundle install]).output
|
26
|
-
end
|
27
|
-
|
28
|
-
def outdated
|
29
|
-
run(%w[bundle outdated], fail_silently: true).output
|
30
|
-
end
|
31
|
-
|
32
|
-
def list
|
33
|
-
run(%w[bundle list]).output
|
34
|
-
end
|
35
|
-
|
36
|
-
def update(args=[])
|
37
|
-
run(%w[bundle update] + args).output
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def run(cmd, fail_silently: false)
|
43
|
-
cmd_line = cmd.join(" ")
|
44
|
-
progress("Running `#{cmd_line}`") do
|
45
|
-
out, err, status = Open3.capture3(*cmd)
|
46
|
-
next(Result.new(out, status.success?)) if status.success? || fail_silently
|
47
|
-
|
48
|
-
raise ["Failed to execute: #{cmd_line}", out, err].compact.join("\n")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
data/lib/bundleup/console.rb
DELETED
@@ -1,100 +0,0 @@
|
|
1
|
-
module Bundleup
|
2
|
-
module Console
|
3
|
-
ANSI_CODES = {
|
4
|
-
red: 31,
|
5
|
-
green: 32,
|
6
|
-
yellow: 33,
|
7
|
-
blue: 34,
|
8
|
-
gray: 90
|
9
|
-
}.freeze
|
10
|
-
|
11
|
-
def ok(message)
|
12
|
-
puts color(:green, "✔ #{message}")
|
13
|
-
end
|
14
|
-
|
15
|
-
def attention(message)
|
16
|
-
puts color(:yellow, message)
|
17
|
-
end
|
18
|
-
|
19
|
-
def color(color_name, message)
|
20
|
-
code = ANSI_CODES[color_name]
|
21
|
-
return message if code.nil?
|
22
|
-
|
23
|
-
"\e[0;#{code};49m#{message}\e[0m"
|
24
|
-
end
|
25
|
-
|
26
|
-
def confirm(question)
|
27
|
-
print question.sub(/\??\z/, " [Yn]? ")
|
28
|
-
$stdin.gets =~ /^($|y)/i
|
29
|
-
end
|
30
|
-
|
31
|
-
# Runs a block in the background and displays a spinner until it completes.
|
32
|
-
def progress(message, &block)
|
33
|
-
spinner = %w[/ - \\ |].cycle
|
34
|
-
print "\e[90m#{message}... \e[0m"
|
35
|
-
result = observing_thread(block, 0.5, 0.1) do
|
36
|
-
print "\r\e[90m#{message}... #{spinner.next} \e[0m"
|
37
|
-
end
|
38
|
-
puts "\r\e[90m#{message}... OK\e[0m"
|
39
|
-
result
|
40
|
-
rescue StandardError
|
41
|
-
puts "\r\e[90m#{message}...\e[0m \e[31mFAILED\e[0m"
|
42
|
-
raise
|
43
|
-
end
|
44
|
-
|
45
|
-
# Given a two-dimensional Array of strings representing a table of data,
|
46
|
-
# translate each row into a single string by joining the values with
|
47
|
-
# whitespace such that all the columns are nicely aligned.
|
48
|
-
#
|
49
|
-
# If a block is given, map the rows through the block first. These two
|
50
|
-
# usages are equivalent:
|
51
|
-
#
|
52
|
-
# tableize(rows.map(&something))
|
53
|
-
# tableize(rows, &something)
|
54
|
-
#
|
55
|
-
# Returns a one-dimensional Array of strings, each representing a formatted
|
56
|
-
# row of the resulting table.
|
57
|
-
#
|
58
|
-
def tableize(rows, &block)
|
59
|
-
rows = rows.map(&block) if block
|
60
|
-
widths = max_length_of_each_column(rows)
|
61
|
-
rows.map do |row|
|
62
|
-
row.zip(widths).map { |value, width| value.ljust(width) }.join(" ")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def max_length_of_each_column(rows)
|
69
|
-
Array.new(rows.first.count) do |i|
|
70
|
-
rows.map { |values| values[i].to_s.length }.max
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Starts the `callable` in a background thread and waits for it to complete.
|
75
|
-
# If the callable fails with an exception, it will be raised here. Otherwise
|
76
|
-
# the main thread is paused for an `initial_wait` time in seconds, and
|
77
|
-
# subsequently for `periodic_wait` repeatedly until the thread completes.
|
78
|
-
# After each wait, `yield` is called to allow a block to execute.
|
79
|
-
def observing_thread(callable, initial_wait, periodic_wait)
|
80
|
-
thread = Thread.new(&callable)
|
81
|
-
thread.report_on_exception = false
|
82
|
-
wait_for_exit(thread, initial_wait)
|
83
|
-
loop do
|
84
|
-
break if wait_for_exit(thread, periodic_wait)
|
85
|
-
|
86
|
-
yield
|
87
|
-
end
|
88
|
-
thread.value
|
89
|
-
end
|
90
|
-
|
91
|
-
def wait_for_exit(thread, seconds)
|
92
|
-
thread.join(seconds)
|
93
|
-
rescue StandardError
|
94
|
-
# Sanity check. If we get an exception, the thread should be dead.
|
95
|
-
raise if thread.alive?
|
96
|
-
|
97
|
-
thread
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
data/lib/bundleup/gem_status.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
# rubocop:disable Metrics/BlockLength
|
2
|
-
module Bundleup
|
3
|
-
GemStatus = Struct.new(:name,
|
4
|
-
:old_version,
|
5
|
-
:new_version,
|
6
|
-
:newest_version,
|
7
|
-
:pin) do
|
8
|
-
def pinned?
|
9
|
-
!pin.nil?
|
10
|
-
end
|
11
|
-
|
12
|
-
def upgraded?
|
13
|
-
new_version != old_version
|
14
|
-
end
|
15
|
-
|
16
|
-
def added?
|
17
|
-
old_version.nil?
|
18
|
-
end
|
19
|
-
|
20
|
-
def removed?
|
21
|
-
new_version.nil?
|
22
|
-
end
|
23
|
-
|
24
|
-
def color
|
25
|
-
if major_upgrade? || removed?
|
26
|
-
:red
|
27
|
-
elsif minor_upgrade?
|
28
|
-
:yellow
|
29
|
-
elsif added?
|
30
|
-
:blue
|
31
|
-
else
|
32
|
-
:plain
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def major_upgrade?
|
37
|
-
return false if new_version.nil? || old_version.nil?
|
38
|
-
|
39
|
-
major(new_version) != major(old_version)
|
40
|
-
end
|
41
|
-
|
42
|
-
def minor_upgrade?
|
43
|
-
return false if new_version.nil? || old_version.nil?
|
44
|
-
|
45
|
-
!major_upgrade? && minor(new_version) != minor(old_version)
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def major(version)
|
51
|
-
version.split(".", 2)[0]
|
52
|
-
end
|
53
|
-
|
54
|
-
def minor(version)
|
55
|
-
version.split(".", 3)[1]
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
# rubocop:enable Metrics/BlockLength
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Bundleup
|
2
|
-
module OutdatedParser
|
3
|
-
def self.parse(output)
|
4
|
-
expr = if output.match?(/^Gem\s+Current\s+Latest/)
|
5
|
-
# Bundler >= 2.2 format
|
6
|
-
/^(\S+)\s\s+\S+\s\s+(\d\S+)\s\s+(\S.*?)(?:$|\s\s)/
|
7
|
-
else
|
8
|
-
# Bundler < 2.2
|
9
|
-
/\* (\S+) \(newest (\S+),.* requested (.*)\)/
|
10
|
-
end
|
11
|
-
|
12
|
-
output.scan(expr).map do |name, newest, pin|
|
13
|
-
{ name: name, newest: newest, pin: pin }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/bundleup/upgrade.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
module Bundleup
|
2
|
-
class Upgrade
|
3
|
-
def initialize(update_args=[], commands=BundleCommands.new)
|
4
|
-
@update_args = update_args
|
5
|
-
@commands = commands
|
6
|
-
@gem_statuses = {}
|
7
|
-
@original_lockfile_contents = IO.read(lockfile)
|
8
|
-
run
|
9
|
-
end
|
10
|
-
|
11
|
-
def upgrades
|
12
|
-
@gem_statuses.values.select(&:upgraded?).sort_by(&:name)
|
13
|
-
end
|
14
|
-
|
15
|
-
def pins
|
16
|
-
@gem_statuses.values.select(&:pinned?).sort_by(&:name)
|
17
|
-
end
|
18
|
-
|
19
|
-
def lockfile_changed?
|
20
|
-
IO.read(lockfile) != original_lockfile_contents
|
21
|
-
end
|
22
|
-
|
23
|
-
def undo
|
24
|
-
IO.write(lockfile, original_lockfile_contents)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
attr_reader :update_args, :commands, :original_lockfile_contents
|
30
|
-
|
31
|
-
def run
|
32
|
-
commands.check || commands.install
|
33
|
-
find_versions(:old)
|
34
|
-
commands.update(update_args)
|
35
|
-
find_versions(:new)
|
36
|
-
find_pinned_versions
|
37
|
-
end
|
38
|
-
|
39
|
-
def lockfile
|
40
|
-
"Gemfile.lock"
|
41
|
-
end
|
42
|
-
|
43
|
-
def find_pinned_versions
|
44
|
-
OutdatedParser.parse(commands.outdated).each do |gem|
|
45
|
-
gem_status(gem[:name]).newest_version = gem[:newest]
|
46
|
-
gem_status(gem[:name]).pin = gem[:pin]
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def find_versions(type)
|
51
|
-
commands.list.scan(/\* (\S+) \((\S+)(?: (\S+))?\)/) do |name, ver, sha|
|
52
|
-
gem_status(name).public_send("#{type}_version=", sha || ver)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def gem_status(name)
|
57
|
-
@gem_statuses[name] ||= GemStatus.new(name)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|