bundleup 0.9.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/exe/bundleup +7 -1
- data/lib/bundleup.rb +19 -5
- 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 +27 -112
- data/.github/release-drafter.yml +0 -17
- data/.github/workflows/push.yml +0 -12
- data/.gitignore +0 -8
- data/.rubocop.yml +0 -47
- data/.travis.yml +0 -16
- data/CHANGELOG.md +0 -1
- data/CODE_OF_CONDUCT.md +0 -49
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -4
- data/Rakefile +0 -93
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bundleup.gemspec +0 -30
- data/lib/bundleup/bundle_commands.rb +0 -31
- data/lib/bundleup/console.rb +0 -99
- data/lib/bundleup/gem_status.rb +0 -59
- data/lib/bundleup/upgrade.rb +0 -60
- data/sample.png +0 -0
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
@@ -1,7 +1,7 @@
|
|
1
1
|
# bundleup
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/bundleup.svg)](http://badge.fury.io/rb/bundleup)
|
4
|
-
[![Build Status](https://travis-ci.org/mattbrictson/bundleup.svg?branch=
|
4
|
+
[![Build Status](https://travis-ci.org/mattbrictson/bundleup.svg?branch=main)](https://travis-ci.org/mattbrictson/bundleup)
|
5
5
|
|
6
6
|
**Run `bundleup` on a Ruby project containing a Gemfile to see what gem dependencies need updating.** It is a friendlier command-line interface to [Bundler’s][bundler] `bundle update` and `bundle outdated`.
|
7
7
|
|
@@ -14,12 +14,12 @@ 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
|
|
21
21
|
- Bundler 1.16 or later
|
22
|
-
- Ruby 2.
|
22
|
+
- Ruby 2.5 or later
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
@@ -46,7 +46,7 @@ bundleup --group=development
|
|
46
46
|
|
47
47
|
## How it works
|
48
48
|
|
49
|
-
bundleup starts by making a backup copy of your Gemfile.lock. Next it runs `bundle list`, then `bundle update` and `bundle list` again to find what gems versions are being used before and after Bundler does its updating magic. (Since gems are actually being installed into your Ruby environment during these steps, the process may take a few moments to complete, especially if gems with native extensions need to be compiled.)
|
49
|
+
bundleup starts by making a backup copy of your Gemfile.lock. Next it runs `bundle check` (and `bundle install` if any gems are missing in your local environment), `bundle list`, then `bundle update` and `bundle list` again to find what gems versions are being used before and after Bundler does its updating magic. (Since gems are actually being installed into your Ruby environment during these steps, the process may take a few moments to complete, especially if gems with native extensions need to be compiled.)
|
50
50
|
|
51
51
|
Finally, bundleup runs `bundle outdated` to see the gems that were _not_ updated due to Gemfile restrictions.
|
52
52
|
|
@@ -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,7 +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/upgrade"
|
2
|
+
require "bundleup/backup"
|
3
|
+
require "bundleup/colors"
|
7
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
|