danger-packwerk 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/danger-packwerk/check/default_formatter.rb +110 -0
- data/lib/danger-packwerk/check/offenses_formatter.rb +23 -0
- data/lib/danger-packwerk/danger_deprecated_references_yml_changes.rb +7 -5
- data/lib/danger-packwerk/danger_packwerk.rb +7 -5
- data/lib/danger-packwerk/private/ownership_information.rb +60 -0
- data/lib/danger-packwerk/private.rb +1 -1
- data/lib/danger-packwerk/update/default_formatter.rb +67 -0
- data/lib/danger-packwerk/update/offenses_formatter.rb +23 -0
- data/lib/danger-packwerk/version.rb +1 -1
- data/lib/danger-packwerk.rb +4 -0
- data/sorbet/rbi/gems/code_ownership@1.29.1.rbi +336 -0
- data/sorbet/rbi/gems/code_teams@1.0.0.rbi +120 -0
- metadata +23 -4
- data/lib/danger-packwerk/private/default_offenses_formatter.rb +0 -37
- data/sorbet/rbi/gems/json@2.6.2.rbi +0 -1418
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ad3f46c259911564eb4b955fb62e8152962db5c5682a6ba6000054dde9d8128
|
4
|
+
data.tar.gz: ef61b403058b20869cda462629724943317e55f5f4b1bfe125f9be7323a634c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfc452185f83aef5f9ff5e53c475ac7472d7cfcda477a0a1f1fcc863704df6e93358a6e6096f5d4f1afb68176c687c8748a8c2c2562458c03bac47d120de91b4
|
7
|
+
data.tar.gz: '08df3f5e1d6e6d7367c3dc85f464346cf479dd13b5d831b26847b194f90190cb33e429c92095517124ae78eb0d23479c2a3b1146ab5a1ef529bfe967b9cf9192'
|
data/README.md
CHANGED
@@ -83,11 +83,11 @@ Without any configuration, `deprecated_references_yml_changes.check` should just
|
|
83
83
|
`deprecated_references_yml_changes.check` can be configured to in the following ways:
|
84
84
|
|
85
85
|
### Change the message that displays in the markdown
|
86
|
-
The default message displayed is from `lib/danger-packwerk/private/default_offenses_formatter.rb`. To customize this this, pass in `
|
86
|
+
The default message displayed is from `lib/danger-packwerk/private/default_offenses_formatter.rb`. To customize this this, pass in `offenses_formatter` to `deprecated_references_yml_changes.check` in your `Dangerfile`. Here's a simple example:
|
87
87
|
```ruby
|
88
88
|
deprecated_references_yml_changes.check(
|
89
89
|
# Offenses are a T::Array[DangerPackwerk::BasicReferenceOffense]
|
90
|
-
|
90
|
+
offenses_formatter: -> (added_offenses) do
|
91
91
|
"There are #{added_offenses.count} new violations this line!"
|
92
92
|
end
|
93
93
|
)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'code_ownership'
|
4
|
+
|
5
|
+
module DangerPackwerk
|
6
|
+
module Check
|
7
|
+
class DefaultFormatter
|
8
|
+
include OffensesFormatter
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig do
|
12
|
+
params(
|
13
|
+
custom_help_message: T.nilable(String)
|
14
|
+
).void
|
15
|
+
end
|
16
|
+
def initialize(custom_help_message: nil)
|
17
|
+
@custom_help_message = custom_help_message
|
18
|
+
end
|
19
|
+
|
20
|
+
sig do
|
21
|
+
override.params(
|
22
|
+
offenses: T::Array[Packwerk::ReferenceOffense],
|
23
|
+
repo_link: String,
|
24
|
+
org_name: String
|
25
|
+
).returns(String)
|
26
|
+
end
|
27
|
+
def format_offenses(offenses, repo_link, org_name)
|
28
|
+
reference_offense = T.must(offenses.first)
|
29
|
+
violation_types = offenses.map(&:violation_type)
|
30
|
+
referencing_file = reference_offense.reference.relative_path
|
31
|
+
referencing_file_pack = ParsePackwerk.package_from_path(referencing_file).name
|
32
|
+
# We remove leading double colons as they feel like an implementation detail of packwerk.
|
33
|
+
constant_name = reference_offense.reference.constant.name.delete_prefix('::')
|
34
|
+
|
35
|
+
constant_source_package_name = reference_offense.reference.constant.package.name
|
36
|
+
|
37
|
+
constant_location = reference_offense.reference.constant.location
|
38
|
+
constant_source_package = T.must(ParsePackwerk.all.find { |p| p.name == constant_source_package_name })
|
39
|
+
constant_source_package_ownership_info = Private::OwnershipInformation.for_package(constant_source_package, org_name)
|
40
|
+
|
41
|
+
disclaimer = 'Before you run `bin/packwerk update-deprecations`, check out these quick suggestions:'
|
42
|
+
referencing_code_in_right_pack = "- Does the code you are writing live in the right pack?\n - If not, try `bin/packs move packs/destination_pack #{referencing_file}`"
|
43
|
+
referenced_code_in_right_pack = "- Does #{constant_name} live in the right pack?\n - If not, try `bin/packs move packs/destination_pack #{constant_location}`"
|
44
|
+
dependency_violation_message = "- Do we actually want to depend on #{constant_source_package_name}?\n - If so, try `bin/packs add_dependency #{referencing_file_pack} #{constant_source_package_name}`\n - If not, what can we change about the design so we do not have to depend on #{constant_source_package_name}?"
|
45
|
+
team_to_work_with = constant_source_package_ownership_info.owning_team ? constant_source_package_ownership_info.markdown_link_to_github_members_no_tag : 'the pack owner'
|
46
|
+
privacy_violation_message = "- Does API in #{constant_source_package.name}/public support this use case?\n - If not, can we work with #{team_to_work_with} to create and use a public API?\n - If `#{constant_name}` should already be public, try `bin/packs make_public #{constant_location}`."
|
47
|
+
|
48
|
+
if violation_types.include?(Packwerk::ViolationType::Dependency) && violation_types.include?(Packwerk::ViolationType::Privacy)
|
49
|
+
<<~MESSAGE
|
50
|
+
**Packwerk Violation**
|
51
|
+
- Type: Privacy :lock: + Dependency :knot:
|
52
|
+
- Constant: [<ins>`#{constant_name}`</ins>](#{repo_link}/blob/main/#{constant_location})
|
53
|
+
- Owning pack: #{constant_source_package_name}
|
54
|
+
#{constant_source_package_ownership_info.ownership_copy}
|
55
|
+
|
56
|
+
<details><summary>Quick suggestions :bulb:</summary>
|
57
|
+
|
58
|
+
#{disclaimer}
|
59
|
+
#{referencing_code_in_right_pack}
|
60
|
+
#{referenced_code_in_right_pack}
|
61
|
+
#{dependency_violation_message}
|
62
|
+
#{privacy_violation_message}
|
63
|
+
|
64
|
+
</details>
|
65
|
+
|
66
|
+
_#{@custom_help_message}_
|
67
|
+
MESSAGE
|
68
|
+
elsif violation_types.include?(Packwerk::ViolationType::Dependency)
|
69
|
+
<<~MESSAGE
|
70
|
+
**Packwerk Violation**
|
71
|
+
- Type: Dependency :knot:
|
72
|
+
- Constant: [<ins>`#{constant_name}`</ins>](#{repo_link}/blob/main/#{constant_location})
|
73
|
+
- Owning pack: #{constant_source_package_name}
|
74
|
+
#{constant_source_package_ownership_info.ownership_copy}
|
75
|
+
|
76
|
+
<details><summary>Quick suggestions :bulb:</summary>
|
77
|
+
|
78
|
+
#{disclaimer}
|
79
|
+
#{referencing_code_in_right_pack}
|
80
|
+
#{referenced_code_in_right_pack}
|
81
|
+
#{dependency_violation_message}
|
82
|
+
|
83
|
+
</details>
|
84
|
+
|
85
|
+
_#{@custom_help_message}_
|
86
|
+
MESSAGE
|
87
|
+
else # violation_types.include?(Packwerk::ViolationType::Privacy)
|
88
|
+
<<~MESSAGE
|
89
|
+
**Packwerk Violation**
|
90
|
+
- Type: Privacy :lock:
|
91
|
+
- Constant: [<ins>`#{constant_name}`</ins>](#{repo_link}/blob/main/#{constant_location})
|
92
|
+
- Owning pack: #{constant_source_package_name}
|
93
|
+
#{constant_source_package_ownership_info.ownership_copy}
|
94
|
+
|
95
|
+
<details><summary>Quick suggestions :bulb:</summary>
|
96
|
+
|
97
|
+
#{disclaimer}
|
98
|
+
#{referencing_code_in_right_pack}
|
99
|
+
#{referenced_code_in_right_pack}
|
100
|
+
#{privacy_violation_message}
|
101
|
+
|
102
|
+
</details>
|
103
|
+
|
104
|
+
_#{@custom_help_message}_
|
105
|
+
MESSAGE
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'code_ownership'
|
4
|
+
|
5
|
+
module DangerPackwerk
|
6
|
+
module Check
|
7
|
+
module OffensesFormatter
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
interface!
|
12
|
+
|
13
|
+
sig do
|
14
|
+
abstract.params(
|
15
|
+
offenses: T::Array[Packwerk::ReferenceOffense],
|
16
|
+
repo_link: String,
|
17
|
+
org_name: String
|
18
|
+
).returns(String)
|
19
|
+
end
|
20
|
+
def format_offenses(offenses, repo_link, org_name); end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -17,23 +17,25 @@ module DangerPackwerk
|
|
17
17
|
# Therefore we hope to capture the majority case of people making changes to code while not spamming PRs that do a big rename.
|
18
18
|
# We set a max (rather than unlimited) to avoid GitHub rate limiting and general spam if a PR does some sort of mass rename.
|
19
19
|
DEFAULT_MAX_COMMENTS = 5
|
20
|
-
AddedOffensesFormatter = T.type_alias { T.proc.params(added_violations: T::Array[BasicReferenceOffense]).returns(String) }
|
21
|
-
DEFAULT_ADDED_OFFENSES_FORMATTER = T.let(->(added_violations) { Private::DefaultAddedOffensesFormatter.format(added_violations) }, AddedOffensesFormatter)
|
22
20
|
BeforeComment = T.type_alias { T.proc.params(violation_diff: ViolationDiff, changed_deprecated_references_ymls: T::Array[String]).void }
|
23
21
|
DEFAULT_BEFORE_COMMENT = T.let(->(violation_diff, changed_deprecated_references_ymls) {}, BeforeComment)
|
24
22
|
|
25
23
|
sig do
|
26
24
|
params(
|
27
|
-
|
25
|
+
offenses_formatter: T.nilable(Update::OffensesFormatter),
|
28
26
|
before_comment: BeforeComment,
|
29
27
|
max_comments: Integer
|
30
28
|
).void
|
31
29
|
end
|
32
30
|
def check(
|
33
|
-
|
31
|
+
offenses_formatter: nil,
|
34
32
|
before_comment: DEFAULT_BEFORE_COMMENT,
|
35
33
|
max_comments: DEFAULT_MAX_COMMENTS
|
36
34
|
)
|
35
|
+
offenses_formatter ||= Update::DefaultFormatter.new
|
36
|
+
repo_link = github.pr_json[:base][:repo][:html_url]
|
37
|
+
org_name = github.pr_json[:base][:repo][:owner][:login]
|
38
|
+
|
37
39
|
changed_deprecated_references_ymls = (git.modified_files + git.added_files + git.deleted_files).grep(DEPRECATED_REFERENCES_PATTERN)
|
38
40
|
|
39
41
|
violation_diff = get_violation_diff
|
@@ -51,7 +53,7 @@ module DangerPackwerk
|
|
51
53
|
location = T.must(violations.first).file_location
|
52
54
|
|
53
55
|
markdown(
|
54
|
-
|
56
|
+
offenses_formatter.format_offenses(violations, repo_link, org_name),
|
55
57
|
line: location.line_number,
|
56
58
|
file: location.file
|
57
59
|
)
|
@@ -19,8 +19,6 @@ module DangerPackwerk
|
|
19
19
|
DEFAULT_MAX_COMMENTS = 15
|
20
20
|
OnFailure = T.type_alias { T.proc.params(offenses: T::Array[Packwerk::ReferenceOffense]).void }
|
21
21
|
DEFAULT_ON_FAILURE = T.let(->(offenses) {}, OnFailure)
|
22
|
-
OffensesFormatter = T.type_alias { T.proc.params(offenses: T::Array[Packwerk::ReferenceOffense]).returns(String) }
|
23
|
-
DEFAULT_OFFENSES_FORMATTER = T.let(->(offenses) { offenses.map(&:message).join("\n\n") }, OffensesFormatter)
|
24
22
|
DEFAULT_FAIL = false
|
25
23
|
DEFAULT_FAILURE_MESSAGE = 'Packwerk violations were detected! Please resolve them to unblock the build.'
|
26
24
|
|
@@ -35,8 +33,8 @@ module DangerPackwerk
|
|
35
33
|
|
36
34
|
sig do
|
37
35
|
params(
|
36
|
+
offenses_formatter: T.nilable(Check::OffensesFormatter),
|
38
37
|
max_comments: Integer,
|
39
|
-
offenses_formatter: OffensesFormatter,
|
40
38
|
fail_build: T::Boolean,
|
41
39
|
failure_message: String,
|
42
40
|
on_failure: OnFailure,
|
@@ -44,13 +42,17 @@ module DangerPackwerk
|
|
44
42
|
).void
|
45
43
|
end
|
46
44
|
def check(
|
45
|
+
offenses_formatter: nil,
|
47
46
|
max_comments: DEFAULT_MAX_COMMENTS,
|
48
|
-
offenses_formatter: DEFAULT_OFFENSES_FORMATTER,
|
49
47
|
fail_build: DEFAULT_FAIL,
|
50
48
|
failure_message: DEFAULT_FAILURE_MESSAGE,
|
51
49
|
on_failure: DEFAULT_ON_FAILURE,
|
52
50
|
grouping_strategy: CommentGroupingStrategy::PerConstantPerLocation
|
53
51
|
)
|
52
|
+
offenses_formatter ||= Check::DefaultFormatter.new
|
53
|
+
repo_link = github.pr_json[:base][:repo][:html_url]
|
54
|
+
org_name = github.pr_json[:base][:repo][:owner][:login]
|
55
|
+
|
54
56
|
# This is important because by default, Danger will leave a concantenated list of all its messages if it can't find a commentable place in the
|
55
57
|
# diff to leave its message. This is an especially bad UX because it will be a huge wall of text not connected to the source of the issue.
|
56
58
|
# Furthermore, dismissing these ensures that something like moving a file from pack to pack does not trigger the danger message. That is,
|
@@ -120,7 +122,7 @@ module DangerPackwerk
|
|
120
122
|
line_number = reference_offense.location&.line
|
121
123
|
referencing_file = reference_offense.reference.relative_path
|
122
124
|
|
123
|
-
message = offenses_formatter.
|
125
|
+
message = offenses_formatter.format_offenses(unique_packwerk_reference_offenses, repo_link, org_name)
|
124
126
|
|
125
127
|
markdown(message, file: referencing_file, line: line_number)
|
126
128
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'code_ownership'
|
4
|
+
|
5
|
+
module DangerPackwerk
|
6
|
+
module Private
|
7
|
+
class OwnershipInformation < T::Struct
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
const :owning_team, T.nilable(CodeTeams::Team)
|
11
|
+
const :github_team, T.nilable(String)
|
12
|
+
const :slack_channel, T.nilable(String)
|
13
|
+
const :org_name, T.nilable(String)
|
14
|
+
|
15
|
+
sig { params(package: ParsePackwerk::Package, org_name: String).returns(OwnershipInformation) }
|
16
|
+
def self.for_package(package, org_name)
|
17
|
+
team = CodeOwnership.for_package(package)
|
18
|
+
|
19
|
+
if team.nil?
|
20
|
+
OwnershipInformation.new
|
21
|
+
else
|
22
|
+
OwnershipInformation.new(
|
23
|
+
owning_team: team,
|
24
|
+
github_team: team.raw_hash.fetch('github', {}).fetch('team', nil),
|
25
|
+
slack_channel: team.raw_hash.fetch('slack', {}).fetch('room_for_humans', nil),
|
26
|
+
org_name: org_name
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
def ownership_copy
|
33
|
+
github_team_flow_sensitive = github_team
|
34
|
+
slack_channel_flow_sensitive = slack_channel
|
35
|
+
|
36
|
+
if owning_team && github_team_flow_sensitive && slack_channel_flow_sensitive
|
37
|
+
team_slack_link = markdown_link_to_slack_room
|
38
|
+
"- Owned by #{markdown_link_to_github_members_no_tag} (Slack: #{team_slack_link})"
|
39
|
+
else
|
40
|
+
'- This pack is unowned.'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { returns(String) }
|
45
|
+
def markdown_link_to_slack_room
|
46
|
+
"[<ins>#{slack_channel}</ins>](https://slack.com/app_redirect?channel=#{T.must(slack_channel).delete('#')})"
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Note this will NOT tag the team on Github, but it will link
|
51
|
+
# to the mentioned team's members page. If you want to tag and
|
52
|
+
# link the team, simply use the string and Github will handle it.
|
53
|
+
#
|
54
|
+
sig { returns(String) }
|
55
|
+
def markdown_link_to_github_members_no_tag
|
56
|
+
"[<ins>#{github_team}</ins>](https://github.com/orgs/#{org_name}/teams/#{T.must(github_team).gsub("@#{org_name}/", '')}/members)"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DangerPackwerk
|
4
|
+
module Update
|
5
|
+
class DefaultFormatter
|
6
|
+
extend T::Sig
|
7
|
+
include OffensesFormatter
|
8
|
+
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
custom_help_message: T.nilable(String)
|
12
|
+
).void
|
13
|
+
end
|
14
|
+
def initialize(custom_help_message: nil)
|
15
|
+
@custom_help_message = custom_help_message
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.params(offenses: T::Array[BasicReferenceOffense], repo_link: String, org_name: String).returns(String) }
|
19
|
+
def format_offenses(offenses, repo_link, org_name)
|
20
|
+
violation = T.must(offenses.first)
|
21
|
+
referencing_file_pack = ParsePackwerk.package_from_path(violation.file)
|
22
|
+
# We remove leading double colons as they feel like an implementation detail of packwerk.
|
23
|
+
constant_name = violation.class_name.delete_prefix('::')
|
24
|
+
constant_source_package_name = violation.to_package_name
|
25
|
+
|
26
|
+
constant_source_package = T.must(ParsePackwerk.find(constant_source_package_name))
|
27
|
+
constant_source_package_owner = Private::OwnershipInformation.for_package(constant_source_package, org_name)
|
28
|
+
|
29
|
+
package_referring_to_constant_owner = Private::OwnershipInformation.for_package(referencing_file_pack, org_name)
|
30
|
+
|
31
|
+
disclaimer = 'We noticed you ran `bin/packwerk update-deprecations`. Check out [the docs](https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md) to see other ways to resolve violations.'
|
32
|
+
pluralized_violation = offenses.count > 1 ? 'these violations' : 'this violation'
|
33
|
+
request_to_add_context = "- Could you add some context as a reply here about why we needed to add #{pluralized_violation}?\n"
|
34
|
+
|
35
|
+
dependency_violation_message = "- cc #{package_referring_to_constant_owner.github_team} (#{package_referring_to_constant_owner.markdown_link_to_slack_room}) for the dependency violation.\n" if package_referring_to_constant_owner.owning_team
|
36
|
+
|
37
|
+
privacy_violation_message = "- cc #{constant_source_package_owner.github_team} (#{constant_source_package_owner.markdown_link_to_slack_room}) for the privacy violation.\n" if constant_source_package_owner.owning_team
|
38
|
+
|
39
|
+
if offenses.any?(&:dependency?) && offenses.any?(&:privacy?)
|
40
|
+
<<~MESSAGE.chomp
|
41
|
+
Hi again! It looks like `#{constant_name}` is private API of `#{constant_source_package_name}`, which is also not in `#{referencing_file_pack.name}`'s list of dependencies.
|
42
|
+
#{disclaimer}
|
43
|
+
|
44
|
+
#{request_to_add_context}#{dependency_violation_message}#{privacy_violation_message}
|
45
|
+
#{@custom_help_message}
|
46
|
+
MESSAGE
|
47
|
+
elsif offenses.any?(&:dependency?)
|
48
|
+
<<~MESSAGE.chomp
|
49
|
+
Hi again! It looks like `#{constant_name}` belongs to `#{constant_source_package_name}`, which is not in `#{referencing_file_pack.name}`'s list of dependencies.
|
50
|
+
#{disclaimer}
|
51
|
+
|
52
|
+
#{request_to_add_context}#{dependency_violation_message}
|
53
|
+
#{@custom_help_message}
|
54
|
+
MESSAGE
|
55
|
+
else # violations.any?(&:privacy?)
|
56
|
+
<<~MESSAGE.chomp
|
57
|
+
Hi again! It looks like `#{constant_name}` is private API of `#{constant_source_package_name}`.
|
58
|
+
#{disclaimer}
|
59
|
+
|
60
|
+
#{request_to_add_context}#{privacy_violation_message}
|
61
|
+
#{@custom_help_message}
|
62
|
+
MESSAGE
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'code_ownership'
|
4
|
+
|
5
|
+
module DangerPackwerk
|
6
|
+
module Update
|
7
|
+
module OffensesFormatter
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
interface!
|
12
|
+
|
13
|
+
sig do
|
14
|
+
abstract.params(
|
15
|
+
offenses: T::Array[BasicReferenceOffense],
|
16
|
+
repo_link: String,
|
17
|
+
org_name: String
|
18
|
+
).returns(String)
|
19
|
+
end
|
20
|
+
def format_offenses(offenses, repo_link, org_name); end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/danger-packwerk.rb
CHANGED
@@ -9,4 +9,8 @@ module DangerPackwerk
|
|
9
9
|
|
10
10
|
require 'danger-packwerk/danger_packwerk'
|
11
11
|
require 'danger-packwerk/danger_deprecated_references_yml_changes'
|
12
|
+
require 'danger-packwerk/check/offenses_formatter'
|
13
|
+
require 'danger-packwerk/check/default_formatter'
|
14
|
+
require 'danger-packwerk/update/offenses_formatter'
|
15
|
+
require 'danger-packwerk/update/default_formatter'
|
12
16
|
end
|