danger 3.1.1 → 3.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3d5dcfa64082b3686cad3e6ae09ca23af616914
4
- data.tar.gz: 151921230dd40b9c27c54c8def4a6740e24b9435
3
+ metadata.gz: 52836eb7928792ec1e8ec0ffb78076c0e1d6cae0
4
+ data.tar.gz: 9d550c596cfe8e25764a0662965fc4e4768ddf54
5
5
  SHA512:
6
- metadata.gz: ff42d5811f941c173b73ea315ba646fcabf692178bbf91d41a19dd5a32d6a151604d40687aff54a3f66ed0e19fa03c19b3fa1e702bda3a3d24a6e4d2cafb2d71
7
- data.tar.gz: 87db09b4a3696166880024b6755c25e17c637724b2e1b4c9e22d1732e7ad12675c7ab1d37d703399b214a4e31971bf24e75d7ffa3205129a98519aae4ddfe2ad
6
+ metadata.gz: 7103cf673b79e9827e1eef36a08c8001540fff0e0f5f535d2d4ce01a4b3d836546ea4cf083e736c5bf4ce0f3a3e9f3f93829713313672fa2d6f10f7c06ab591a
7
+ data.tar.gz: 2af2f1aebc31d5c3b573e2af054a46975160c9b200b3d06511036d0b7a05862b36869273a8d99083724725f11edf97632664200e752b5b63aba3f26695cf6a0b
@@ -26,4 +26,18 @@ module Danger
26
26
  end
27
27
  return Gem::Specification.find_by_name(gem_name).gem_dir
28
28
  end
29
+
30
+ # @return [String] Latest version of Danger on https://rubygems.org
31
+ def self.danger_outdated?
32
+ require "danger/clients/rubygems_client"
33
+ latest_version = RubyGemsClient.latest_danger_version
34
+
35
+ if Gem::Version.new(latest_version) > Gem::Version.new(Danger::VERSION)
36
+ latest_version
37
+ else
38
+ false
39
+ end
40
+ rescue StandardError => _e
41
+ false
42
+ end
29
43
  end
@@ -38,7 +38,7 @@ module Danger
38
38
  end
39
39
 
40
40
  def supported_request_sources
41
- @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab]
41
+ @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab, Danger::RequestSources::BitbucketServer]
42
42
  end
43
43
 
44
44
  def initialize(env)
@@ -12,6 +12,10 @@ module Danger
12
12
  env.key? "DANGER_USE_LOCAL_GIT"
13
13
  end
14
14
 
15
+ def self.validates_as_pr?(_env)
16
+ false
17
+ end
18
+
15
19
  def git
16
20
  @git ||= GitRepo.new
17
21
  end
@@ -0,0 +1,14 @@
1
+ module Danger
2
+ class RubyGemsClient
3
+ API_URL = "https://rubygems.org/api/v1/versions/danger/latest.json".freeze
4
+ DUMMY_VERSION = "0.0.0".freeze
5
+
6
+ def self.latest_danger_version
7
+ require "json"
8
+ json = JSON.parse(Faraday.get(API_URL).body)
9
+ json.fetch("version") { DUMMY_VERSION }
10
+ rescue StandardError => _e
11
+ DUMMY_VERSION
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ <%- @tables.each do |table| -%>
2
+ <%- if table[:content].any? || table[:resolved].any? -%>
3
+ | | <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %> |
4
+ |---|---|
5
+ <%- table[:content].each do |violation| -%>
6
+ | <%= character_from_emoji(table[:emoji]) %> | <%= violation.message %> |
7
+ <%- end -%>
8
+ <%- table[:resolved].each do |message| -%>
9
+ | :white_check_mark: | <%= message %> |
10
+ <%- end -%>
11
+
12
+ <%- end -%>
13
+ <%- end -%>
14
+
15
+ <%- @markdowns.each do |current| -%>
16
+ <%= current %>
17
+ <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
18
+ <%- end -%>
19
+
20
+ Generated by 🚫 [danger](http://example.com/ "generated_by_<%= @danger_id %>")
@@ -0,0 +1,26 @@
1
+ <%- @tables.each do |table| -%>
2
+ <%- if table[:content].any? -%>
3
+ <table data-meta="generated_by_<%= @danger_id %>">
4
+ <tbody>
5
+ <%- table[:content].each do |violation| -%>
6
+ <tr>
7
+ <td>:<%= table[:emoji] %>:</td>
8
+ <td width="100%" data-sticky="<%= violation.sticky %>"><%= "<del>" if table[:resolved] %><%= violation.message %><%= "</del>" if table[:resolved] %></td>
9
+ </tr>
10
+ <%- end -%>
11
+ </tbody>
12
+ </table>
13
+ <%- end -%>
14
+ <%- end -%>
15
+
16
+ <%- @markdowns.each do |current| -%>
17
+ <%= current %>
18
+ <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
19
+ <%- end -%>
20
+ <%# We need to add the generated_by_ to identify comments from danger. But with inlines %>
21
+ <%# it might be a little annoying, so we set on the table, but if we have markdown we add the footer anyway %>
22
+ <%- if @markdowns.count > 0 -%>
23
+ <p align="right" data-meta="generated_by_<%= @danger_id %>">
24
+ Generated by :no_entry_sign: <a href="http://danger.systems/">danger</a>
25
+ </p>
26
+ <%- end -%>
@@ -8,6 +8,7 @@ require "danger/danger_core/plugins/dangerfile_danger_plugin"
8
8
  require "danger/danger_core/plugins/dangerfile_git_plugin"
9
9
  require "danger/danger_core/plugins/dangerfile_github_plugin"
10
10
  require "danger/danger_core/plugins/dangerfile_gitlab_plugin"
11
+ require "danger/danger_core/plugins/dangerfile_bitbucket_server_plugin"
11
12
 
12
13
  module Danger
13
14
  class Dangerfile
@@ -35,7 +36,7 @@ module Danger
35
36
 
36
37
  # The ones that everything would break without
37
38
  def self.essential_plugin_classes
38
- [DangerfileMessagingPlugin, DangerfileGitPlugin, DangerfileDangerPlugin, DangerfileGitHubPlugin, DangerfileGitLabPlugin]
39
+ [DangerfileMessagingPlugin, DangerfileGitPlugin, DangerfileDangerPlugin, DangerfileGitHubPlugin, DangerfileGitLabPlugin, DangerfileBitbucketServerPlugin]
39
40
  end
40
41
 
41
42
  # Both of these methods exist on all objects
@@ -203,7 +204,8 @@ module Danger
203
204
 
204
205
  def print_results
205
206
  status = status_report
206
- return if (status[:errors] + status[:warnings] + status[:messages] + status[:markdowns]).count.zero?
207
+ violations = violation_report
208
+ return if (violations[:errors] + violations[:warnings] + violations[:messages] + status[:markdowns]).count.zero?
207
209
 
208
210
  ui.section("Results:") do
209
211
  [:errors, :warnings, :messages].each do |key|
@@ -216,14 +218,15 @@ module Danger
216
218
  else
217
219
  formatted
218
220
  end
219
- rows = status[key]
221
+ rows = violations[key]
220
222
  print_list(title, rows)
221
223
  end
222
224
 
223
225
  if status[:markdowns].count > 0
224
- ui.section("Markdown:") do
226
+ ui.title("Markdown:") do
225
227
  status[:markdowns].each do |current_markdown|
226
- ui.puts current_markdown
228
+ ui.puts "#{current_markdown.file}\#L#{current_markdown.line}" if current_markdown.file && current_markdown.line
229
+ ui.puts current_markdown.message
227
230
  end
228
231
  end
229
232
  end
@@ -235,7 +238,13 @@ module Danger
235
238
  def print_list(title, rows)
236
239
  ui.title(title) do
237
240
  rows.each do |row|
238
- ui.puts("- [ ] #{row}")
241
+ if row.file && row.line
242
+ path = "#{row.file}\#L#{row.line}: "
243
+ else
244
+ path = ""
245
+ end
246
+
247
+ ui.puts("- [ ] #{path}#{row.message}")
239
248
  end
240
249
  end unless rows.empty?
241
250
  end
@@ -0,0 +1,28 @@
1
+ module Danger
2
+ class Markdown
3
+ attr_accessor :message, :file, :line
4
+
5
+ def initialize(message, file, line)
6
+ self.message = message
7
+ self.file = file
8
+ self.line = line
9
+ end
10
+
11
+ def ==(other)
12
+ return false if other.nil?
13
+ return false unless other.kind_of? self.class
14
+
15
+ other.message == message &&
16
+ other.file == file &&
17
+ other.line == line
18
+ end
19
+
20
+ def to_s
21
+ extra = []
22
+ extra << "file: #{file}" unless file.nil?
23
+ extra << "line: #{line}" unless line.nil?
24
+
25
+ "Markdown #{message} { #{extra.join ', '} }"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ module Danger
2
+ class Violation
3
+ attr_accessor :message, :sticky, :file, :line
4
+
5
+ def initialize(message, sticky, file, line)
6
+ self.message = message
7
+ self.sticky = sticky
8
+ self.file = file
9
+ self.line = line
10
+ end
11
+
12
+ def ==(other)
13
+ return false if other.nil?
14
+ return false unless other.kind_of? self.class
15
+
16
+ other.message == message &&
17
+ other.sticky == sticky &&
18
+ other.file == file &&
19
+ other.line == line
20
+ end
21
+
22
+ def inline?
23
+ return (file.nil? && line.nil?) == false
24
+ end
25
+
26
+ def to_s
27
+ extra = []
28
+ extra << "sticky: true" if sticky
29
+ extra << "file: #{file}" unless file.nil?
30
+ extra << "line: #{line}" unless line.nil?
31
+
32
+ "Violation #{message} { #{extra.join ', '} }"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,182 @@
1
+ # coding: utf-8
2
+ require "danger/plugin_support/plugin"
3
+
4
+ module Danger
5
+ # Handles interacting with Bitbucket Server inside a Dangerfile. Provides a few functions which wrap `pr_json` and also
6
+ # through a few standard functions to simplify your code.
7
+ #
8
+ # @example Warn when a PR is classed as work in progress
9
+ #
10
+ # warn "PR is classed as Work in Progress" if bitbucket_server.pr_title.include? "[WIP]"
11
+ #
12
+ # @example Declare a PR to be simple to avoid specific Danger rules
13
+ #
14
+ # declared_trivial = (bitbucket_server.pr_title + bitbucket_server.pr_body).include?("#trivial")
15
+ #
16
+ # @example Ensure that labels have been used on the PR
17
+ #
18
+ # fail "Please add labels to this PR" if bitbucket_server.pr_labels.empty?
19
+ #
20
+ # @example Check if a user is in a specific bitbucket_server org, and message them if so
21
+ #
22
+ # unless bitbucket_server.api.organization_member?('danger', bitbucket_server.pr_author)
23
+ # message "@#{bitbucket_server.pr_author} is not a contributor yet, would you like to join the Danger org?"
24
+ # end
25
+ #
26
+ # @example Ensure there is a summary for a PR
27
+ #
28
+ # fail "Please provide a summary in the Pull Request description" if bitbucket_server.pr_body.length < 5
29
+ #
30
+ # @example Only accept PRs to the develop branch
31
+ #
32
+ # fail "Please re-submit this PR to develop, we may have already fixed your issue." if bitbucket_server.branch_for_base != "develop"
33
+ #
34
+ # @example Highlight when a celebrity makes a pull request
35
+ #
36
+ # message "Welcome, Danger." if bitbucket_server.pr_author == "dangermcshane"
37
+ #
38
+ # @example Ensure that all PRs have an assignee
39
+ #
40
+ # warn "This PR does not have any assignees yet." unless bitbucket_server.pr_json["reviewers"].length == 0
41
+ #
42
+ # @example Send a message with links to a collection of specific files
43
+ #
44
+ # if git.modified_files.include? "config/*.js"
45
+ # config_files = git.modified_files.select { |path| path.include? "config/" }
46
+ # message "This PR changes #{ bitbucket_server.html_link(config_files) }"
47
+ # end
48
+ #
49
+ # @example Highlight with a clickable link if a Package.json is changed
50
+ #
51
+ # warn "#{bitbucket_server.html_link("Package.json")} was edited." if git.modified_files.include? "Package.json"
52
+ #
53
+ # @see danger/danger
54
+ # @tags core, bitbucket_server
55
+ #
56
+ class DangerfileBitbucketServerPlugin < Plugin
57
+ # So that this init can fail.
58
+ def self.new(dangerfile)
59
+ return nil if dangerfile.env.request_source.class != Danger::RequestSources::BitbucketServer
60
+ super
61
+ end
62
+
63
+ # The instance name used in the Dangerfile
64
+ # @return [String]
65
+ #
66
+ def self.instance_name
67
+ "bitbucket_server"
68
+ end
69
+
70
+ def initialize(dangerfile)
71
+ super(dangerfile)
72
+ @bs = dangerfile.env.request_source
73
+ end
74
+
75
+ # @!group Bitbucket Server Misc
76
+ # The hash that represents the PR's JSON. For an example of what this looks like
77
+ # see the [Danger Fixture'd one](https://raw.githubusercontent.com/danger/danger/master/spec/fixtures/bitbucket_server_api/pr_response.json).
78
+ # @return [Hash]
79
+ def pr_json
80
+ @bs.pr_json
81
+ end
82
+
83
+ # @!group PR Metadata
84
+ # The title of the Pull Request.
85
+ # @return [String]
86
+ #
87
+ def pr_title
88
+ @bs.pr_json[:title].to_s
89
+ end
90
+
91
+ # @!group PR Metadata
92
+ # The body text of the Pull Request.
93
+ # @return [String]
94
+ #
95
+ def pr_description
96
+ @bs.pr_json[:description].to_s
97
+ end
98
+ alias pr_body pr_description
99
+
100
+ # @!group PR Metadata
101
+ # The username of the author of the Pull Request.
102
+ # @return [String]
103
+ #
104
+ def pr_author
105
+ @bs.pr_json[:author][:user][:slug].to_s
106
+ end
107
+
108
+ # @!group PR Commit Metadata
109
+ # The branch to which the PR is going to be merged into.
110
+ # @return [String]
111
+ #
112
+ def branch_for_base
113
+ @bs.pr_json[:toRef][:displayId].to_s
114
+ end
115
+
116
+ # @!group PR Commit Metadata
117
+ # A href that represents the current PR
118
+ # @return [String]
119
+ #
120
+ def pr_link
121
+ @bs.pr_json[:links][:self].flat_map { |l| l[:href] }.first.to_s
122
+ end
123
+
124
+ # @!group PR Commit Metadata
125
+ # The branch to which the PR is going to be merged from.
126
+ # @return [String]
127
+ #
128
+ def branch_for_head
129
+ @bs.pr_json[:fromRef][:displayId].to_s
130
+ end
131
+
132
+ # @!group PR Commit Metadata
133
+ # The base commit to which the PR is going to be merged as a parent.
134
+ # @return [String]
135
+ #
136
+ def base_commit
137
+ @bs.pr_json[:toRef][:latestCommit].to_s
138
+ end
139
+
140
+ # @!group PR Commit Metadata
141
+ # The head commit to which the PR is requesting to be merged from.
142
+ # @return [String]
143
+ #
144
+ def head_commit
145
+ @bs.pr_json[:fromRef][:latestCommit].to_s
146
+ end
147
+
148
+ # @!group Bitbucket Server Misc
149
+ # Returns a list of HTML anchors for a file, or files in the head repository.
150
+ # It returns a string of multiple anchors if passed an array.
151
+ # @param [String or Array<String>] paths
152
+ # A list of strings to convert to github anchors
153
+ # @param [Bool] full_path
154
+ # Shows the full path as the link's text, defaults to `true`.
155
+ #
156
+ # @return [String]
157
+ #
158
+ def html_link(paths, full_path: true)
159
+ paths = [paths] unless paths.kind_of?(Array)
160
+ commit = head_commit
161
+ repo = pr_json[:fromRef][:repository][:links][:self].flat_map { |l| l[:href] }.first
162
+
163
+ paths = paths.map do |path|
164
+ path, line = path.split("#")
165
+ url_path = path.start_with?("/") ? path : "/#{path}"
166
+ text = full_path ? path : File.basename(path)
167
+ url_path.gsub!(" ", "%20")
168
+ line_ref = line ? "##{line}" : ""
169
+ create_link("#{repo}/browse/#{url_path}?at=#{commit}#{line_ref}", text)
170
+ end
171
+
172
+ return paths.first if paths.count < 2
173
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
174
+ end
175
+
176
+ private
177
+
178
+ def create_link(href, text)
179
+ "<a href='#{href}'>#{text}</a>"
180
+ end
181
+ end
182
+ end
@@ -40,7 +40,7 @@ module Danger
40
40
  # @return [void]
41
41
  #
42
42
  def import_plugin(path_or_url)
43
- raise "`import` requires a string" unless path_or_url.kind_of?(String)
43
+ raise "`import_plugin` requires a string" unless path_or_url.kind_of?(String)
44
44
 
45
45
  if path_or_url.start_with?("http")
46
46
  import_url(path_or_url)
@@ -49,23 +49,78 @@ module Danger
49
49
  end
50
50
  end
51
51
 
52
+ # @!group Danger
53
+ # Import a Dangerfile.
54
+ #
55
+ # @param [Hash] opts
56
+ # @option opts [String] :github Github path
57
+ # @option opts [String] :gem Gem name
58
+ # @option opts [String] :path Path to Dangerfile
59
+ # @return [void]
60
+ def import_dangerfile(opts)
61
+ if opts.kind_of?(String)
62
+ warn "Use `import_dangerfile(github: '#{opts}')` instead of `import_dangerfile '#{opts}'`."
63
+ import_dangerfile_from_github(opts)
64
+ elsif opts.kind_of?(Hash)
65
+ if opts.key?(:github)
66
+ import_dangerfile_from_github(opts[:github])
67
+ elsif opts.key?(:path)
68
+ import_dangerfile_from_path(opts[:path])
69
+ elsif opts.key?(:gem)
70
+ import_dangerfile_from_gem(opts[:gem])
71
+ else
72
+ raise "`import` requires a Hash with either :github or :gem"
73
+ end
74
+ else
75
+ raise "`import` requires a Hash" unless opts.kind_of?(Hash)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # @!group Danger
82
+ # Read and execute a local Dangerfile.
83
+ #
84
+ # @param [String] path
85
+ # A path to a Dangerfile.
86
+ # @return [void]
87
+ #
88
+ def import_dangerfile_from_path(path)
89
+ raise "`import_dangerfile_from_path` requires a string" unless path.kind_of?(String)
90
+ local_path = File.join(path, "Dangerfile")
91
+ @dangerfile.parse(Pathname.new(local_path))
92
+ end
93
+
94
+ # @!group Danger
95
+ # Read and execute a Dangerfile from a gem.
96
+ #
97
+ # @param [String] name
98
+ # The name of the gem that contains a Dangerfile.
99
+ # @return [void]
100
+ #
101
+ def import_dangerfile_from_gem(name)
102
+ raise "`import_dangerfile_from_gem` requires a string" unless name.kind_of?(String)
103
+ spec = Gem::Specification.find_by_name(name)
104
+ import_dangerfile_from_path(spec.gem_dir)
105
+ rescue Gem::MissingSpecError
106
+ raise "`import_dangerfile_from_gem` tried to load `#{name}` and failed, did you forget to include it in your Gemfile?"
107
+ end
108
+
52
109
  # @!group Danger
53
110
  # Download and execute a remote Dangerfile.
54
111
  #
55
- # @param [String] repo slug
112
+ # @param [String] slug
56
113
  # A slug that represents the repo where the Dangerfile is.
57
114
  # @return [void]
58
115
  #
59
- def import_dangerfile(slug)
60
- raise "`import` requires a string" unless slug.kind_of?(String)
116
+ def import_dangerfile_from_github(slug)
117
+ raise "`import_dangerfile_from_github` requires a string" unless slug.kind_of?(String)
61
118
  org, repo = slug.split("/")
62
119
  download_url = env.request_source.file_url(organisation: org, repository: repo, branch: "master", path: "Dangerfile")
63
120
  local_path = download(download_url)
64
121
  @dangerfile.parse(Pathname.new(local_path))
65
122
  end
66
123
 
67
- private
68
-
69
124
  # @!group Plugins
70
125
  # Download a local or remote plugin or Dangerfile.
71
126
  # This method will not import the file for you, use plugin.import instead