bundler-audited_update 0.1.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.
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: []