bundleup 1.2.0 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +35 -5
- data/exe/bundleup +7 -1
- data/lib/bundleup.rb +20 -6
- data/lib/bundleup/backup.rb +29 -0
- data/lib/bundleup/cli.rb +123 -61
- data/lib/bundleup/colors.rb +56 -0
- data/lib/bundleup/commands.rb +40 -0
- data/lib/bundleup/gemfile.rb +48 -2
- data/lib/bundleup/logger.rb +64 -0
- data/lib/bundleup/pin_report.rb +37 -0
- data/lib/bundleup/report.rb +38 -0
- data/lib/bundleup/shell.rb +35 -0
- data/lib/bundleup/update_report.rb +63 -0
- data/lib/bundleup/version.rb +1 -1
- data/lib/bundleup/version_spec.rb +54 -0
- metadata +18 -122
- data/.github/release-drafter.yml +0 -17
- data/.github/workflows/push.yml +0 -14
- data/.gitignore +0 -8
- data/.rubocop.yml +0 -46
- data/.travis.yml +0 -14
- 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 -40
- 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/outdated_parser.rb +0 -17
- data/lib/bundleup/upgrade.rb +0 -59
- 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: feeb625fa4809af16d2bd8432e0a346dbcc842b513ede9bf5359209d8048eed9
|
4
|
+
data.tar.gz: d7fd509ab3ec67d7edda8e7ae99ceeb7d22e9ec913fddc6f56da554e2faa82a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4bc171b6589fc4a7c34a7de34e89ac81581d029060323627f36fc8fe863f5fc942a00178c84ab1821f4d4148dcc4ba25d131dec70c52146f50890d7490abc59b
|
7
|
+
data.tar.gz: 1a760272468231a61eccb246111bd42b4e0af8bd6185a3a715fbcc4a4d885d4e754ca11cf50ec5387b03c5ba5347e5211d37d6ad1391095359de5a13965c9418
|
data/LICENSE.txt
CHANGED
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://
|
4
|
+
[![Build Status](https://circleci.com/gh/mattbrictson/bundleup/tree/main.svg?style=shield)](https://app.circleci.com/pipelines/github/mattbrictson/bundleup?branch=main)
|
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,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
|
|
@@ -44,9 +44,39 @@ Protip: Any extra command-line arguments will be passed along to `bundle update`
|
|
44
44
|
bundleup --group=development
|
45
45
|
```
|
46
46
|
|
47
|
-
|
47
|
+
### Experimental: `--update-gemfile`
|
48
48
|
|
49
|
-
|
49
|
+
> 💡 This is an experimental feature that may be removed or changed in future versions.
|
50
|
+
|
51
|
+
Normally bundleup only makes changes to your Gemfile.lock. It honors the version restrictions ("pins") in your Gemfile and will not update your Gemfile.lock to have versions that are not allowed. However with the `--update-gemfile` flag, bundleup can update the version pins in your Gemfile as well. Consider the following Gemfile:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
gem 'sidekiq', '~> 5.2'
|
55
|
+
gem 'rubocop', '0.89.0'
|
56
|
+
```
|
57
|
+
|
58
|
+
Normally running `bundleup` will report that these gems are pinned and therefore cannot be updated to the latest versions. However, if you pass the `--update-gemfile` option like this:
|
59
|
+
|
60
|
+
```
|
61
|
+
$ bundleup --update-gemfile
|
62
|
+
```
|
63
|
+
|
64
|
+
Now bundleup will automatically edit your Gemfile pins as needed to bring those gems up to date. For example, bundleup would change the Gemfile to look like this:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
gem 'sidekiq', '~> 6.1'
|
68
|
+
gem 'rubocop', '0.90.0'
|
69
|
+
```
|
70
|
+
|
71
|
+
Note that `--update-gemfile` will _not_ modify Gemfile entries that contain a comment, like this:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
gem 'sidekiq', '~> 5.2' # our monkey patch doesn't work on 6.0+
|
75
|
+
```
|
76
|
+
|
77
|
+
## How bundleup works
|
78
|
+
|
79
|
+
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
80
|
|
51
81
|
Finally, bundleup runs `bundle outdated` to see the gems that were _not_ updated due to Gemfile restrictions.
|
52
82
|
|
@@ -54,7 +84,7 @@ After displaying its findings, bundleup gives you the option of keeping the chan
|
|
54
84
|
|
55
85
|
## Roadmap
|
56
86
|
|
57
|
-
bundleup is
|
87
|
+
bundleup is very simple at this point, but it could be more. Some possibilities:
|
58
88
|
|
59
89
|
- Automatically commit the Gemfile.lock changes with a nice commit message
|
60
90
|
- Integrate with bundler-audit to mark upgrades that have important security fixes
|
data/exe/bundleup
CHANGED
data/lib/bundleup.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
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
|
+
require "bundleup/version_spec"
|
13
|
+
|
14
|
+
module Bundleup
|
15
|
+
class << self
|
16
|
+
attr_accessor :commands, :logger, :shell
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Bundleup.commands = Bundleup::Commands.new
|
21
|
+
Bundleup.logger = Bundleup::Logger.new
|
22
|
+
Bundleup.shell = Bundleup::Shell.new
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bundleup
|
2
|
+
class Backup
|
3
|
+
def self.restore_on_error(*paths)
|
4
|
+
backup = new(*paths)
|
5
|
+
begin
|
6
|
+
yield(backup)
|
7
|
+
rescue StandardError, Interrupt
|
8
|
+
backup.restore
|
9
|
+
raise
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*paths)
|
14
|
+
@original_contents = paths.each_with_object({}) do |path, hash|
|
15
|
+
hash[path] = IO.read(path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def restore
|
20
|
+
original_contents.each do |path, contents|
|
21
|
+
IO.write(path, contents)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :original_contents
|
28
|
+
end
|
29
|
+
end
|
data/lib/bundleup/cli.rb
CHANGED
@@ -1,86 +1,148 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module Bundleup
|
2
4
|
class CLI
|
3
|
-
|
4
|
-
|
5
|
-
def run
|
6
|
-
puts \
|
7
|
-
"Please wait a moment while I upgrade your Gemfile.lock..."
|
8
|
-
|
9
|
-
committed = false
|
10
|
-
review_upgrades
|
11
|
-
review_pins
|
12
|
-
committed = upgrades.any? && confirm_commit
|
13
|
-
puts "Done!" if committed
|
14
|
-
ensure
|
15
|
-
restore_lockfile unless committed
|
16
|
-
end
|
5
|
+
Error = Class.new(StandardError)
|
17
6
|
|
18
|
-
|
7
|
+
include Colors
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators :Bundleup, :commands, :logger
|
19
10
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
11
|
+
def initialize(args)
|
12
|
+
@args = args.dup
|
13
|
+
@update_gemfile = @args.delete("--update-gemfile")
|
14
|
+
end
|
15
|
+
|
16
|
+
def run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
17
|
+
print_usage && return if (args & %w[-h --help]).any?
|
18
|
+
|
19
|
+
assert_gemfile_and_lock_exist!
|
20
|
+
|
21
|
+
logger.puts "Please wait a moment while I upgrade your Gemfile.lock..."
|
22
|
+
Backup.restore_on_error("Gemfile", "Gemfile.lock") do |backup|
|
23
|
+
perform_analysis_and_optionally_bump_gemfile_versions do |update_report, pin_report|
|
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?
|
33
|
+
|
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
|
40
|
+
end
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
29
|
-
|
30
|
-
return if pins.empty?
|
44
|
+
private
|
31
45
|
|
32
|
-
|
33
|
-
print_pins_table
|
34
|
-
end
|
46
|
+
attr_reader :args
|
35
47
|
|
36
|
-
def
|
37
|
-
|
38
|
-
end
|
48
|
+
def print_usage # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
49
|
+
logger.puts(<<~USAGE.gsub(/^/, " "))
|
39
50
|
|
40
|
-
|
41
|
-
return unless defined?(@upgrade)
|
42
|
-
return unless upgrade.lockfile_changed?
|
51
|
+
Usage: #{green('bundleup')} #{yellow('[GEMS...] [OPTIONS]')}
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
|
53
|
+
Use #{blue('bundleup')} in place of #{blue('bundle update')} to interactively update your project
|
54
|
+
Gemfile.lock to the latest gem versions. Bundleup will show what gems will
|
55
|
+
be updated, color-code them based on semver, and ask you to confirm the
|
56
|
+
updates before finalizing them. For example:
|
47
57
|
|
48
|
-
|
49
|
-
@upgrade ||= Upgrade.new(ARGV)
|
50
|
-
end
|
58
|
+
The following gems will be updated:
|
51
59
|
|
52
|
-
|
53
|
-
|
54
|
-
|
60
|
+
#{yellow('bundler-audit 0.6.1 → 0.7.0.1')}
|
61
|
+
i18n 1.8.2 → 1.8.5
|
62
|
+
#{red('json 2.2.0 → (removed)')}
|
63
|
+
parser 2.7.1.1 → 2.7.1.4
|
64
|
+
#{red('rails e063bef → 57a4ead')}
|
65
|
+
#{blue('rubocop-ast (new) → 0.3.0')}
|
66
|
+
#{red('thor 0.20.3 → 1.0.1')}
|
67
|
+
#{yellow('zeitwerk 2.3.0 → 2.4.0')}
|
68
|
+
|
69
|
+
#{yellow('Do you want to apply these changes [Yn]?')}
|
70
|
+
|
71
|
+
Bundleup will also let you know if there are gems that can't be updated
|
72
|
+
because they are pinned in the Gemfile. Any relevant comments from the
|
73
|
+
Gemfile will also be included, explaining the pins:
|
74
|
+
|
75
|
+
Note that the following gems are being held back:
|
55
76
|
|
56
|
-
|
57
|
-
|
77
|
+
rake 12.3.3 → 13.0.1 : pinned at ~> 12.0 #{gray('# Not ready for 13 yet')}
|
78
|
+
rubocop 0.89.0 → 0.89.1 : pinned at = 0.89.0
|
79
|
+
|
80
|
+
You may optionally specify one or more #{yellow('GEMS')} or pass #{yellow('OPTIONS')} to bundleup;
|
81
|
+
these will be passed through to bundler. See #{blue('bundle update --help')} for the
|
82
|
+
full list of the options that bundler supports.
|
83
|
+
|
84
|
+
Finally, bundleup also supports an experimental #{yellow('--update-gemfile')} option.
|
85
|
+
If specified, bundleup with modify the version restrictions specified in
|
86
|
+
your Gemfile so that it can install the latest version of each gem. For
|
87
|
+
instance, if your Gemfile specifies #{yellow('gem "sidekiq", "~> 5.2"')} but an update
|
88
|
+
to version 6.1.2 is available, bundleup will modify the Gemfile entry to
|
89
|
+
be #{yellow('gem "sidekiq", "~> 6.1"')} in order to permit the update.
|
90
|
+
|
91
|
+
Examples:
|
92
|
+
|
93
|
+
#{gray('# Update all gems')}
|
94
|
+
#{blue('$ bundleup')}
|
95
|
+
|
96
|
+
#{gray('# Only update gems in the development group')}
|
97
|
+
#{blue('$ bundleup --group=development')}
|
98
|
+
|
99
|
+
#{gray('# Only update the rake gem')}
|
100
|
+
#{blue('$ bundleup rake')}
|
101
|
+
|
102
|
+
#{gray('# Experimental: modify Gemfile to allow the latest gem versions')}
|
103
|
+
#{blue('$ bundleup --update-gemfile')}
|
104
|
+
|
105
|
+
USAGE
|
106
|
+
true
|
58
107
|
end
|
59
108
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
upgrades.zip(rows).each do |g, row|
|
65
|
-
puts color(g.color, row)
|
66
|
-
end
|
109
|
+
def assert_gemfile_and_lock_exist!
|
110
|
+
return if File.exist?("Gemfile") && File.exist?("Gemfile.lock")
|
111
|
+
|
112
|
+
raise Error, "Gemfile and Gemfile.lock must both be present."
|
67
113
|
end
|
68
114
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
115
|
+
def perform_analysis_and_optionally_bump_gemfile_versions # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
116
|
+
gemfile = Gemfile.new
|
117
|
+
lockfile_backup = Backup.new("Gemfile.lock")
|
118
|
+
update_report, pin_report, _, outdated_gems = perform_analysis
|
119
|
+
updatable_gems = gemfile.gem_pins_without_comments.slice(*outdated_gems.keys)
|
120
|
+
|
121
|
+
if updatable_gems.any? && @update_gemfile
|
122
|
+
lockfile_backup.restore
|
123
|
+
orig_gemfile = Gemfile.new
|
124
|
+
gemfile.relax_gem_pins!(updatable_gems.keys)
|
125
|
+
update_report, pin_report, new_versions, = perform_analysis
|
126
|
+
orig_gemfile.shift_gem_pins!(new_versions.slice(*updatable_gems.keys))
|
127
|
+
commands.install
|
72
128
|
end
|
73
|
-
puts rows.join("\n")
|
74
|
-
end
|
75
129
|
|
76
|
-
|
77
|
-
|
78
|
-
pin_operator, pin_version = gem.pin.split(" ", 2)
|
79
|
-
[":", "pinned at", pin_operator.rjust(2), pin_version, notes]
|
130
|
+
logger.clear_line
|
131
|
+
yield(update_report, pin_report)
|
80
132
|
end
|
81
133
|
|
82
|
-
def
|
83
|
-
|
134
|
+
def perform_analysis # rubocop:disable Metrics/AbcSize
|
135
|
+
gem_comments = Gemfile.new.gem_comments
|
136
|
+
commands.check? || commands.install
|
137
|
+
old_versions = commands.list
|
138
|
+
commands.update(args)
|
139
|
+
new_versions = commands.list
|
140
|
+
outdated_gems = commands.outdated
|
141
|
+
|
142
|
+
update_report = UpdateReport.new(old_versions: old_versions, new_versions: new_versions)
|
143
|
+
pin_report = PinReport.new(gem_versions: new_versions, outdated_gems: outdated_gems, gem_comments: gem_comments)
|
144
|
+
|
145
|
+
[update_report, pin_report, new_versions, outdated_gems]
|
84
146
|
end
|
85
147
|
end
|
86
148
|
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
|