packwerk 2.3.0 → 3.0.1
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 +3 -8
- data/TROUBLESHOOT.md +2 -25
- data/UPGRADING.md +12 -0
- data/USAGE.md +136 -54
- data/dev.yml +1 -1
- data/exe/packwerk +4 -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 +55 -40
- 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 +26 -18
- 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/package_todo.rb +19 -20
- data/lib/packwerk/parse_run.rb +27 -34
- 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 +9 -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 +34 -15
- 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: 9af43a5e2d624ca5d6b38bfa3c9e65d1d12b1b9cc148a153563ebd55d3b6c82a
|
4
|
+
data.tar.gz: 6dee0dac16640632fb272c72ebe024a36c6a85ba23748b2ab5886279cccd863c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 197f933a60a46aba470ee66a2c04bbabcf1ff3c3386665551dbb7b8241b9357d73e7576de7edd7c322249b8d45cfc0b282616e0f9a5a665d4560964632a66806
|
7
|
+
data.tar.gz: ec903a206f47df277d6187afc5c17a9cfa1591446afb249f2ba62171f7c56ba8687ce403041db451eb8ea0dba842feb26cc7c32e9ff600b93e60e49eafb4584f
|
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.1)
|
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.8
|
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,7 +3,7 @@
|
|
3
3
|
Violations can be [recorded as a deprecation](#recording-violations) or (better!) [eliminated](#eliminating-violations).
|
4
4
|
|
5
5
|
## Recording Violations
|
6
|
-
💡 New
|
6
|
+
💡 New 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?
|
@@ -31,7 +31,7 @@ Violations can be [recorded as a deprecation](#recording-violations) or (better!
|
|
31
31
|
➡️ Stay strong. Eliminate the violation. It is important to build a sustainable business that optimizes for the long-term. Look for advocates such as from mentors or within your team who can help you justify improving system design.
|
32
32
|
|
33
33
|
## Eliminating Violations
|
34
|
-
💡 Dependency
|
34
|
+
💡 Dependency violations are Packwerk's signal that what we've stated about the desired system design (what
|
35
35
|
packages exist and what lives in them, the dependencies in a package.yml, and the public interface in the package's public folder) doesn't match the reality of our system.
|
36
36
|
If what we've stated about our system design doesn't feel right, then the violations won't make sense either! Make sure to think through system design before addressing a violation.
|
37
37
|
|
@@ -40,12 +40,7 @@ If what we've stated about our system design doesn't feel right, then the violat
|
|
40
40
|
|
41
41
|
If not, find a better place for it to live.
|
42
42
|
|
43
|
-
Otherwise, follow the guide for eliminating [
|
44
|
-
|
45
|
-
### Privacy Violations
|
46
|
-
💡 Packwerk thinks something is a privacy violation if you're referencing a constant, class, or module defined in the private implementation (i.e. not the public folder) of another package. We care about these because we want to make sure we only use parts of a package that have been exposed as public API.
|
47
|
-
|
48
|
-
An explicit and implementation-hiding public API is a cornerstone of well-modularized code.
|
43
|
+
Otherwise, follow the guide for eliminating [Dependency Violations](#dependency-violations).
|
49
44
|
|
50
45
|
#### Use Existing Public Interface
|
51
46
|
❔ Does the package you're using expose public API in its public folder that supports your use case?
|
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,11 @@ 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
|
-
|
20
|
-
|
21
|
-
_Note: You cannot specify folders or packages for `bin/packwerk validate` because the command runs for the entire application._
|
17
|
+
_Note: You cannot specify folders or packages for `bin/packwerk validate` and `bin/packwerk update-todo` because the
|
18
|
+
command runs for the entire application._
|
22
19
|
|
23
20
|
![](static/packwerk_check_violation.gif)
|
24
21
|
|
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
22
|
### Package Dependency violation
|
46
23
|
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
24
|
|
data/UPGRADING.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# Upgrading from 2.x to 3.0
|
2
|
+
|
3
|
+
In Packwerk 3.0, we've made two notable changes:
|
4
|
+
|
5
|
+
## Renaming deprecated_references to package_todo
|
6
|
+
|
7
|
+
The `update-deprecations` subcommand has been renamed to `update-todo`. Old `deprecated_references.yml` files will be automatically deleted and replaced with `package_todo.yml` files when you run `update-todo`. This behaviour has been in Packwerk [since 2.3.0](https://github.com/Shopify/packwerk/releases/tag/v2.3.0), and automatic deletion will be removed in the next release.
|
8
|
+
|
9
|
+
### Removal of privacy checking
|
10
|
+
|
11
|
+
Privacy checking via `enforce_privacy` has been removed. Developers are encouraged to focus on leveraging Packwerk for dependency checking. For those who still need privacy checks, please use [Gusto's extension gem](https://github.com/rubyatscale/packwerk-extensions).
|
12
|
+
|
1
13
|
# Upgrading from 1.x to 2.0
|
2
14
|
|
3
15
|
With Packwerk 2.0, we made a few changes to simplify the setup. Updating will require removing some previously necessary files and configuration.
|
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)
|
23
22
|
* [Understanding the package todo file](#understanding-the-package-todo-file)
|
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. Other checking support may be added by extension gems.
|
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,9 +195,10 @@ 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
|
-
When you have a new dependency
|
201
|
+
When you have a new dependency violation, what do you do?
|
231
202
|
|
232
203
|
See: [RESOLVING_VIOLATIONS.md](RESOLVING_VIOLATIONS.md)
|
233
204
|
|
@@ -239,19 +210,12 @@ If so, you will want to stop the bleeding and prevent more violations from occur
|
|
239
210
|
|
240
211
|
bin/packwerk update-todo
|
241
212
|
|
242
|
-
Similar to `bin/packwerk check`, you may also run `bin/packwerk update-todo` on folders or packages:
|
243
|
-
|
244
|
-
bin/packwerk update-todo components/your_package
|
245
|
-
|
246
213
|
![](static/packwerk_update.gif)
|
247
214
|
|
248
|
-
_Note: Changing dependencies or enabling dependencies will not require a full update of the codebase, only the package that changed. On the other hand, changing or enabling privacy will require a full update of the codebase._
|
249
|
-
|
250
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
|
|
254
|
-
|
255
219
|
### Understanding the package todo file
|
256
220
|
|
257
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.
|
@@ -271,7 +235,125 @@ 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
|
-
* `dependency` - type of violation,
|
238
|
+
* `dependency` - type of violation, typically dependency
|
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.
|
241
|
+
Violations exist within the package that makes a violating reference.
|
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 from extension gems.
|
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
|
+
### Custom Validators
|
331
|
+
|
332
|
+
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.
|
333
|
+
|
334
|
+
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:
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
# ./path/to/file.rb
|
338
|
+
class MyValidator
|
339
|
+
include Packwerk::Validator
|
340
|
+
# implement the `Validator` interface
|
341
|
+
|
342
|
+
sig { override.returns(T::Array[String]) }
|
343
|
+
def permitted_keys
|
344
|
+
['enforce_my_custom_checker']
|
345
|
+
end
|
346
|
+
|
347
|
+
sig { override.params(package_set: PackageSet, configuration: Configuration).returns(ApplicationValidator::Result) }
|
348
|
+
def call(package_set, configuration)
|
349
|
+
# your logic here
|
350
|
+
end
|
351
|
+
end
|
352
|
+
```
|
353
|
+
|
354
|
+
Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
|
355
|
+
|
356
|
+
```yml
|
357
|
+
require:
|
358
|
+
- ./path/to/file.rb
|
359
|
+
```
|
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)
|