bundler-patch 0.6.1 → 0.7.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/.gitignore +1 -0
- data/Gemfile +0 -1
- data/README.md +158 -75
- data/bin/{bundle-patch → bundler-patch} +1 -1
- data/bundler-patch.gemspec +4 -4
- data/lib/bundler/patch/advisory_consolidator.rb +17 -3
- data/lib/bundler/patch/cli.rb +112 -0
- data/lib/bundler/patch/conservative_definition.rb +24 -33
- data/lib/bundler/patch/conservative_resolver.rb +24 -27
- data/lib/bundler/patch/gems_to_patch_reconciler.rb +22 -0
- data/lib/bundler/patch/version.rb +1 -1
- data/lib/bundler/patch.rb +2 -1
- metadata +15 -14
- data/lib/bundler/patch/scanner.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3c93c22ec89fe88cd7225e53b51f4367109dcc5
|
4
|
+
data.tar.gz: db75eca98eb9622a9299668d7c8d5e77868b73f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cb458c1bfb71fd70fc1fc5145caa97c391aead8dddbc8fbc292de9ea310bdc47d3b15f5b63c0dc7325e6e53a502a2be0bfc5a5a7c6921aa653407d15e2920fa
|
7
|
+
data.tar.gz: 799ce18e825ddc8027b9ac06553874e094262f0f1d23951cf863b28fc402d4f5d1eb37f95c21fecf6d668f19730cd6810242eb533fd6e5241226f4d29d486589
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# bundler-patch
|
2
2
|
|
3
|
-
`bundler-patch` can update your
|
3
|
+
`bundler-patch` can update your gems conservatively to deal with vulnerable
|
4
|
+
gems or just get more current.
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
- Don't security patch past the minimum gem version required. (This may change).
|
10
|
-
- Minimal munging to existing version spec.
|
11
|
-
- Support a database of custom advisories for internal gems.
|
12
|
-
- Provide reasonable support for keeping a large number of apps and services up-to-date as automatically as possible.
|
6
|
+
By default, "conservatively" means it will prefer the latest releases from the
|
7
|
+
current version, over the latest minor releases or the latest major releases.
|
8
|
+
This is somewhat opposite from `bundle update` which prefers newest/major
|
9
|
+
versions first.
|
13
10
|
|
14
11
|
## Installation
|
15
12
|
|
@@ -17,120 +14,206 @@
|
|
17
14
|
|
18
15
|
## Usage
|
19
16
|
|
20
|
-
|
17
|
+
With the `bundler-patch` binary available, both `bundler-patch` and `bundle
|
18
|
+
patch` can be used to execute.
|
19
|
+
|
20
|
+
Without any options, all gems will be conservatively updated. An attempt to
|
21
|
+
upgrade any vulnerable gem (according to
|
22
|
+
https://github.com/rubysec/ruby-advisory-db) to a patched version will be
|
23
|
+
made.
|
24
|
+
|
25
|
+
$ bundle patch
|
26
|
+
|
27
|
+
"Conservatively" means it will sort all available versions to prefer the
|
28
|
+
latest releases from the current version, then the latest minor releases and
|
29
|
+
then the latest major releases.
|
30
|
+
|
31
|
+
Gem requirements as defined in the Gemfile will restrict the available version
|
32
|
+
options.
|
33
|
+
|
34
|
+
For example, if gem 'foo' is locked at 1.0.2, with no gem requirement defined
|
35
|
+
in the Gemfile, and versions 1.0.3, 1.0.4, 1.1.0, 1.1.1, 2.0.0 all exist, the
|
36
|
+
default order of preference will be "1.0.4, 1.0.3, 1.0.2, 1.1.1, 1.1.0,
|
37
|
+
2.0.0".
|
38
|
+
|
39
|
+
"Prefer" means that no available versions are removed from consideration*, to
|
40
|
+
help ensure a suitable dependency graph can be reconciled. This does mean some
|
41
|
+
gems cannot be upgraded or will be upgraded to unexpected versions.
|
21
42
|
|
22
|
-
|
43
|
+
_*That's a white-lie. bundler-patch will actually remove from consideration
|
44
|
+
any versions older than the currently locked version, which `bundle update`
|
45
|
+
will not do. It's not common, but it is possible for `bundle update` to
|
46
|
+
regress a gem to an older version, if necessary to reconcile the dependency
|
47
|
+
graph._
|
23
48
|
|
24
|
-
|
49
|
+
With no gem names provided on the command line, all gems will be unlocked and
|
50
|
+
open for updating. A list of gem names can be passed to restrict to just those
|
51
|
+
gems.
|
25
52
|
|
26
|
-
|
53
|
+
$ bundle patch foo bar
|
27
54
|
|
28
|
-
|
55
|
+
* `-m/--minor_preferred` option will give preference for minor versions over
|
56
|
+
release versions.
|
29
57
|
|
30
|
-
|
58
|
+
* `-p/--prefer_minimal` option will reverse the preference order within
|
59
|
+
release, minor, major groups to just 'the next' version. In the prior
|
60
|
+
example, the order of preference changes to "1.0.3, 1.0.4, 1.0.2, 1.1.0,
|
61
|
+
1.1.1, 2.0.0"
|
31
62
|
|
32
|
-
|
63
|
+
* `-s/--strict_updates` option will actually remove from consideration
|
64
|
+
versions outside either the current release (or minor version if `-m`
|
65
|
+
specified). This increases the chances of Bundler being unable to
|
66
|
+
reconcile the dependency graph and could raise a `VersionConflict`.
|
33
67
|
|
34
|
-
|
68
|
+
`bundler-patch` will also check for vulnerabilities based on the
|
69
|
+
`ruby-advisory-db`, but also will _modify_ (if necessary) the gem requirement
|
70
|
+
in the Gemfile on vulnerable gems to ensure they can be upgraded.
|
35
71
|
|
36
|
-
|
72
|
+
* `-l/--list` option will just list vulnerable gems. No updates will be
|
73
|
+
performed.
|
37
74
|
|
38
|
-
|
75
|
+
* `-a/--advisory_db_path` option can provide the path to an additional
|
76
|
+
custom ruby-advisory-db styled directory. The path should not include the
|
77
|
+
final `gems` directory, that will be appended automatically. This can be
|
78
|
+
used for flagging necessary updates for custom/internal gems.
|
39
79
|
|
40
|
-
|
41
|
-
|
80
|
+
The rules for updating vulnerable gems are almost identical to the general
|
81
|
+
`bundler-patch` behavior described above, and abide by the same options (`-m`,
|
82
|
+
`-p`, and `-s`) though there are some tweaks to encourage getting to at least
|
83
|
+
a patched version of the gem. Keep in mind Bundler may choose unexpected
|
84
|
+
versions in order to satisfy the dependency graph.
|
42
85
|
|
43
|
-
|
86
|
+
* `-v/--vulnerable_gems_only` option will automatically restrict the gems
|
87
|
+
to update list to currently vulnerable gems. If a combination of `-v` and
|
88
|
+
a list of gem names are passed, the `-v` option is ignored in favor of
|
89
|
+
the listed gem names.
|
44
90
|
|
45
|
-
To update any gem conservatively, use the `update` command:
|
46
91
|
|
47
|
-
|
92
|
+
## Examples
|
48
93
|
|
49
|
-
|
50
|
-
`1.4.3` and the latest available `1.4.x` version is `1.4.8`, it will attempt to upgrade it to `1.4.8`). If any
|
51
|
-
dependent gems need to be upgraded to a new minor or even major version, then it will do those as well, presuming the
|
52
|
-
gem requirements specified in the `Gemfile` also allow it.
|
94
|
+
### Single Gem
|
53
95
|
|
54
|
-
|
96
|
+
| Requirements| Locked | Available | Options | Result |
|
97
|
+
|-------------|---------|-----------------------------|----------|--------|
|
98
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | | 1.4.5 |
|
99
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | -m | 1.5.1 |
|
100
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | -p | 1.4.4 |
|
101
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | -m -p | 1.5.0 |
|
55
102
|
|
56
|
-
|
103
|
+
### Two Gems
|
57
104
|
|
58
|
-
|
59
|
-
due to the limitations, it will leave the requested gems at their current version.
|
105
|
+
Given the following gem specifications:
|
60
106
|
|
61
|
-
|
62
|
-
|
107
|
+
- foo 1.4.3, requires: ~> bar 2.0
|
108
|
+
- foo 1.4.4, requires: ~> bar 2.0
|
109
|
+
- foo 1.4.5, requires: ~> bar 2.1
|
110
|
+
- foo 1.5.0, requires: ~> bar 2.1
|
111
|
+
- foo 1.5.1, requires: ~> bar 3.0
|
112
|
+
- bar with versions 2.0.3, 2.0.4, 2.1.0, 2.1.1, 3.0.0
|
63
113
|
|
64
|
-
|
114
|
+
Gemfile:
|
65
115
|
|
66
|
-
|
67
|
-
not in strict mode.
|
116
|
+
gem 'foo'
|
68
117
|
|
69
|
-
|
70
|
-
available, then without `--minor_allowed` and without `--strict`, `foo` itself will only be upgraded to `1.4.8`, though
|
71
|
-
any gems further down the dependency tree could be upgraded to a new minor or major version if they have to be to use
|
72
|
-
`foo 1.4.8`.
|
118
|
+
Gemfile.lock:
|
73
119
|
|
74
|
-
|
75
|
-
|
76
|
-
|
120
|
+
foo (1.4.3)
|
121
|
+
bar (~> 2.0)
|
122
|
+
bar (2.0.3)
|
77
123
|
|
78
|
-
|
124
|
+
| # | Command Line | Result |
|
125
|
+
|---|--------------|---------------------------|
|
126
|
+
| 1 | | 'foo 1.4.5', 'bar 2.1.1' |
|
127
|
+
| 2 | foo | 'foo 1.4.4', 'bar 2.0.3' |
|
128
|
+
| 3 | -m | 'foo 1.5.1', 'bar 3.0.0' |
|
129
|
+
| 4 | -m -s | 'foo 1.5.0', 'bar 2.1.1' |
|
130
|
+
| 5 | -s | 'foo 1.4.4', 'bar 2.0.4' |
|
131
|
+
| 6 | -p | 'foo 1.4.4', 'bar 2.0.4' |
|
132
|
+
| 7 | -p -m | 'foo 1.5.0', 'bar 2.1.0' |
|
79
133
|
|
80
|
-
|
134
|
+
In case 1, `bar` is upgraded to 2.1.0, a minor version increase, because the
|
135
|
+
dependency from `foo` 1.4.5 required it.
|
81
136
|
|
82
|
-
|
137
|
+
In case 2, only `foo` is unlocked, so `bar` can only go to 1.4.4 to satisfy
|
138
|
+
the dependency from `foo`.
|
139
|
+
|
140
|
+
In case 3, `bar` goes up a whole major release, because a minor increase is
|
141
|
+
preferred now for `foo`.
|
142
|
+
|
143
|
+
In case 4, `foo` is preferred up to a 1.5.x, but 1.5.1 won't work because the
|
144
|
+
strict `-s` flag removes `bar` 3.0.0 from consideration since it's a major
|
145
|
+
increment.
|
146
|
+
|
147
|
+
In case 5, both `foo` and `bar` have any minor or major increments removed
|
148
|
+
from consideration, so the most they can move is up to 1.4.4 and 2.0.4.
|
149
|
+
|
150
|
+
In case 6, the prefer minimal switch `-p` means they only increment to the
|
151
|
+
next available release.
|
152
|
+
|
153
|
+
In case 7, the `-p` and `-m` switches allow both to move to just the next
|
154
|
+
available minor version.
|
83
155
|
|
84
156
|
|
85
157
|
### Troubleshooting
|
86
158
|
|
87
|
-
First
|
159
|
+
First, make sure the current `bundle` command itself runs to completion on its
|
160
|
+
own without any problems.
|
88
161
|
|
89
|
-
The most frequent problems with this tool involve expectations around what
|
90
|
-
|
91
|
-
|
162
|
+
The most frequent problems with this tool involve expectations around what
|
163
|
+
gems should or shouldn't be upgraded. This can quickly get complicated as even
|
164
|
+
a small dependency tree can involve many moving parts, and Bundler works hard
|
165
|
+
to find a combination that satisfies all of the dependencies and requirements.
|
92
166
|
|
93
|
-
You can get a (very verbose) look into how Bundler's resolution algorithm is
|
94
|
-
environment variable. While it can be
|
95
|
-
to
|
167
|
+
You can get a (very verbose) look into how Bundler's resolution algorithm is
|
168
|
+
working by setting the `DEBUG_RESOLVER` environment variable. While it can be
|
169
|
+
tricky to dig through, it should explain how it came to the conclusions it
|
170
|
+
came to.
|
96
171
|
|
97
|
-
Adding to the usual Bundler complexity, `bundler-patch` is injecting its own
|
98
|
-
its goals. If there's a bug
|
99
|
-
|
172
|
+
Adding to the usual Bundler complexity, `bundler-patch` is injecting its own
|
173
|
+
logic to the resolution process to achieve its goals. If there's a bug
|
174
|
+
involved, it's almost certainly in the `bundler-patch` code as Bundler has
|
175
|
+
been around a long time and has thorough testing and real world experience.
|
100
176
|
|
101
|
-
In particular, grep for 'Unwinding for conflict' to isolate some key issues
|
102
|
-
expect.
|
177
|
+
In particular, grep for 'Unwinding for conflict' to isolate some key issues
|
178
|
+
that may be preventing the outcome you expect.
|
103
179
|
|
104
|
-
`bundler-patch` can dump its own debug output, potentially helpful, with
|
180
|
+
`bundler-patch` can dump its own debug output, potentially helpful, with
|
181
|
+
`DEBUG_PATCH_RESOLVER`.
|
105
182
|
|
106
|
-
To get additional Bundler debugging output, enable the `DEBUG` env variable.
|
107
|
-
the downloading the full dependency
|
183
|
+
To get additional Bundler debugging output, enable the `DEBUG` env variable.
|
184
|
+
This will include all of the details of the downloading the full dependency
|
185
|
+
data from remote sources.
|
108
186
|
|
109
187
|
|
110
188
|
## Development
|
111
189
|
|
112
190
|
### Status
|
113
191
|
|
114
|
-
0.x versions are subject to breaking changes, there's a fair amount of
|
115
|
-
|
192
|
+
0.x versions are subject to breaking changes, there's a fair amount of
|
193
|
+
experimenting going on.
|
116
194
|
|
117
|
-
We'd love to get real world scenarios where things don't go as planned to help
|
118
|
-
believe a conservative update should
|
195
|
+
We'd love to get real world scenarios where things don't go as planned to help
|
196
|
+
flesh out varying details of what many believe a conservative update should
|
197
|
+
be.
|
119
198
|
|
120
199
|
### How To
|
121
200
|
|
122
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
123
|
-
|
201
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
202
|
+
run `rake spec` to run the tests. You can also run `bin/console` for an
|
203
|
+
interactive prompt that will allow you to experiment.
|
124
204
|
|
125
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
126
|
-
version number in `version.rb`, and then
|
127
|
-
|
205
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
206
|
+
To release a new version, update the version number in `version.rb`, and then
|
207
|
+
run `bundle exec rake release`, which will create a git tag for the version,
|
208
|
+
push git commits and tags, and push the `.gem` file to
|
209
|
+
[rubygems.org](https://rubygems.org).
|
128
210
|
|
129
211
|
## Contributing
|
130
212
|
|
131
|
-
Bug reports and pull requests are welcome on GitHub at
|
132
|
-
|
213
|
+
Bug reports and pull requests are welcome on GitHub at
|
214
|
+
https://github.com/livingsocial/bundler-patch.
|
133
215
|
|
134
216
|
## License
|
135
217
|
|
136
|
-
The gem is available as open source under the terms of the [MIT
|
218
|
+
The gem is available as open source under the terms of the [MIT
|
219
|
+
License](http://opensource.org/licenses/MIT).
|
data/bundler-patch.gemspec
CHANGED
@@ -16,14 +16,14 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
18
|
spec.bindir = 'bin'
|
19
|
-
spec.executables = ['
|
19
|
+
spec.executables = ['bundler-patch']
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
22
|
spec.add_dependency 'bundler-advise', '~> 1.0', '>= 1.0.3'
|
23
|
-
spec.add_dependency '
|
24
|
-
spec.add_dependency 'bundler', '~> 1.10'
|
23
|
+
spec.add_dependency 'slop', '~> 4.0'
|
24
|
+
spec.add_dependency 'bundler', '~> 1.10.0' # TODO: does not work with 1.11.x yet
|
25
25
|
|
26
|
-
spec.add_development_dependency 'bundler-fixture', '~> 1.
|
26
|
+
spec.add_development_dependency 'bundler-fixture', '~> 1.3'
|
27
27
|
spec.add_development_dependency 'pry'
|
28
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
29
29
|
spec.add_development_dependency 'rspec'
|
@@ -40,7 +40,7 @@ module Bundler::Patch
|
|
40
40
|
else
|
41
41
|
GemPatch.new(gem_name: up_spec.gem_name, old_version: old_version, patched_versions: up_spec.patched_versions)
|
42
42
|
end
|
43
|
-
end
|
43
|
+
end
|
44
44
|
end
|
45
45
|
|
46
46
|
private
|
@@ -61,13 +61,27 @@ module Bundler::Patch
|
|
61
61
|
end
|
62
62
|
|
63
63
|
class GemPatch
|
64
|
+
include Comparable
|
65
|
+
|
64
66
|
attr_reader :gem_name, :old_version, :new_version, :patched_versions
|
65
67
|
|
66
68
|
def initialize(gem_name:, old_version: nil, new_version: nil, patched_versions: nil)
|
67
69
|
@gem_name = gem_name
|
68
|
-
@old_version = old_version
|
69
|
-
@new_version = new_version
|
70
|
+
@old_version = Gem::Version.new(old_version) if old_version
|
71
|
+
@new_version = Gem::Version.new(new_version) if new_version
|
70
72
|
@patched_versions = patched_versions
|
71
73
|
end
|
74
|
+
|
75
|
+
def <=>(other)
|
76
|
+
self.gem_name <=> other.gem_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def hash
|
80
|
+
@gem_name.hash
|
81
|
+
end
|
82
|
+
|
83
|
+
def eql?(other)
|
84
|
+
@gem_name.eql?(other.gem_name)
|
85
|
+
end
|
72
86
|
end
|
73
87
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'bundler/advise'
|
2
|
+
require 'slop'
|
3
|
+
|
4
|
+
module Bundler::Patch
|
5
|
+
class CLI
|
6
|
+
def self.execute
|
7
|
+
opts = Slop.parse do |o|
|
8
|
+
o.banner = "Bundler Patch Version #{Bundler::Patch::VERSION}\nUsage: bundle patch [options] [gems_to_update]"
|
9
|
+
o.separator ''
|
10
|
+
o.separator 'bundler-patch attempts to update gems conservatively.'
|
11
|
+
o.separator ''
|
12
|
+
o.bool '-m', '--minor_preferred', 'Prefer update to the latest minor.release version.' # TODO: change to --minor_preferred
|
13
|
+
o.bool '-p', '--prefer_minimal', 'Prefer minimal version updates over most recent release (or minor if -m used).'
|
14
|
+
o.bool '-s', '--strict_updates', 'Restrict any gem to be upgraded past most recent release (or minor if -m used).'
|
15
|
+
o.bool '-l', '--list', 'List vulnerable gems and new version target. No updates will be performed.'
|
16
|
+
o.bool '-v', '--vulnerable_gems_only', 'Only update vulnerable gems.'
|
17
|
+
o.string '-a', '--advisory_db_path', 'Optional custom advisory db path. `gems` dir will be appended to this path.'
|
18
|
+
o.on('-h', 'Show this help') { show_help(o) }
|
19
|
+
o.on('--help', 'Show README.md') { show_readme }
|
20
|
+
end
|
21
|
+
|
22
|
+
show_readme if opts.arguments.include?('help')
|
23
|
+
options = opts.to_hash
|
24
|
+
options[:gems_to_update] = opts.arguments
|
25
|
+
|
26
|
+
CLI.new.patch(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.show_help(opts)
|
30
|
+
puts opts.to_s(prefix: ' ')
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.show_readme
|
35
|
+
Kernel.exec "less '#{File.expand_path('../../../../README.md', __FILE__)}'"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@no_vulns_message = 'No known vulnerabilities to update.'
|
41
|
+
end
|
42
|
+
|
43
|
+
def patch(options={})
|
44
|
+
Bundler.ui = Bundler::UI::Shell.new
|
45
|
+
|
46
|
+
return list(options) if options[:list]
|
47
|
+
|
48
|
+
_patch(options)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def conservative_update(gem_patches, options={}, bundler_def=nil)
|
54
|
+
prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep }
|
55
|
+
|
56
|
+
# update => true is very important, otherwise without any Gemfile changes, the installer
|
57
|
+
# may end up concluding everything can be resolved locally, nothing is changing,
|
58
|
+
# and then nothing is done. lib/bundler/cli/update.rb also hard-codes this.
|
59
|
+
Bundler::Installer.install(Bundler.root, prep.bundler_def, {'update' => true})
|
60
|
+
end
|
61
|
+
|
62
|
+
def list(options)
|
63
|
+
gem_patches = AdvisoryConsolidator.new(options).vulnerable_gems
|
64
|
+
|
65
|
+
if gem_patches.empty?
|
66
|
+
Bundler.ui.info @no_vulns_message
|
67
|
+
else
|
68
|
+
Bundler.ui.info '' # extra line to separate from advisory db update text
|
69
|
+
Bundler.ui.info 'Detected vulnerabilities:'
|
70
|
+
Bundler.ui.info '-------------------------'
|
71
|
+
Bundler.ui.info gem_patches.map(&:to_s).uniq.sort.join("\n")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def _patch(options)
|
76
|
+
vulnerable_patches = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch
|
77
|
+
requested_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) }
|
78
|
+
|
79
|
+
all_gem_patches = GemsToPatchReconciler.new(vulnerable_patches, requested_patches).reconciled_patches
|
80
|
+
all_gem_patches.push(*vulnerable_patches) if options[:vulnerable_gems_only] && all_gem_patches.empty?
|
81
|
+
|
82
|
+
vulnerable_patches, warnings = vulnerable_patches.partition { |gp| !gp.new_version.nil? }
|
83
|
+
|
84
|
+
unless warnings.empty?
|
85
|
+
warnings.each do |gp|
|
86
|
+
Bundler.ui.warn "* Could not attempt upgrade for #{gp.gem_name} from #{gp.old_version} to any patched versions " \
|
87
|
+
+ "#{gp.patched_versions.join(', ')}. Most often this is because a major version increment would be " \
|
88
|
+
+ "required and it's safer for a major version increase to be done manually."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if vulnerable_patches.empty?
|
93
|
+
Bundler.ui.info @no_vulns_message
|
94
|
+
else
|
95
|
+
vulnerable_patches.each do |gp|
|
96
|
+
Bundler.ui.info "Attempting conservative update for vulnerable gem '#{gp.gem_name}': #{gp.old_version} => #{gp.new_version}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if all_gem_patches.empty?
|
101
|
+
Bundler.ui.info 'Updating all gems conservatively.'
|
102
|
+
else
|
103
|
+
Bundler.ui.info "Updating '#{all_gem_patches.map(&:gem_name).join(' ')}' conservatively."
|
104
|
+
end
|
105
|
+
conservative_update(all_gem_patches, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if __FILE__ == $0
|
111
|
+
Bundler::Patch::CLI.execute
|
112
|
+
end
|
@@ -3,7 +3,7 @@ module Bundler::Patch
|
|
3
3
|
attr_accessor :gems_to_update
|
4
4
|
|
5
5
|
# pass-through options to ConservativeResolver
|
6
|
-
attr_accessor :strict, :
|
6
|
+
attr_accessor :strict, :minor_preferred, :prefer_minimal
|
7
7
|
|
8
8
|
# This copies more code than I'd like out of Bundler::Definition, but for now seems the least invasive way in.
|
9
9
|
# Backing up and intervening into the creation of a Definition instance itself involves a lot more code, a lot
|
@@ -31,7 +31,8 @@ module Bundler::Patch
|
|
31
31
|
resolver.gems_to_update = @gems_to_update
|
32
32
|
resolver.locked_specs = locked_specs
|
33
33
|
resolver.strict = @strict
|
34
|
-
resolver.
|
34
|
+
resolver.minor_preferred = @minor_preferred
|
35
|
+
resolver.prefer_minimal = @prefer_minimal
|
35
36
|
result = resolver.start(expanded_dependencies)
|
36
37
|
spec_set = Bundler::SpecSet.new(result)
|
37
38
|
|
@@ -46,7 +47,7 @@ module Bundler::Patch
|
|
46
47
|
|
47
48
|
def initialize(bundler_def, gem_patches, options)
|
48
49
|
@bundler_def = bundler_def
|
49
|
-
@gems_to_update =
|
50
|
+
@gems_to_update = GemsToPatch.new(gem_patches)
|
50
51
|
@options = options
|
51
52
|
end
|
52
53
|
|
@@ -54,41 +55,39 @@ module Bundler::Patch
|
|
54
55
|
@bundler_def ||= Bundler.definition(@gems_to_update.to_bundler_definition)
|
55
56
|
@bundler_def.extend ConservativeDefinition
|
56
57
|
@bundler_def.gems_to_update = @gems_to_update
|
57
|
-
@bundler_def.strict = @options[:
|
58
|
-
@bundler_def.
|
58
|
+
@bundler_def.strict = @options[:strict_updates]
|
59
|
+
@bundler_def.minor_preferred = @options[:minor_preferred]
|
60
|
+
@bundler_def.prefer_minimal = @options[:prefer_minimal]
|
59
61
|
fixup_empty_remotes if @gems_to_update.to_bundler_definition === true
|
60
62
|
@bundler_def
|
61
63
|
end
|
62
64
|
|
63
|
-
# This
|
64
|
-
#
|
65
|
-
#
|
66
|
-
# set in rubygems_aggregrate, it won't work.
|
65
|
+
# This came out a real-life case with sidekiq and sidekiq-pro where the sidekiq-pro gem is served from their gem
|
66
|
+
# server and depends on the open-source sidekiq gem served from rubygems.org, and when patching those, without
|
67
|
+
# the appropriate remotes being set in rubygems_aggregrate, it won't work.
|
67
68
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
# is initialized to empty array, and the related rubygems_aggregrate
|
75
|
-
# source instance ends up with no @remotes set in it, which I think happens during
|
76
|
-
# converge_sources. Without those set, then the index will list no gem versions in
|
77
|
-
# some cases. (It was complicated enough to discover this patch, I haven't fully
|
78
|
-
# worked out the flaw, which still could be on my side of the fence).
|
69
|
+
# The underlying issue in Bundler 1.10 appears to be when the Definition constructor receives `true` as the
|
70
|
+
# `unlock` parameter, then @locked_sources is initialized to empty array, and the related rubygems_aggregrate
|
71
|
+
# source instance ends up with no @remotes set in it, which I think happens during converge_sources. Without
|
72
|
+
# those set, then the index will list no gem versions in some cases. (It was complicated enough to discover this
|
73
|
+
# patch, I haven't fully worked out the flaw, though I believe I recreated the problem with plain ol `bundle
|
74
|
+
# update`).
|
79
75
|
def fixup_empty_remotes
|
76
|
+
STDERR.puts 'fixing empty remotes' if ENV['DEBUG_PATCH_RESOLVER']
|
80
77
|
b_sources = @bundler_def.send(:sources)
|
81
78
|
empty_remotes = b_sources.rubygems_sources.detect { |s| s.remotes.empty? }
|
79
|
+
STDERR.puts "empty_remotes: <#{empty_remotes}>" if ENV['DEBUG_PATCH_RESOLVER']
|
82
80
|
empty_remotes.remotes.push(*b_sources.rubygems_remotes) if empty_remotes
|
81
|
+
empty_remotes = b_sources.rubygems_sources.detect { |s| s.remotes.empty? }
|
82
|
+
STDERR.puts "empty_remotes after fixed: <#{empty_remotes}>" if ENV['DEBUG_PATCH_RESOLVER']
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
class
|
87
|
-
attr_reader :gem_patches
|
86
|
+
class GemsToPatch
|
87
|
+
attr_reader :gem_patches
|
88
88
|
|
89
|
-
def initialize(gem_patches
|
89
|
+
def initialize(gem_patches)
|
90
90
|
@gem_patches = Array(gem_patches)
|
91
|
-
@patching = options[:patching]
|
92
91
|
end
|
93
92
|
|
94
93
|
def to_bundler_definition
|
@@ -99,20 +98,12 @@ module Bundler::Patch
|
|
99
98
|
@gem_patches.map(&:gem_name)
|
100
99
|
end
|
101
100
|
|
102
|
-
def patching_gem?(gem_name)
|
103
|
-
@patching && to_gem_names.include?(gem_name)
|
104
|
-
end
|
105
|
-
|
106
|
-
def patching_but_not_this_gem?(gem_name)
|
107
|
-
@patching && !to_gem_names.include?(gem_name)
|
108
|
-
end
|
109
|
-
|
110
101
|
def gem_patch_for(gem_name)
|
111
102
|
@gem_patches.detect { |gp| gp.gem_name == gem_name }
|
112
103
|
end
|
113
104
|
|
114
105
|
def unlocking_all?
|
115
|
-
@
|
106
|
+
@gem_patches.empty?
|
116
107
|
end
|
117
108
|
|
118
109
|
def unlocking_gem?(gem_name)
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Bundler::Patch
|
2
2
|
class ConservativeResolver < Bundler::Resolver
|
3
|
-
attr_accessor :locked_specs, :gems_to_update, :strict, :
|
3
|
+
attr_accessor :locked_specs, :gems_to_update, :strict, :minor_preferred, :prefer_minimal
|
4
4
|
|
5
5
|
def search_for(dependency)
|
6
6
|
res = super(dependency)
|
7
7
|
|
8
8
|
dep = dependency.dep unless dependency.is_a? Gem::Dependency
|
9
|
+
|
9
10
|
@conservative_search_for ||= {}
|
10
|
-
|
11
|
-
begin
|
11
|
+
res = @conservative_search_for[dep] ||= begin
|
12
12
|
gem_name = dep.name
|
13
13
|
|
14
14
|
# An Array per version returned, different entries for different platforms.
|
@@ -18,20 +18,14 @@ module Bundler::Patch
|
|
18
18
|
(@strict ?
|
19
19
|
filter_specs(res, locked_spec) :
|
20
20
|
sort_specs(res, locked_spec)).tap do |res|
|
21
|
-
if ENV['DEBUG_PATCH_RESOLVER']
|
22
|
-
# TODO: if we keep this, gotta go through Bundler.ui
|
23
|
-
begin
|
24
|
-
if res
|
25
|
-
p debug_format_result(dep, res)
|
26
|
-
else
|
27
|
-
p "No res for #{dep.to_s}. Orig res: #{super(dependency)}"
|
28
|
-
end
|
29
|
-
rescue => e
|
30
|
-
p [e.message, e.backtrace[0..5]]
|
31
|
-
end
|
32
|
-
end
|
21
|
+
STDERR.puts debug_format_result(dep, res).inspect if ENV['DEBUG_PATCH_RESOLVER']
|
33
22
|
end
|
34
23
|
end
|
24
|
+
|
25
|
+
# dup is important, in weird (large) cases Bundler will empty the result array corrupting the cache.
|
26
|
+
# Bundler itself doesn't have this problem because the super search_for does a select on its cached
|
27
|
+
# search results, effectively duping it.
|
28
|
+
res.dup
|
35
29
|
end
|
36
30
|
|
37
31
|
def debug_format_result(dep, res)
|
@@ -50,7 +44,7 @@ module Bundler::Patch
|
|
50
44
|
gsv = gem_spec.version
|
51
45
|
lsv = locked_spec.version
|
52
46
|
|
53
|
-
must_match = @
|
47
|
+
must_match = @minor_preferred ? [0] : [0, 1]
|
54
48
|
|
55
49
|
matches = must_match.map { |idx| gsv.segments[idx] == lsv.segments[idx] }
|
56
50
|
(matches.uniq == [true]) ? gsv.send(:>=, lsv) : false
|
@@ -62,6 +56,7 @@ module Bundler::Patch
|
|
62
56
|
sort_specs(res, locked_spec)
|
63
57
|
end
|
64
58
|
|
59
|
+
# reminder: sort still filters anything older than locked version
|
65
60
|
def sort_specs(specs, locked_spec)
|
66
61
|
return specs unless locked_spec
|
67
62
|
gem_name = locked_spec.name
|
@@ -72,33 +67,35 @@ module Bundler::Patch
|
|
72
67
|
filtered.sort do |a, b|
|
73
68
|
a_ver = a.first.version
|
74
69
|
b_ver = b.first.version
|
70
|
+
gem_patch = @gems_to_update.gem_patch_for(gem_name)
|
71
|
+
new_version = gem_patch ? gem_patch.new_version : nil
|
75
72
|
case
|
76
73
|
when a_ver.segments[0] != b_ver.segments[0]
|
77
74
|
b_ver <=> a_ver
|
78
|
-
when !@
|
75
|
+
when !@minor_preferred && (a_ver.segments[1] != b_ver.segments[1])
|
76
|
+
b_ver <=> a_ver
|
77
|
+
when @prefer_minimal && !@gems_to_update.unlocking_gem?(gem_name)
|
79
78
|
b_ver <=> a_ver
|
80
|
-
when @gems_to_update.
|
79
|
+
when @prefer_minimal && @gems_to_update.unlocking_gem?(gem_name) &&
|
80
|
+
(![a_ver, b_ver].include?(locked_version) &&
|
81
|
+
(!new_version || (new_version && a_ver >= new_version && b_ver >= new_version)))
|
81
82
|
b_ver <=> a_ver
|
82
83
|
else
|
83
84
|
a_ver <=> b_ver
|
84
85
|
end
|
85
86
|
end.tap do |result|
|
86
87
|
if @gems_to_update.unlocking_gem?(gem_name)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# TODO: not sure if we want this special logic to remain or not.
|
91
|
-
new_version = @gems_to_update.gem_patch_for(gem_name).new_version
|
92
|
-
swap_version_to_end(specs, new_version, result) if new_version
|
88
|
+
gem_patch = @gems_to_update.gem_patch_for(gem_name)
|
89
|
+
if gem_patch && gem_patch.new_version && @prefer_minimal
|
90
|
+
move_version_to_end(specs, gem_patch.new_version, result)
|
93
91
|
end
|
94
92
|
else
|
95
|
-
|
96
|
-
swap_version_to_end(specs, locked_version, result)
|
93
|
+
move_version_to_end(specs, locked_version, result)
|
97
94
|
end
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
101
|
-
def
|
98
|
+
def move_version_to_end(specs, version, result)
|
102
99
|
spec_group = specs.detect { |s| s.first.version.to_s == version.to_s }
|
103
100
|
if spec_group
|
104
101
|
result.reject! { |s| s.first.version.to_s === version.to_s }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class GemsToPatchReconciler
|
2
|
+
attr_reader :reconciled_patches
|
3
|
+
|
4
|
+
def initialize(vulnerable_patches, requested_patches=[])
|
5
|
+
@vulnerable_patches = vulnerable_patches
|
6
|
+
@requested_patches = requested_patches
|
7
|
+
reconcile
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def reconcile
|
13
|
+
@reconciled_patches = []
|
14
|
+
unless @requested_patches.empty?
|
15
|
+
@vulnerable_patches.reject! { |gp| !@requested_patches.include?(gp) }
|
16
|
+
@reconciled_patches.push(*((@vulnerable_patches + @requested_patches).uniq))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
data/lib/bundler/patch.rb
CHANGED
@@ -11,5 +11,6 @@ require 'bundler/patch/ruby_version'
|
|
11
11
|
require 'bundler/patch/advisory_consolidator'
|
12
12
|
require 'bundler/patch/conservative_definition'
|
13
13
|
require 'bundler/patch/conservative_resolver'
|
14
|
-
require 'bundler/patch/
|
14
|
+
require 'bundler/patch/gems_to_patch_reconciler'
|
15
|
+
require 'bundler/patch/cli'
|
15
16
|
require 'bundler/patch/version'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler-patch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- chrismo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler-advise
|
@@ -31,47 +31,47 @@ dependencies:
|
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 1.0.3
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: slop
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '0'
|
39
|
+
version: '4.0'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "
|
44
|
+
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
46
|
+
version: '4.0'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bundler
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: 1.10.0
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: 1.10.0
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: bundler-fixture
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '1.
|
67
|
+
version: '1.3'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '1.
|
74
|
+
version: '1.3'
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: pry
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,7 +118,7 @@ description:
|
|
118
118
|
email:
|
119
119
|
- chrismo@clabs.org
|
120
120
|
executables:
|
121
|
-
-
|
121
|
+
- bundler-patch
|
122
122
|
extensions: []
|
123
123
|
extra_rdoc_files: []
|
124
124
|
files:
|
@@ -130,17 +130,18 @@ files:
|
|
130
130
|
- LICENSE.txt
|
131
131
|
- README.md
|
132
132
|
- Rakefile
|
133
|
-
- bin/
|
133
|
+
- bin/bundler-patch
|
134
134
|
- bin/console
|
135
135
|
- bin/setup
|
136
136
|
- bundler-patch.gemspec
|
137
137
|
- lib/bundler/patch.rb
|
138
138
|
- lib/bundler/patch/advisory_consolidator.rb
|
139
|
+
- lib/bundler/patch/cli.rb
|
139
140
|
- lib/bundler/patch/conservative_definition.rb
|
140
141
|
- lib/bundler/patch/conservative_resolver.rb
|
141
142
|
- lib/bundler/patch/gemfile.rb
|
143
|
+
- lib/bundler/patch/gems_to_patch_reconciler.rb
|
142
144
|
- lib/bundler/patch/ruby_version.rb
|
143
|
-
- lib/bundler/patch/scanner.rb
|
144
145
|
- lib/bundler/patch/updater.rb
|
145
146
|
- lib/bundler/patch/version.rb
|
146
147
|
homepage: https://github.com/livingsocial/bundler-patch
|
@@ -1,85 +0,0 @@
|
|
1
|
-
require 'bundler/advise'
|
2
|
-
require 'boson/runner'
|
3
|
-
|
4
|
-
module Bundler::Patch
|
5
|
-
class Scanner < Boson::Runner
|
6
|
-
def initialize
|
7
|
-
@no_vulns_message = 'No known vulnerabilities to update.'
|
8
|
-
end
|
9
|
-
|
10
|
-
option :advisory_db_path, type: :string, desc: 'Optional custom advisory db path.'
|
11
|
-
desc 'Scans current directory for known vulnerabilities and outputs them.'
|
12
|
-
|
13
|
-
def scan(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
14
|
-
header
|
15
|
-
gem_patches = AdvisoryConsolidator.new(options).vulnerable_gems
|
16
|
-
|
17
|
-
if gem_patches.empty?
|
18
|
-
puts @no_vulns_message
|
19
|
-
else
|
20
|
-
puts # extra line to separate from advisory db update text
|
21
|
-
puts 'Detected vulnerabilities:'
|
22
|
-
puts '-------------------------'
|
23
|
-
puts gem_patches.map(&:to_s).uniq.sort.join("\n")
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
option :strict, type: :boolean, desc: 'Do not allow any gem to be upgraded past most recent release (or minor if -m used). Sometimes raises VersionConflict.'
|
28
|
-
option :minor_allowed, type: :boolean, desc: 'Upgrade to the latest minor.release version.'
|
29
|
-
option :advisory_db_path, type: :string, desc: 'Optional custom advisory db path.'
|
30
|
-
desc 'Scans current directory for known vulnerabilities and attempts to patch your files to fix them.'
|
31
|
-
|
32
|
-
def patch(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
33
|
-
header
|
34
|
-
|
35
|
-
gem_patches, warnings = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch
|
36
|
-
|
37
|
-
unless warnings.empty?
|
38
|
-
warnings.each do |gp|
|
39
|
-
# TODO: Bundler.ui
|
40
|
-
puts "* Could not attempt upgrade for #{gp.gem_name} from #{gp.old_version} to any patched versions " \
|
41
|
-
+ "#{gp.patched_versions.join(', ')}. Most often this is because a major version increment would be " \
|
42
|
-
+ "required and it's safer for a major version increase to be done manually."
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
if gem_patches.empty?
|
47
|
-
puts @no_vulns_message
|
48
|
-
else
|
49
|
-
gem_patches.each do |gp|
|
50
|
-
puts "Attempting #{gp.gem_name}: #{gp.old_version} => #{gp.new_version}" # TODO: Bundler.ui
|
51
|
-
end
|
52
|
-
|
53
|
-
puts "Updating '#{gem_patches.map(&:gem_name).join(' ')}' to address vulnerabilities"
|
54
|
-
conservative_update(gem_patches, options.merge(patching: true))
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
option :strict, type: :boolean, desc: 'Do not allow any gem to be upgraded past most recent release (or minor if -m used). Sometimes raises VersionConflict.'
|
59
|
-
option :minor_allowed, type: :boolean, desc: 'Upgrade to the latest minor.release version.'
|
60
|
-
# TODO: be nice to support array w/o quotes like real `bundle update`
|
61
|
-
option :gems_to_update, type: :array, split: ' ', desc: 'Optional list of gems to update, in quotes, space delimited'
|
62
|
-
desc 'Update spec gems to the latest release version. Required gems could be upgraded to latest minor or major if necessary.'
|
63
|
-
config default_option: 'gems_to_update'
|
64
|
-
|
65
|
-
def update(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
66
|
-
header
|
67
|
-
gem_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) }
|
68
|
-
conservative_update(gem_patches, options.merge(updating: true))
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
def header
|
74
|
-
puts "Bundler Patch Version #{Bundler::Patch::VERSION}"
|
75
|
-
end
|
76
|
-
|
77
|
-
def conservative_update(gem_patches, options={}, bundler_def=nil)
|
78
|
-
Bundler.ui = Bundler::UI::Shell.new
|
79
|
-
|
80
|
-
prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep }
|
81
|
-
|
82
|
-
Bundler::Installer.install(Bundler.root, prep.bundler_def)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|