packwerk 2.2.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -5
- data/.ruby-version +1 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +5 -95
- data/README.md +2 -7
- data/RESOLVING_VIOLATIONS.md +7 -7
- data/TROUBLESHOOT.md +1 -23
- data/USAGE.md +149 -59
- data/dev.yml +1 -1
- data/exe/packwerk +1 -0
- data/gemfiles/Gemfile-rails-6-1 +1 -1
- data/lib/packwerk/application_validator.rb +54 -285
- data/lib/packwerk/association_inspector.rb +2 -0
- data/lib/packwerk/cache.rb +6 -5
- data/lib/packwerk/checker.rb +54 -0
- data/lib/packwerk/cli/result.rb +11 -0
- data/lib/packwerk/cli.rb +56 -31
- data/lib/packwerk/configuration.rb +61 -40
- data/lib/packwerk/const_node_inspector.rb +2 -0
- data/lib/packwerk/constant_context.rb +8 -0
- data/lib/packwerk/constant_discovery.rb +5 -6
- data/lib/packwerk/constant_name_inspector.rb +2 -0
- data/lib/packwerk/disable_sorbet.rb +41 -0
- data/lib/packwerk/extension_loader.rb +24 -0
- data/lib/packwerk/file_processor.rb +3 -1
- data/lib/packwerk/files_for_processing.rb +25 -12
- data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
- data/lib/packwerk/formatters/progress_formatter.rb +31 -12
- data/lib/packwerk/generators/configuration_file.rb +7 -2
- data/lib/packwerk/generators/root_package.rb +5 -1
- data/lib/packwerk/generators/templates/package.yml +0 -10
- data/lib/packwerk/graph.rb +10 -2
- data/lib/packwerk/node.rb +1 -1
- data/lib/packwerk/node_helpers.rb +14 -7
- data/lib/packwerk/node_processor.rb +2 -0
- data/lib/packwerk/node_processor_factory.rb +6 -4
- data/lib/packwerk/node_visitor.rb +10 -1
- data/lib/packwerk/offense_collection.rb +43 -23
- data/lib/packwerk/offenses_formatter.rb +59 -2
- data/lib/packwerk/package.rb +7 -35
- data/lib/packwerk/package_set.rb +1 -1
- data/lib/packwerk/{deprecated_references.rb → package_todo.rb} +29 -13
- data/lib/packwerk/parse_run.rb +29 -36
- data/lib/packwerk/parsed_constant_definitions.rb +28 -5
- data/lib/packwerk/parsers/erb.rb +23 -4
- data/lib/packwerk/parsers/factory.rb +11 -2
- data/lib/packwerk/parsers/parser_interface.rb +1 -1
- data/lib/packwerk/parsers/ruby.rb +13 -3
- data/lib/packwerk/parsers.rb +6 -2
- data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
- data/lib/packwerk/reference.rb +7 -1
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
- data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
- data/lib/packwerk/reference_extractor.rb +24 -12
- data/lib/packwerk/reference_offense.rb +2 -2
- data/lib/packwerk/run_context.rb +7 -10
- data/lib/packwerk/spring_command.rb +11 -2
- data/lib/packwerk/unresolved_reference.rb +9 -1
- data/lib/packwerk/validator/result.rb +18 -0
- data/lib/packwerk/validator.rb +90 -0
- data/lib/packwerk/validators/dependency_validator.rb +154 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +64 -26
- data/packwerk.gemspec +4 -2
- data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
- data/sorbet/rbi/shims/minitest/test.rb +8 -0
- data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
- data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
- data/sorbet/rbi/shims/parser.rbi +13 -0
- metadata +35 -16
- data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
- data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
- data/lib/packwerk/result.rb +0 -9
- data/lib/packwerk/sanity_checker.rb +0 -8
- data/lib/packwerk/violation_type.rb +0 -11
- data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
- data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11206a06e6ac99c1fb8ef4cdefbe334133317d0599fce6480d4dfe10f694c747
|
4
|
+
data.tar.gz: 606778530b8a872d331e5f331b9c6e14cb37ff6e0a5e13247bb0aebe1ca9e0cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66166a9c683483fda99341adc79c807c40f8190c349c8650b8646810e06d11f9b4728e2e938ef0abf640967d0764d78b66e956aba3bdaef830c4a15253711542
|
7
|
+
data.tar.gz: c1cce5df16246f1561cd0954abca4685e21adcaf52d3f6d164acd776f9466a0ad276a65b9a2f5cc9e358b058fcdc2cef265c2b3948043f9f3e35b1d4798ec5e9
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
name: CI
|
2
2
|
|
3
|
-
on: [push]
|
3
|
+
on: [push, pull_request]
|
4
4
|
|
5
5
|
jobs:
|
6
6
|
tests:
|
@@ -12,13 +12,10 @@ jobs:
|
|
12
12
|
- gemfiles/Gemfile-rails-6-0
|
13
13
|
- gemfiles/Gemfile-rails-6-1
|
14
14
|
ruby:
|
15
|
-
- 2.6
|
16
15
|
- 2.7
|
17
16
|
- 3.0
|
18
17
|
- 3.1
|
19
|
-
|
20
|
-
- ruby: 2.6
|
21
|
-
gemfile: Gemfile
|
18
|
+
- 3.2
|
22
19
|
env:
|
23
20
|
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
24
21
|
name: "Tests: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}"
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.2.1
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
packwerk (
|
5
|
-
activesupport (>=
|
4
|
+
packwerk (3.0.0)
|
5
|
+
activesupport (>= 6.0)
|
6
6
|
ast
|
7
7
|
better_html
|
8
8
|
bundler
|
@@ -15,31 +15,6 @@ PATH
|
|
15
15
|
GEM
|
16
16
|
remote: https://rubygems.org/
|
17
17
|
specs:
|
18
|
-
actioncable (7.0.3.1)
|
19
|
-
actionpack (= 7.0.3.1)
|
20
|
-
activesupport (= 7.0.3.1)
|
21
|
-
nio4r (~> 2.0)
|
22
|
-
websocket-driver (>= 0.6.1)
|
23
|
-
actionmailbox (7.0.3.1)
|
24
|
-
actionpack (= 7.0.3.1)
|
25
|
-
activejob (= 7.0.3.1)
|
26
|
-
activerecord (= 7.0.3.1)
|
27
|
-
activestorage (= 7.0.3.1)
|
28
|
-
activesupport (= 7.0.3.1)
|
29
|
-
mail (>= 2.7.1)
|
30
|
-
net-imap
|
31
|
-
net-pop
|
32
|
-
net-smtp
|
33
|
-
actionmailer (7.0.3.1)
|
34
|
-
actionpack (= 7.0.3.1)
|
35
|
-
actionview (= 7.0.3.1)
|
36
|
-
activejob (= 7.0.3.1)
|
37
|
-
activesupport (= 7.0.3.1)
|
38
|
-
mail (~> 2.5, >= 2.5.4)
|
39
|
-
net-imap
|
40
|
-
net-pop
|
41
|
-
net-smtp
|
42
|
-
rails-dom-testing (~> 2.0)
|
43
18
|
actionpack (7.0.3.1)
|
44
19
|
actionview (= 7.0.3.1)
|
45
20
|
activesupport (= 7.0.3.1)
|
@@ -47,34 +22,12 @@ GEM
|
|
47
22
|
rack-test (>= 0.6.3)
|
48
23
|
rails-dom-testing (~> 2.0)
|
49
24
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
50
|
-
actiontext (7.0.3.1)
|
51
|
-
actionpack (= 7.0.3.1)
|
52
|
-
activerecord (= 7.0.3.1)
|
53
|
-
activestorage (= 7.0.3.1)
|
54
|
-
activesupport (= 7.0.3.1)
|
55
|
-
globalid (>= 0.6.0)
|
56
|
-
nokogiri (>= 1.8.5)
|
57
25
|
actionview (7.0.3.1)
|
58
26
|
activesupport (= 7.0.3.1)
|
59
27
|
builder (~> 3.1)
|
60
28
|
erubi (~> 1.4)
|
61
29
|
rails-dom-testing (~> 2.0)
|
62
30
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
63
|
-
activejob (7.0.3.1)
|
64
|
-
activesupport (= 7.0.3.1)
|
65
|
-
globalid (>= 0.3.6)
|
66
|
-
activemodel (7.0.3.1)
|
67
|
-
activesupport (= 7.0.3.1)
|
68
|
-
activerecord (7.0.3.1)
|
69
|
-
activemodel (= 7.0.3.1)
|
70
|
-
activesupport (= 7.0.3.1)
|
71
|
-
activestorage (7.0.3.1)
|
72
|
-
actionpack (= 7.0.3.1)
|
73
|
-
activejob (= 7.0.3.1)
|
74
|
-
activerecord (= 7.0.3.1)
|
75
|
-
activesupport (= 7.0.3.1)
|
76
|
-
marcel (~> 1.0)
|
77
|
-
mini_mime (>= 1.1.0)
|
78
31
|
activesupport (7.0.3.1)
|
79
32
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
80
33
|
i18n (>= 1.6, < 2)
|
@@ -95,10 +48,7 @@ GEM
|
|
95
48
|
constant_resolver (0.2.0)
|
96
49
|
crass (1.0.6)
|
97
50
|
diff-lcs (1.5.0)
|
98
|
-
digest (3.1.0)
|
99
51
|
erubi (1.11.0)
|
100
|
-
globalid (1.0.0)
|
101
|
-
activesupport (>= 5.0)
|
102
52
|
i18n (1.12.0)
|
103
53
|
concurrent-ruby (~> 1.0)
|
104
54
|
json (2.6.2)
|
@@ -109,37 +59,16 @@ GEM
|
|
109
59
|
m (1.6.0)
|
110
60
|
method_source (>= 0.6.7)
|
111
61
|
rake (>= 0.9.2.2)
|
112
|
-
mail (2.7.1)
|
113
|
-
mini_mime (>= 0.1.1)
|
114
|
-
marcel (1.0.2)
|
115
62
|
method_source (1.0.0)
|
116
|
-
mini_mime (1.1.2)
|
117
63
|
mini_portile2 (2.8.0)
|
118
64
|
minitest (5.16.2)
|
119
65
|
minitest-focus (1.3.1)
|
120
66
|
minitest (>= 4, < 6)
|
121
67
|
mocha (1.14.0)
|
122
|
-
net-imap (0.2.3)
|
123
|
-
digest
|
124
|
-
net-protocol
|
125
|
-
strscan
|
126
|
-
net-pop (0.1.1)
|
127
|
-
digest
|
128
|
-
net-protocol
|
129
|
-
timeout
|
130
|
-
net-protocol (0.1.3)
|
131
|
-
timeout
|
132
|
-
net-smtp (0.3.1)
|
133
|
-
digest
|
134
|
-
net-protocol
|
135
|
-
timeout
|
136
68
|
netrc (0.11.0)
|
137
|
-
nio4r (2.5.8)
|
138
69
|
nokogiri (1.13.8)
|
139
70
|
mini_portile2 (~> 2.8.0)
|
140
71
|
racc (~> 1.4)
|
141
|
-
nokogiri (1.13.8-x86_64-darwin)
|
142
|
-
racc (~> 1.4)
|
143
72
|
parallel (1.22.1)
|
144
73
|
parser (3.1.2.1)
|
145
74
|
ast (~> 2.4.1)
|
@@ -151,20 +80,6 @@ GEM
|
|
151
80
|
rack (2.2.4)
|
152
81
|
rack-test (2.0.2)
|
153
82
|
rack (>= 1.3)
|
154
|
-
rails (7.0.3.1)
|
155
|
-
actioncable (= 7.0.3.1)
|
156
|
-
actionmailbox (= 7.0.3.1)
|
157
|
-
actionmailer (= 7.0.3.1)
|
158
|
-
actionpack (= 7.0.3.1)
|
159
|
-
actiontext (= 7.0.3.1)
|
160
|
-
actionview (= 7.0.3.1)
|
161
|
-
activejob (= 7.0.3.1)
|
162
|
-
activemodel (= 7.0.3.1)
|
163
|
-
activerecord (= 7.0.3.1)
|
164
|
-
activestorage (= 7.0.3.1)
|
165
|
-
activesupport (= 7.0.3.1)
|
166
|
-
bundler (>= 1.15.0)
|
167
|
-
railties (= 7.0.3.1)
|
168
83
|
rails-dom-testing (2.0.3)
|
169
84
|
activesupport (>= 4.2.0)
|
170
85
|
nokogiri (>= 1.6)
|
@@ -232,7 +147,6 @@ GEM
|
|
232
147
|
sorbet-runtime (>= 0.5.9204)
|
233
148
|
thor (>= 0.19.2)
|
234
149
|
spring (4.0.0)
|
235
|
-
strscan (3.0.4)
|
236
150
|
syntax_tree (3.3.0)
|
237
151
|
prettier_print
|
238
152
|
tapioca (0.9.2)
|
@@ -246,7 +160,6 @@ GEM
|
|
246
160
|
thor (>= 1.2.0)
|
247
161
|
yard-sorbet
|
248
162
|
thor (1.2.1)
|
249
|
-
timeout (0.3.0)
|
250
163
|
tzinfo (2.0.5)
|
251
164
|
concurrent-ruby (~> 1.0)
|
252
165
|
unicode-display_width (2.2.0)
|
@@ -254,15 +167,12 @@ GEM
|
|
254
167
|
diff-lcs (~> 1.3)
|
255
168
|
parser (>= 3.1.0)
|
256
169
|
webrick (1.7.0)
|
257
|
-
websocket-driver (0.7.5)
|
258
|
-
websocket-extensions (>= 0.1.0)
|
259
|
-
websocket-extensions (0.1.5)
|
260
170
|
yard (0.9.28)
|
261
171
|
webrick (~> 1.7.0)
|
262
172
|
yard-sorbet (0.6.1)
|
263
173
|
sorbet-runtime (>= 0.5)
|
264
174
|
yard (>= 0.9)
|
265
|
-
zeitwerk (2.6.
|
175
|
+
zeitwerk (2.6.4)
|
266
176
|
|
267
177
|
PLATFORMS
|
268
178
|
ruby
|
@@ -276,7 +186,7 @@ DEPENDENCIES
|
|
276
186
|
minitest-focus
|
277
187
|
mocha
|
278
188
|
packwerk!
|
279
|
-
|
189
|
+
railties
|
280
190
|
rake
|
281
191
|
rubocop-performance
|
282
192
|
rubocop-shopify
|
@@ -288,4 +198,4 @@ DEPENDENCIES
|
|
288
198
|
zeitwerk
|
289
199
|
|
290
200
|
BUNDLED WITH
|
291
|
-
2.
|
201
|
+
2.4.7
|
data/README.md
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# Packwerk [![Build Status](https://github.com/Shopify/packwerk/workflows/CI/badge.svg)](https://github.com/Shopify/packwerk/actions?query=workflow%3ACI)
|
2
2
|
|
3
|
-
### ⚠️ While Shopify is actively using `packwerk`, we consider it feature complete.
|
4
|
-
We are keeping `packwerk` compatible with current versions of Ruby and Rails, but will accept feature requests only in rare cases. Please submit bug fixes though!
|
5
|
-
|
6
|
-
---
|
7
|
-
|
8
3
|
> "I know who you are and because of that I know what you do."
|
9
4
|
> This knowledge is a dependency that raises the cost of change.
|
10
5
|
|
@@ -15,14 +10,13 @@ Packwerk is a Ruby gem used to enforce boundaries and modularize Rails applicati
|
|
15
10
|
Packwerk can be used to:
|
16
11
|
* Combine groups of files into packages
|
17
12
|
* Define package-level constant visibility (i.e. have publicly accessible constants)
|
18
|
-
* Enforce privacy (inbound) and dependency (outbound) boundaries between packages
|
19
13
|
* Help existing codebases to become more modular without obstructing development
|
20
14
|
|
21
15
|
## Prerequisites
|
22
16
|
|
23
17
|
Packwerk needs [Zeitwerk](https://github.com/fxn/zeitwerk) enabled, which comes with Rails 6.
|
24
18
|
|
25
|
-
Packwerk supports MRI versions 2.
|
19
|
+
Packwerk supports MRI versions 2.7 and above.
|
26
20
|
|
27
21
|
## Demo
|
28
22
|
|
@@ -67,6 +61,7 @@ Various third parties have built tooling on top of packwerk. Here's a selection
|
|
67
61
|
- https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
|
68
62
|
- https://github.com/Gusto/stimpack sets up Rails autoloading, as well as `rspec` and `FactoryBot` integration, for packages arranged in a flat list. Stimpack is quite convenient, but for autoloading we recommend to use `Rails::Engine`s instead.
|
69
63
|
- https://github.com/rubyatscale/danger-packwerk integrates packwerk with [danger.systems](https://danger.systems) to provide packwerk feedback as Github inline PR comments
|
64
|
+
- https://github.com/rubyatscale/packwerk-extensions contains extensions for packwerk, including a checker for packwerk that allows you to enforce public API boundaries. This was originally extracted from `packwerk` itself.
|
70
65
|
|
71
66
|
## Development
|
72
67
|
|
data/RESOLVING_VIOLATIONS.md
CHANGED
@@ -3,27 +3,27 @@
|
|
3
3
|
Violations can be [recorded as a deprecation](#recording-violations) or (better!) [eliminated](#eliminating-violations).
|
4
4
|
|
5
5
|
## Recording Violations
|
6
|
-
💡 New privacy and dependency violations are never hard-blocked. There are many very valid reasons to run `bin/packwerk update-
|
6
|
+
💡 New privacy and dependency violations are never hard-blocked. There are many very valid reasons to run `bin/packwerk update-todo`, adding new violations to `package_todo.yml` files. Even if you feel your reason might not be "valid," if your judgement says adding the violation and shipping your change will produce positive impact, trust your gut.
|
7
7
|
|
8
8
|
### Emergency Fixes
|
9
9
|
❔ Is it a revert or is there a lot of urgency because you are fixing a production bug impacting customers?
|
10
10
|
|
11
|
-
➡️ Simply run `bin/packwerk update-
|
11
|
+
➡️ Simply run `bin/packwerk update-todo`, and address the violation when the customer issue is resolved.
|
12
12
|
|
13
13
|
### Improving System Design
|
14
14
|
❔ Are you improving system boundaries by renaming or moving a file, class, constant,` or module?
|
15
15
|
|
16
|
-
➡️ Simply run `bin/packwerk update-
|
16
|
+
➡️ Simply run `bin/packwerk update-todo`. We've improved how our system is organized, so the new violations are natural.
|
17
17
|
|
18
18
|
### Making Things Explicit
|
19
19
|
❔ Are you making something that was implicit (hidden to Packwerk) explicit, such as adding a Sorbet signature, using a class instead of a method call, or something similar?
|
20
20
|
|
21
|
-
➡️ Simply run `bin/packwerk update-
|
21
|
+
➡️ Simply run `bin/packwerk update-todo`. Making something implicit explicit and capturing that as a new violation is a strict improvement.
|
22
22
|
|
23
23
|
### Temporary State
|
24
24
|
❔ Is the violation temporary because you will soon delete the code or is it part of a refactor to improve system boundaries and reduce violations overall?
|
25
25
|
|
26
|
-
➡️ Simply run `bin/packwerk update-
|
26
|
+
➡️ Simply run `bin/packwerk update-todo`. Sometimes things get "worse" before they get better.
|
27
27
|
|
28
28
|
### Delivering Features
|
29
29
|
❔ Are you in a rush to get a feature out and product, your manager, or an internal sense of urgency has made you feel like you can't resolve system design issues?
|
@@ -57,7 +57,7 @@ An explicit and implementation-hiding public API is a cornerstone of well-modula
|
|
57
57
|
|
58
58
|
➡️ Work together on a new public API and use that instead! If the thing we're using should be public, move it to the public folder to make it public!
|
59
59
|
|
60
|
-
⛈️ If working with the package's owner to improve the API is not possible, run `bin/packwerk update-
|
60
|
+
⛈️ If working with the package's owner to improve the API is not possible, run `bin/packwerk update-todo`. Add some context to your PR about why it's not possible.
|
61
61
|
|
62
62
|
### Dependency Violations
|
63
63
|
💡 Packwerk thinks something is a dependency violation if you're referencing a constant, class, module defined ANYWHERE but your package doesn't list it as an explicit dependency in its `package.yml`. We care about these because it allows us to be systematically intentional about what our code needs to run and helps us untangle and remove dependency cycles from our system.
|
@@ -78,4 +78,4 @@ Thoughtful dependency management is another cornerstone of well-modularized code
|
|
78
78
|
|
79
79
|
➡️ Work with the owners of the relevant packages, as well as your team, to think through a design that doesn't include the unwanted dependency.
|
80
80
|
|
81
|
-
⛈️ If this is not possible within the scope of your changes (think hard about this one!), run `bin/packwerk update-
|
81
|
+
⛈️ If this is not possible within the scope of your changes (think hard about this one!), run `bin/packwerk update-todo`. Add some context to your PR about why it's not possible, and any additional context you may have, such as a possible solution.
|
data/TROUBLESHOOT.md
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
* [Troubleshooting violations](#Troubleshooting-violations)
|
4
4
|
* [Feedback loop](#Feedback-loop)
|
5
|
-
* [Package Privacy violation](#Package-Privacy-violation)
|
6
|
-
* [Interpreting Privacy violation](#Interpreting-Privacy-violation)
|
7
5
|
* [Package Dependency violation](#Package-Dependency-violation)
|
8
6
|
* [Interpreting Dependency violation](#Interpreting-Dependency-violation)
|
9
7
|
|
@@ -16,32 +14,12 @@ You can specify folders or packages in Packwerk commands for a shorter run time:
|
|
16
14
|
|
17
15
|
bin/packwerk check components/your_package
|
18
16
|
|
19
|
-
bin/packwerk update-
|
17
|
+
bin/packwerk update-todo components/your_package
|
20
18
|
|
21
19
|
_Note: You cannot specify folders or packages for `bin/packwerk validate` because the command runs for the entire application._
|
22
20
|
|
23
21
|
![](static/packwerk_check_violation.gif)
|
24
22
|
|
25
|
-
### Package Privacy violation
|
26
|
-
A constant that is private to its package has been referenced from outside of the package. Constants are declared private in their package’s `package.yml`.
|
27
|
-
|
28
|
-
See: [USAGE.md - Enforcing privacy boundary](USAGE.md#Enforcing-privacy-boundary)
|
29
|
-
|
30
|
-
#### Interpreting Privacy violation
|
31
|
-
|
32
|
-
> /Users/JaneDoe/src/github.com/sample-project/user/app/controllers/labels_controller.rb:170:30
|
33
|
-
> Privacy violation: '::Billing::CarrierInvoiceTransaction' is private to 'billing' but referenced from 'user'.
|
34
|
-
> Is there a public entrypoint in 'billing/app/public/' that you can use instead?
|
35
|
-
>
|
36
|
-
> Inference details: 'Billing::CarrierInvoiceTransaction' refers to ::Billing::CarrierInvoiceTransaction which seems to be defined in billing/app/models/billing/carrier_invoice_transaction.rb.
|
37
|
-
|
38
|
-
There has been a privacy violation of the package `billing` in the package `user`, through the use of the constant `Billing::CarrierInvoiceTransaction` in the file `user/app/controllers/labels_controller.rb`.
|
39
|
-
|
40
|
-
##### Suggestions
|
41
|
-
You may be accessing the implementation of a piece of functionality that is supposed to be accessed through a public interface on the package. Try to use the public interface instead. A package’s public interface should be defined in its `app/public` folder and documented.
|
42
|
-
|
43
|
-
The functionality you’re looking for may not be intended to be reused across packages at all. If there is no public interface for it but you have a good reason to use it from outside of its package, find the people responsible for the package and discuss a solution with them.
|
44
|
-
|
45
23
|
### Package Dependency violation
|
46
24
|
A constant defined in a package A is referenced from a package B that doesn’t define a dependency on A. Packages define their dependencies in their `package.yml`.
|
47
25
|
|
data/USAGE.md
CHANGED
@@ -13,14 +13,15 @@
|
|
13
13
|
* [Defining packages](#defining-packages)
|
14
14
|
* [Package metadata](#package-metadata)
|
15
15
|
* [Types of boundary checks](#types-of-boundary-checks)
|
16
|
-
* [Enforcing privacy boundary](#enforcing-privacy-boundary)
|
17
|
-
* [Using public folders](#using-public-folders)
|
18
16
|
* [Enforcing dependency boundary](#enforcing-dependency-boundary)
|
17
|
+
* [Using strict mode](#using-strict-mode)
|
19
18
|
* [Checking for violations](#checking-for-violations)
|
20
19
|
* [Resolving new violations](#resolving-new-violations)
|
21
20
|
* [Understanding how to respond to new violations](#understanding-how-to-respond-to-new-violations)
|
22
21
|
* [Recording existing violations](#recording-existing-violations)
|
22
|
+
* [Understanding the package todo file](#understanding-the-package-todo-file)
|
23
23
|
* [Understanding the list of deprecated references](#understanding-the-list-of-deprecated-references)
|
24
|
+
* [Loading extensions](#loading-extensions)
|
24
25
|
|
25
26
|
## What problem does Packwerk solve?
|
26
27
|
|
@@ -143,50 +144,10 @@ Example:
|
|
143
144
|
|
144
145
|
## Types of boundary checks
|
145
146
|
|
146
|
-
Packwerk
|
147
|
-
|
148
|
-
#### Enforcing privacy boundary
|
149
|
-
|
150
|
-
A package's privacy boundary is violated when there is a reference to the package's private constants from a source outside the package.
|
151
|
-
|
152
|
-
There are two ways you can enforce privacy for your package:
|
153
|
-
|
154
|
-
1. Enforce privacy for all external sources
|
155
|
-
|
156
|
-
```yaml
|
157
|
-
# components/merchandising/package.yml
|
158
|
-
enforce_privacy: true # will make everything private that is not in
|
159
|
-
# the components/merchandising/app/public folder
|
160
|
-
```
|
161
|
-
|
162
|
-
Setting `enforce_privacy` to true will make all references to private constants in your package a violation.
|
163
|
-
|
164
|
-
2. Enforce privacy for specific constants
|
165
|
-
|
166
|
-
```yaml
|
167
|
-
# components/merchandising/package.yml
|
168
|
-
enforce_privacy:
|
169
|
-
- "::Merchandising::Product"
|
170
|
-
- "::SomeNamespace" # enforces privacy for the namespace and
|
171
|
-
# everything nested in it
|
172
|
-
```
|
173
|
-
|
174
|
-
It will be a privacy violation when a file outside of the `components/merchandising` package tries to reference `Merchandising::Product`.
|
175
|
-
|
176
|
-
##### Using public folders
|
177
|
-
You may enforce privacy either way mentioned above and still expose a public API for your package by placing constants in the public folder, which by default is `app/public`. The constants in the public folder will be made available for use by the rest of the application.
|
178
|
-
|
179
|
-
##### Defining your own public folder
|
180
|
-
|
181
|
-
You may prefer to override the default public folder, you can do so on a per-package basis by defining a `public_path`.
|
182
|
-
|
183
|
-
Example:
|
184
|
-
|
185
|
-
```yaml
|
186
|
-
public_path: my/custom/path/
|
187
|
-
```
|
147
|
+
Packwerk ships with dependency boundary checking only. See [`packwerk-extensions`](https://github.com/rubyatscale/packwerk-extensions) to incorporate privacy checks into your use of `packwerk`.
|
188
148
|
|
189
149
|
#### Enforcing dependency boundary
|
150
|
+
|
190
151
|
A package's dependency boundary is violated whenever it references a constant in some package that has not been declared as a dependency.
|
191
152
|
|
192
153
|
Specify `enforce_dependencies: true` to start enforcing the dependencies of a package. The intentional dependencies of the package are specified as a list under a `dependencies:` key.
|
@@ -202,6 +163,15 @@ dependencies:
|
|
202
163
|
|
203
164
|
It will be a dependency violation when `components/shop_identity` tries to reference a constant that is not within `components/platform` or itself.
|
204
165
|
|
166
|
+
#### Using strict mode
|
167
|
+
|
168
|
+
Once there are no more violations in a package, you can turn on `strict` mode, which will prevent new violations from being added to the package's `package_todo.yml`. To use this, simply change `enforce_dependencies: true` to `enforce_dependencies: strict` in your `package.yml`.
|
169
|
+
|
170
|
+
Then, when you run `bin/packwerk check`, new violations will cause the following error to be displayed:
|
171
|
+
```
|
172
|
+
packs/referencing_package cannot have dependency violations on packs/defining_package because strict mode is enabled for dependency violations in packs/referencing_package/package.yml
|
173
|
+
```
|
174
|
+
|
205
175
|
## Checking for violations
|
206
176
|
|
207
177
|
After enforcing the boundary checks for a package, you may execute:
|
@@ -225,6 +195,7 @@ In order to keep the package system valid at each version of the application, we
|
|
225
195
|
See: [TROUBLESHOOT.md - Sample violations](TROUBLESHOOT.md#Sample-violations)
|
226
196
|
|
227
197
|
## Resolving new violations
|
198
|
+
|
228
199
|
### Understanding how to respond to new violations
|
229
200
|
|
230
201
|
When you have a new dependency or privacy violation, what do you do?
|
@@ -235,28 +206,21 @@ See: [RESOLVING_VIOLATIONS.md](RESOLVING_VIOLATIONS.md)
|
|
235
206
|
|
236
207
|
For existing codebases, packages are likely to have existing boundary violations.
|
237
208
|
|
238
|
-
If so, you will want to stop the bleeding and prevent more violations from occuring. The existing violations in the codebase can be recorded in a [
|
239
|
-
|
240
|
-
bin/packwerk update-deprecations
|
241
|
-
|
242
|
-
Similar to `bin/packwerk check`, you may also run `bin/packwerk update-deprecations` on folders or packages:
|
209
|
+
If so, you will want to stop the bleeding and prevent more violations from occuring. The existing violations in the codebase can be recorded in a [todo list](#understanding-the-package-todo-file) by executing:
|
243
210
|
|
244
|
-
bin/packwerk update-
|
211
|
+
bin/packwerk update-todo
|
245
212
|
|
246
213
|
![](static/packwerk_update.gif)
|
247
214
|
|
248
|
-
|
249
|
-
|
250
|
-
`bin/packwerk update-deprecations` should only be run to record existing violations and to remove deprecated references that have been worked off. Running `bin/packwerk update-deprecations` to resolve a violation should be the very last resort.
|
215
|
+
`bin/packwerk update-todo` should only be run to record existing violations and to remove violations that have been worked off. Running `bin/packwerk update-todo` to resolve a violation should be the very last resort.
|
251
216
|
|
252
217
|
See: [TROUBLESHOOT.md - Troubleshooting violations](TROUBLESHOOT.md#Troubleshooting_violations)
|
253
218
|
|
219
|
+
### Understanding the package todo file
|
254
220
|
|
255
|
-
|
256
|
-
|
257
|
-
The deprecated references list is called `deprecated_references.yml` and can be found in the package folder. The list outlines the constant violations of the package, where the violation is located, and the file defining the violation.
|
221
|
+
The package TODO list is called `package_todo.yml` and can be found in the package folder. The list outlines the constant violations of the package, where the violation is located, and the file defining the violation.
|
258
222
|
|
259
|
-
The
|
223
|
+
The package TODO list should not be added to, but worked off over time.
|
260
224
|
|
261
225
|
```yaml
|
262
226
|
components/merchant:
|
@@ -267,11 +231,137 @@ components/merchant:
|
|
267
231
|
- components/merchant/app/public/merchant/generate_order.rb
|
268
232
|
```
|
269
233
|
|
270
|
-
Above is an example of a constant violation entry in `
|
234
|
+
Above is an example of a constant violation entry in `package_todo.yml`.
|
271
235
|
|
272
236
|
* `components/merchant` - package where the constant violation is found
|
273
237
|
* `::Checkouts::Core::CheckoutId` - violated constant in question
|
274
238
|
* `dependency` - type of violation, either dependency or privacy
|
275
239
|
* `components/merchant/app/public/merchant/generate_order.rb` - path to the file containing the violated constant
|
276
240
|
|
277
|
-
Violations exist within the package that makes a violating reference. This means privacy violations of your package can be found listed in `
|
241
|
+
Violations exist within the package that makes a violating reference. This means privacy violations of your package can be found listed in `package_todo.yml` files in the packages with the reference to a private constant.
|
242
|
+
|
243
|
+
# Loading Extensions
|
244
|
+
|
245
|
+
You can optionally specify ruby files that you'd like to be loaded with `packwerk` by specifying a `require` directive in `packwerk.yml`:
|
246
|
+
```yml
|
247
|
+
require:
|
248
|
+
- ./path/to/file.rb
|
249
|
+
- my_gem
|
250
|
+
```
|
251
|
+
|
252
|
+
`packwerk` will directly call `require` with these paths.
|
253
|
+
You can prefix local files with a dot to define them relative to `packwerk.yml`, or you can use absolute paths.
|
254
|
+
You can also reference the name of a gem.
|
255
|
+
|
256
|
+
## Examples
|
257
|
+
|
258
|
+
### Custom Offense Formatter
|
259
|
+
|
260
|
+
While `packwerk` ships with its own offense formatter, you may specify a custom one in your configuration file via the `offenses_formatter:` key. Your custom formatter will be used when `bin/packwerk check` is run.
|
261
|
+
|
262
|
+
Firstly, you'll need to create an `OffensesFormatter` class that includes `Packwerk::OffensesFormatter`. You can use [`Packwerk::Formatters::OffensesFormatter`](lib/packwerk/formatters/offenses_formatter.rb) as a point of reference for this. Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
|
263
|
+
```ruby
|
264
|
+
# ./path/to/file.rb
|
265
|
+
class MyOffensesFormatter
|
266
|
+
include Packwerk::OffensesFormatter
|
267
|
+
# implement the `OffensesFormatter` interface
|
268
|
+
|
269
|
+
def identifier
|
270
|
+
'my_offenses_formatter'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
Then in `packwerk.yml`, you can set the `formatter` to the identifier for your class:
|
276
|
+
```yml
|
277
|
+
offenses_formatter: my_offenses_formatter
|
278
|
+
```
|
279
|
+
|
280
|
+
You can also pass in a formatter on the command line:
|
281
|
+
```
|
282
|
+
bin/packwerk check --offenses-formatter=my_offenses_formatter
|
283
|
+
```
|
284
|
+
|
285
|
+
### Custom Checkers
|
286
|
+
|
287
|
+
Packwerk ships with a way to analyze dependencies and also supports custom checkers, such as the privacy checker listed below.
|
288
|
+
|
289
|
+
Custom checkers will allow references to constants to be analyzed in new ways, and for those invalid references to show up as violations in `package_todo.yml`.
|
290
|
+
|
291
|
+
To create a custom checker, you'll first need to create a checker class that includes `Packwerk::Checker`. You can use [`Packwerk::ReferenceChecking::Checkers::DependencyChecker`](lib/packwerk/reference_checking/checkers/dependency_checker.rb) as a point of reference for this. Here is an example:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
# ./path/to/file.rb
|
295
|
+
class MyChecker
|
296
|
+
include Packwerk::Checker
|
297
|
+
# implement the `Checker` interface
|
298
|
+
|
299
|
+
sig { override.returns(String) }
|
300
|
+
def violation_type
|
301
|
+
'my_custom_violation_type'
|
302
|
+
end
|
303
|
+
|
304
|
+
sig { override.params(listed_offense: ReferenceOffense).returns(T::Boolean) }
|
305
|
+
def strict_mode_violation?(listed_offense)
|
306
|
+
# This will allow "strict mode" to be supported in your checker
|
307
|
+
referencing_package = listed_offense.reference.package
|
308
|
+
referencing_package.config["enforce_custom"] == "strict"
|
309
|
+
end
|
310
|
+
|
311
|
+
sig { override.params(reference: Reference).returns(T::Boolean) }
|
312
|
+
def invalid_reference?(reference)
|
313
|
+
# your logic here
|
314
|
+
end
|
315
|
+
|
316
|
+
sig { override.params(reference: Reference).returns(String) }
|
317
|
+
def message(reference)
|
318
|
+
# your message here
|
319
|
+
end
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
|
324
|
+
|
325
|
+
```yml
|
326
|
+
require:
|
327
|
+
- ./path/to/file.rb
|
328
|
+
```
|
329
|
+
|
330
|
+
#### Privacy Checker
|
331
|
+
|
332
|
+
[`packwerk-extensions`](https://github.com/rubyatscale/packwerk-extensions) (originally extracted from `packwerk`) can be used to help define and enforce public API boundaries of a package. See the README.md for more details. To use this, add it to your `Gemfile` and then require it via `packwerk.yml`:
|
333
|
+
```yml
|
334
|
+
require:
|
335
|
+
- packwerk-extensions
|
336
|
+
```
|
337
|
+
|
338
|
+
### Custom Validators
|
339
|
+
|
340
|
+
Similar to checkers, you can define your own validator to be executed when `bin/packwerk validate` is invoked. This can be used to support your custom checker (by specifying permitted keys) or to provide any other validations you want to impose on packages.
|
341
|
+
|
342
|
+
To create a custom validator, you'll first need to create a validator class that includes `Packwerk::Validator`. You can use [`Packwerk::Validators::DependencyValidator`](lib/packwerk/validators/dependency_validator.rb) as a point of reference for this. Here is an example:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
# ./path/to/file.rb
|
346
|
+
class MyValidator
|
347
|
+
include Packwerk::Validator
|
348
|
+
# implement the `Validator` interface
|
349
|
+
|
350
|
+
sig { override.returns(T::Array[String]) }
|
351
|
+
def permitted_keys
|
352
|
+
['enforce_my_custom_checker']
|
353
|
+
end
|
354
|
+
|
355
|
+
sig { override.params(package_set: PackageSet, configuration: Configuration).returns(ApplicationValidator::Result) }
|
356
|
+
def call(package_set, configuration)
|
357
|
+
# your logic here
|
358
|
+
end
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
|
363
|
+
|
364
|
+
```yml
|
365
|
+
require:
|
366
|
+
- ./path/to/file.rb
|
367
|
+
```
|
data/dev.yml
CHANGED
data/exe/packwerk
CHANGED
data/gemfiles/Gemfile-rails-6-1
CHANGED
@@ -7,7 +7,7 @@ gemspec path: ".."
|
|
7
7
|
# Specify the same dependency sources as the application Gemfile
|
8
8
|
|
9
9
|
gem("spring")
|
10
|
-
gem("rails", "~> 6.1
|
10
|
+
gem("rails", "~> 6.1")
|
11
11
|
gem("constant_resolver", require: false)
|
12
12
|
gem("sorbet-runtime", require: false)
|
13
13
|
gem("rubocop-performance", require: false)
|