bundler-audited_update 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/bundler/audited_update.rb +252 -0
  3. metadata +101 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bbfc1d502ed22c2bc57ce8560f3851f6dae3cb95914c3499cac9759ad689abfa
4
+ data.tar.gz: 93ab57d94ee1ef17af96febfe7310cd790028d573ed652336087fe8bd525a05c
5
+ SHA512:
6
+ metadata.gz: 102338a0fc8098a5acac9cd047928ee09b92be8b70ae80fa5ff18222c52a7f28d37096cbeb6577b28cff190c71c79f3ff17aab57abaf3054a8721198ad89c2b9
7
+ data.tar.gz: ac8ef5414c618a802943495281dcf204d945ac362cbe318fb93e91094863dbb208dad34db8ca24571e79f78f13f216816fd8381f8b2194dfd6edcdee9ecf4ba1
@@ -0,0 +1,252 @@
1
+ require 'bundler'
2
+ require 'bundler/lockfile_parser'
3
+ require "bundler/cli"
4
+ require "bundler/cli/update"
5
+ require "open-uri"
6
+ require 'net/http'
7
+ require 'json'
8
+ require 'versionomy'
9
+ require 'launchy'
10
+
11
+ module Bundler
12
+ class AuditedUpdate
13
+ def run!
14
+ @before_specs = gem_specs
15
+ bundle_update
16
+ @after_specs = gem_specs
17
+
18
+ @output = ""
19
+ @output += "# Gem Changes\n"
20
+ @output += "\n"
21
+
22
+ output_gems("Added Gems", added_gems)
23
+ output_gems("Removed Gems", removed_gems)
24
+ output_changed_gems(changed_gems)
25
+
26
+ puts "\n\n\n\n\n"
27
+
28
+ puts "--------------------------------"
29
+ puts "Audited Bundle Upgrade Text"
30
+ puts "--------------------------------"
31
+
32
+ puts @output
33
+ end
34
+
35
+ def output_gems(title, gems)
36
+ return if gems.empty?
37
+
38
+ @output += "## #{title}\n"
39
+ @output += "\n"
40
+ gems.each { |the_gem| gem_output(the_gem.name, the_gem.version) }
41
+
42
+ @output += "\n"
43
+ end
44
+
45
+ def output_changed_gems(gems)
46
+ return if gems.empty?
47
+
48
+ major_upgrades = gems.select {|_, versions| versions[:before].major != versions[:after].major }
49
+ minor_upgrades = gems.select {|name, versions| !major_upgrades.keys.include?(name) && versions[:before].minor != versions[:after].minor }
50
+ point_upgrades = gems.reject { |name, _| major_upgrades.keys.include?(name) || minor_upgrades.keys.include?(name) }
51
+
52
+ @output += "## Upgraded Gems\n"
53
+ @output += "\n"
54
+
55
+ output_changed_gems_section("Major", major_upgrades)
56
+ output_changed_gems_section("Minor", minor_upgrades)
57
+ output_changed_gems_section("Point", point_upgrades)
58
+
59
+ @output += "\n"
60
+ end
61
+
62
+ def output_changed_gems_section(title, gems)
63
+ @output += "### #{title} Upgrades\n"
64
+ @output += "\n"
65
+ gems.each { |name, versions| gem_output(name, versions) }
66
+
67
+ @output += "\n"
68
+ end
69
+
70
+ def gem_output(name, version)
71
+ if version.is_a? Hash
72
+ version_string = "#{version[:before]} -> #{version[:after]}"
73
+ info = gem_info(name, version[:after])
74
+
75
+ guessed_source = gem_source_url(info)
76
+
77
+ if guessed_source
78
+ changelog_text, changelog_url = guess_changelog(guessed_source)
79
+
80
+ if changelog_text && !changelog_text.empty?
81
+ puts "\n\n\n"
82
+ puts "--------------------------------"
83
+ puts "#{name} changes from #{version_string}"
84
+ puts "--------------------------------"
85
+ # Output the changelog text from top until the line that contains the previous version
86
+ changelog_output = changelog_text.split(/^.*#{version[:before]}/, 2).first
87
+ # Max 200 lines
88
+ changelog_output = changelog_output.lines.to_a[0...200].join
89
+ puts changelog_output
90
+ impact = nil
91
+ while impact.nil?
92
+ puts "Does this impact your application? (y/n/[o]pen in browser)"
93
+ answer = gets
94
+ answer = answer.downcase.strip
95
+ if answer == "y"
96
+ puts "What's a short description of the impact?"
97
+ impact = gets
98
+ elsif answer == "n"
99
+ impact = "No impact"
100
+ elsif answer == "o"
101
+ Launchy.open(changelog_url)
102
+ else
103
+ puts "Invalid answer"
104
+ end
105
+ end
106
+
107
+ change_detail = impact
108
+ end
109
+ end
110
+
111
+ else
112
+ version_string = version
113
+ info = gem_info(name, version)
114
+ guessed_source = gem_source_url(info)
115
+ change_detail = guessed_source
116
+ end
117
+
118
+ change_detail ||= "Unsupported source URL, cannot search for changelog"
119
+
120
+
121
+ @output += "* #{name} (#{version_string}): #{change_detail}\n"
122
+ end
123
+
124
+ def guess_changelog(root_url)
125
+ filenames = %w{
126
+ CHANGELOG
127
+ CHANGELOG.md
128
+ Changelog
129
+ Changelog.md
130
+ History
131
+ History.md
132
+ History.rdoc
133
+ Changes
134
+ CHANGES
135
+ CHANGES.md
136
+ NEWS
137
+ }
138
+ changelog_text = nil
139
+ changelog_url = nil
140
+
141
+ filenames.each do |filename|
142
+ changelog_text = try_changelog_url(root_url, filename)
143
+ if changelog_text
144
+ changelog_url = changelog_url_for(root_url, filename)
145
+ break
146
+ end
147
+ end
148
+
149
+ unless changelog_text
150
+ changelog_text = github_releases_bodies(root_url)
151
+ changelog_url = github_releases_url(root_url) if changelog_text
152
+ end
153
+
154
+ unless changelog_text
155
+ changelog_text = "Could not find changelog URL, try manually"
156
+ changelog_url = root_url
157
+ end
158
+
159
+ [changelog_text, changelog_url]
160
+ end
161
+
162
+ def gem_source_url(info)
163
+ source_url_guess = info["source_code_uri"] || info["homepage_uri"]
164
+ if source_url_guess&.include?("github.com")
165
+ source_url_guess
166
+ else
167
+ # Unsupported source URL
168
+ nil
169
+ end
170
+ end
171
+
172
+ def added_gems
173
+ @after_specs.reject {|spec| @before_specs.map(&:name).include?(spec.name) }
174
+ end
175
+
176
+ def removed_gems
177
+ @before_specs.reject {|spec| @after_specs.map(&:name).include?(spec.name) }
178
+ end
179
+
180
+ def changed_gems
181
+ gems = @after_specs.reject do |after_spec|
182
+ next unless after_spec
183
+ before_spec = @before_specs.find {|before_spec| before_spec && before_spec.name == after_spec.name }
184
+ next unless before_spec # new gem
185
+ before_spec.version == after_spec.version
186
+ end
187
+
188
+ gems.map! do |the_gem|
189
+ before_gem = @before_specs.find {|before_spec| before_spec.name == the_gem.name }
190
+ after_gem = @after_specs.find {|after_spec| after_spec.name == the_gem.name }
191
+ versions = {
192
+ before: Versionomy.parse(before_gem.version.to_s),
193
+ after: Versionomy.parse(after_gem.version.to_s)
194
+ }
195
+ [the_gem.name, versions]
196
+ end
197
+
198
+ gems.to_h
199
+ end
200
+
201
+ def github_releases_url(source_root)
202
+ api_source_root = source_root.gsub("github.com/", "api.github.com/repos/")
203
+ "#{api_source_root}/releases"
204
+ end
205
+
206
+ def github_releases_bodies(source_root)
207
+ response = URI.parse(github_releases_url(source_root)).read
208
+ releases = JSON.parse(response)
209
+ release_notes = ""
210
+ releases.each do |release|
211
+ next unless release["body"]
212
+ release_notes += release["name"]
213
+ release_notes += "\n"
214
+ release_notes += release["body"]
215
+ release_notes += "\n"
216
+ release_notes += "\n"
217
+ end
218
+ release_notes
219
+ rescue OpenURI::HTTPError
220
+ return nil
221
+ end
222
+
223
+ def changelog_url_for(source_root, filename)
224
+ raw_source_root = source_root.gsub("github.com", "raw.githubusercontent.com")
225
+ url = "#{raw_source_root}/master/#{filename}"
226
+ end
227
+
228
+ def try_changelog_url(source_root, filename)
229
+ URI.parse(changelog_url_for(source_root, filename)).read
230
+ rescue OpenURI::HTTPError
231
+ return nil
232
+ end
233
+
234
+ def gem_info(name, version)
235
+ gem_url = "https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}"
236
+ response = URI.parse(gem_url).read
237
+ JSON.parse(response)
238
+ end
239
+
240
+ def gem_specs
241
+ root = File.expand_path(Dir.pwd)
242
+ lockfile = Bundler::LockfileParser.new(
243
+ File.read(File.join(root, 'Gemfile.lock'))
244
+ )
245
+ lockfile.specs
246
+ end
247
+
248
+ def bundle_update
249
+ Bundler::CLI::Update.new({}, []).run
250
+ end
251
+ end
252
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bundler-audited_update
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brendan Mulholland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: versionomy
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: launchy
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Runs a bundle upgrade, shows the changelog for each gem that was upgraded,
70
+ and outputs a summary view of gem changes plus their impact.
71
+ email: audited_bundle_update@bmulholland.ca
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/bundler/audited_update.rb
77
+ homepage: http://rubygems.org/gems/bundler-audited_update
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.7.6
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Streamlined bundler audit with Changelog detection and summary ouput
101
+ test_files: []