danger-migrations 0.15.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f2bbab97b583c8624b1bd9888ffc87b4b3b7795ead6e3ff0a6d240bc92f4072e
4
+ data.tar.gz: 54a4301bed56abcbfe9b0ddb33a9b6bc39b52ba72191a8981e5b7be93dc6e243
5
+ SHA512:
6
+ metadata.gz: 89b8cbaf9fd9519365cab32c3509bd964dbfae868aec2f6e01fff09dd478b741c39a86e9fb97f59e83741ee871f093f3c6388647b3d2b69dec9e05db641df05e
7
+ data.tar.gz: 061a43cbee49cf2dc6130e2fc2e4cbe630a4d88f817b27cc905e539e80ec3d222c56fefe153c0c6899ca6e7e7500e20d5e12a03b1ab49e942557312a90a699d4
data/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # danger-packwerk
2
+
3
+ `danger-packwerk` integrates [`packwerk`](https://github.com/Shopify/packwerk) with [`danger`](https://github.com/danger/danger) to provide inline comments in PRs related to boundaries in a Rails application.
4
+
5
+ ## Installation and Basic Usage
6
+ Step 1: Add this line to your `Gemfile` (to whatever group your CI uses, as it is not needed in production) and `bundle install`:
7
+
8
+ ```ruby
9
+ gem 'danger-packwerk', group: :test
10
+ ```
11
+
12
+ Step 2: Add these to your `Dangerfile`:
13
+
14
+ ```ruby
15
+ packwerk.check
16
+ package_todo_yml_changes.check
17
+ ```
18
+
19
+ That's it for basic usage!
20
+
21
+ ## Advanced Usage
22
+
23
+ There are currently two danger checks that ship with `danger-packwerk`:
24
+ 1) One that runs `bin/packwerk check` and leaves inline comments in source code on new violations
25
+ 2) One that looks at changes to `package_todo.yml` files and leaves inline comments on added violations.
26
+
27
+ In upcoming iterations, we will include other danger checks, including:
28
+ 1) A danger check that detects changes to `package.yml` files and posts user-configurable messages on the `package.yml` files that are modified.
29
+ 2) A danger check that detects changes to `packwerk.yml` files and allows you to specify the action taken when that happens.
30
+
31
+ ## packwerk.check
32
+ ![This is an image displaying a comment from the Danger github bot after running bin/packwerk check.](docs/check_1.png)
33
+ ![This is an image displaying a comment from the Danger github bot after running bin/packwerk check with the "quick suggestions" accordian open](docs/check_2.png)
34
+
35
+ Without any configuration, `packwerk.check` should just work. By default, it will post a maximum of 15 messages in a PR and it will not fail the build.
36
+
37
+ `packwerk.check` can be configured to in the following ways:
38
+
39
+ ### Change the message that displays in the markdown
40
+ To customize the message in the GitHub comment, pass in `offenses_formatter` to `packwerk.check` in your `Dangerfile`. Here's a simple example:
41
+ ```ruby
42
+ class MyFormatter
43
+ extend T::Sig
44
+ include DangerPackwerk::Check::OffensesFormatter
45
+ # Packwerk::ReferenceOffense: https://github.com/Shopify/packwerk/blob/main/lib/packwerk/reference_offense.rb
46
+ sig do
47
+ override.params(
48
+ offenses: T::Array[Packwerk::ReferenceOffense],
49
+ repo_link: String,
50
+ org_name: String
51
+ repo_url_builder: T.nilable(T.proc.params(constant_path: String).returns(String))
52
+ ).returns(String)
53
+ end
54
+ def format_offenses(offenses, repo_link, org_name, repo_builder_url: nil)
55
+ # your logic here
56
+ end
57
+ end
58
+
59
+ packwerk.check(offenses_formatter: MyFormatter.new)
60
+ ```
61
+
62
+ If you'd like to keep the default messaging but add some context customized to your organization, you can pass that in as follows:
63
+ ```ruby
64
+ custom_help_message = "Need help? Check out our internal docs [here](www.example.com)"
65
+ packwerk.check(offenses_formatter: DangerPackwerk::Check::DefaultFormatter.new(custom_help_message: custom_help_message))
66
+ ```
67
+
68
+ ### Fail the build on new violations
69
+ Simply pass in `fail_build: true` into `check`, as such:
70
+ ```ruby
71
+ packwerk.check(fail_build: true)
72
+ ```
73
+
74
+ If you want to change the default error message, which is `Packwerk violations were detected! Please resolve them to unblock the build.`, then you can also pass in `failure_message`.
75
+
76
+ ### Change the max number of comments that will display
77
+ If you do not change this, the default max is 15. More information about why we chose this number in the source code.
78
+ ```ruby
79
+ packwerk.check(max_comments: 3)
80
+ ```
81
+
82
+ ### Do something extra when there are packwerk failures
83
+ Maybe you want to notify slack or do something else when there are packwerk failures.
84
+
85
+ ```ruby
86
+ packwerk.check(
87
+ # Offenses are a T::Array[Packwerk::ReferenceOffense] => https://github.com/Shopify/packwerk/blob/main/lib/packwerk/reference_offense.rb
88
+ on_failure: -> (offenses) do
89
+ # Notify slack or otherwise do something extra!
90
+ end
91
+ )
92
+ ```
93
+
94
+ ### Supporting new violation types
95
+ By default, only `dependency` and `privacy` violation types are supported. If you wish to use other violation types you'll need to provide them when calling `check`:
96
+ ```ruby
97
+ packwerk.check(
98
+ violation_types: %w[dependency privacy layer]
99
+ )
100
+ ```
101
+
102
+ Any violations not included in this list will be ignored.
103
+
104
+ You will also most likely want to customize the offenses formatter and provide specific feedback for the new violation types.
105
+
106
+ ## package_todo_yml_changes.check
107
+ ![This is an image displaying an inline comment from the Danger github bot.](docs/update.png)
108
+
109
+ Without any configuration, `package_todo_yml_changes.check` should just work. By default, it will post a maximum of 15 messages in a PR, using default messaging defined within this gem.
110
+
111
+ `package_todo_yml_changes.check` can be configured to in the following ways:
112
+
113
+ ### Change the message that displays in the markdown
114
+ To customize the message in the GitHub comment, pass in `offenses_formatter` to `package_todo_yml_changes.check` in your `Dangerfile`. Here's a simple example:
115
+ ```ruby
116
+ class MyFormatter
117
+ extend T::Sig
118
+ include DangerPackwerk::Update::OffensesFormatter
119
+ # DangerPackwerk::BasicReferenceOffense
120
+ sig do
121
+ override.params(
122
+ offenses: T::Array[Packwerk::ReferenceOffense],
123
+ repo_link: String,
124
+ org_name: String
125
+ repo_url_builder: T.nilable(T.proc.params(constant_path: String).returns(String))
126
+ ).returns(String)
127
+ end
128
+ def format_offenses(offenses, repo_link, org_name, repo_builder_url: nil)
129
+ # your logic here
130
+ end
131
+ end
132
+
133
+ package_todo_yml_changes.check(offenses_formatter: MyFormatter.new)
134
+ ```
135
+
136
+ If you'd like to keep the default messaging but add some context customized to your organization, you can pass that in as follows:
137
+ ```ruby
138
+ custom_help_message = "Need help? Check out our internal docs [here](www.example.com)"
139
+ package_todo_yml_changes.check(offenses_formatter: DangerPackwerk::Update::DefaultFormatter.new(custom_help_message: custom_help_message))
140
+ ```
141
+
142
+ ### Change the max number of comments that will display
143
+ If you do not change this, the default max is 15. More information about why we chose this number in the source code.
144
+ ```ruby
145
+ package_todo_yml_changes.check(max_comments: 3)
146
+ ```
147
+
148
+ ### Do something extra before we leave comments
149
+ Maybe you want to notify slack or do something else before we leave comments.
150
+
151
+ ```ruby
152
+ package_todo_yml_changes.check(
153
+ # violation_diff is a DangerPackwerk::ViolationDiff and changed_package_todo_ymls is a T::Array[String]
154
+ before_comment: -> (violation_diff, changed_package_todo_ymls) do
155
+ # Notify slack or otherwise do something extra!
156
+ end
157
+ )
158
+ ```
159
+
160
+ ## Development
161
+
162
+ We welcome your contributions! Please create an issue or pull request and we'd be happy to take a look.
@@ -0,0 +1,99 @@
1
+ # @team Developer Productivity Rails
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ require 'danger'
6
+
7
+ module Danger
8
+ class MigrationsAlone < Plugin
9
+ extend T::Sig
10
+
11
+ MIGRATION_DEPENDENT_PATTERNS = T.let([%r(ar_doc/), %r(db/), %r(\Agems/.*/spec/dummy/db/)].freeze, T::Array[Regexp])
12
+ ALLOWED_FOLDERS = T.let([%r(\Aspec/), %r(\Apacks/.*/spec/), %r(\Agems/.*/spec/), %r(\Asorbet/rails-rbi/), %r(\Asorbet/rbi/dsl/)].freeze, T::Array[Regexp])
13
+
14
+ sig { void }
15
+ def check
16
+ return if new_migrations.empty?
17
+
18
+ disallowed_changed_files = find_disallowed_changed_files
19
+ if disallowed_changed_files.any?
20
+ file_list = (files - disallowed_changed_files).map { |path| "<li>#{path}</li>" }.join("\n")
21
+ disallowed_files = disallowed_changed_files.map { |path| "<li>#{path}</li>" }.join("\n")
22
+
23
+ message = <<~MESSAGE
24
+ <b>Migrations must be checked in alone.</b>
25
+ To ensure application code is not dependent on database migrations taking place
26
+ during the deploys, <i>please separate the schema change along with migration files into a dedicated PR:</i>
27
+ <ul>
28
+ #{file_list}
29
+ </ul>
30
+
31
+ <b>List of disallowed files</b>
32
+ <ul>
33
+ #{disallowed_files}
34
+ </ul>
35
+ <a href='https://confluence.gustocorp.com/display/BE/Database+Schema+Migrations+in+Hawaiian+Ice'>More information</a>
36
+ MESSAGE
37
+
38
+ fail message
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ sig { returns(T::Array[String]) }
45
+ def find_disallowed_changed_files
46
+ files.reject do |file_path|
47
+ next true if migration?(file_path)
48
+
49
+ next true if %r(\Adb/.*structure.sql).match?(file_path)
50
+
51
+ next true if %r(\Adb/.*seeds.rb).match?(file_path)
52
+
53
+ next true if allow_listed_file?(file_path, MIGRATION_DEPENDENT_PATTERNS)
54
+
55
+ next true if allow_listed_file?(file_path, ALLOWED_FOLDERS)
56
+
57
+ next true if comment_and_whitespace_changes_only?(file_path)
58
+ end
59
+ end
60
+
61
+ sig { returns(T::Array[String]) }
62
+ def new_migrations
63
+ git.added_files.select { |file_path| migration?(file_path) }
64
+ end
65
+
66
+ sig { params(file_path: String).returns(T::Boolean) }
67
+ def migration?(file_path)
68
+ %r(db/migrate/[^/]+\z).match?(file_path)
69
+ end
70
+
71
+ sig { params(file_path: String, patterns: T::Array[Regexp]).returns(T::Boolean) }
72
+ def allow_listed_file?(file_path, patterns)
73
+ patterns.any? { |pattern| pattern =~ file_path }
74
+ end
75
+
76
+ sig { params(file: String).returns(T::Boolean) }
77
+ def comment_and_whitespace_changes_only?(file)
78
+ return false unless git.modified_files.include?(file)
79
+
80
+ cleaned_diff(file).all? do |line|
81
+ # comment added || comment removed || whitespace added || whitespace removed
82
+ line.start_with?('+#', '-#') || line =~ /\A\+\s*\z/ || line =~ /\A-\s*\z/
83
+ end
84
+ end
85
+
86
+ sig { params(file_path: String).returns(T::Array[String]) }
87
+ def cleaned_diff(file_path)
88
+ diff = git.diff_for_file(file_path)
89
+ diff.patch.lines.select do |line|
90
+ line.start_with?('-', '+') && !line.include?(diff.path)
91
+ end
92
+ end
93
+
94
+ sig { returns(T::Array[String]) }
95
+ def files
96
+ git.added_files + git.modified_files + git.deleted_files
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,66 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'code_ownership'
5
+ require 'packs'
6
+
7
+ # In order to support running danger-migrations from a non-root filepath, we need
8
+ # to wrap some git functions in filesystem wrappers: packwerk runs relative to
9
+ # the rails app root, whereas git returns paths on the actual filesystem.
10
+ module DangerMigrations
11
+ module Private
12
+ class GitFilesystem < T::Struct
13
+ extend T::Sig
14
+
15
+ const :git, Danger::DangerfileGitPlugin
16
+ const :root, String
17
+
18
+ sig { returns(T::Array[{ after: String, before: String }]) }
19
+ def renamed_files
20
+ @git.renamed_files.map do |f|
21
+ {
22
+ after: convert_file_from_filesystem(f[:after]),
23
+ before: convert_file_from_filesystem(f[:before]),
24
+ }
25
+ end
26
+ end
27
+
28
+ sig { returns(T::Array[String]) }
29
+ def modified_files
30
+ convert_from_filesystem(@git.modified_files.to_a)
31
+ end
32
+
33
+ sig { returns(T::Array[String]) }
34
+ def deleted_files
35
+ convert_from_filesystem(@git.deleted_files.to_a)
36
+ end
37
+
38
+ sig { returns(T::Array[String]) }
39
+ def added_files
40
+ convert_from_filesystem(@git.added_files.to_a)
41
+ end
42
+
43
+ sig { params(filename_on_disk: String).returns(::Git::Diff::DiffFile) }
44
+ def diff(filename_on_disk)
45
+ @git.diff[filename_on_disk]
46
+ end
47
+
48
+ sig { params(path: String).returns(String) }
49
+ def convert_to_filesystem(path)
50
+ Pathname(@root).join(path).to_s
51
+ end
52
+
53
+ private
54
+
55
+ sig { params(files: T::Array[String]).returns(T::Array[String]) }
56
+ def convert_from_filesystem(files)
57
+ files.map { |f| convert_file_from_filesystem(f) }
58
+ end
59
+
60
+ sig { params(file: String).returns(String) }
61
+ def convert_file_from_filesystem(file)
62
+ Pathname(file).relative_path_from(@root).to_s
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module DangerMigrations
5
+ VERSION = '0.15.0'
6
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file exists so clients can call `require 'danger-migrations'`
5
+ require 'sorbet-runtime'
6
+ require 'danger-migrations/migrations_alone'
@@ -0,0 +1,4 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'danger/migrations'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: danger-migrations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.15.0
5
+ platform: ruby
6
+ authors:
7
+ - Gusto Engineers
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: danger-plugin-api
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: sorbet-runtime
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: Danger plugin for migrations.
41
+ email:
42
+ - dev@gusto.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.md
48
+ - lib/danger-migrations.rb
49
+ - lib/danger-migrations/migrations_alone.rb
50
+ - lib/danger-migrations/private/git.rb
51
+ - lib/danger-migrations/version.rb
52
+ - lib/danger_plugin.rb
53
+ homepage: https://github.com/rubyatscale/danger-migrations
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/rubyatscale/danger-migrations
58
+ source_code_uri: https://github.com/rubyatscale/danger-migrations
59
+ changelog_uri: https://github.com/rubyatscale/danger-migrations/releases
60
+ allowed_push_host: https://rubygems.org
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.9
76
+ specification_version: 4
77
+ summary: Danger plugin for migrations.
78
+ test_files: []