packwerk 2.3.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +5 -95
  6. data/README.md +2 -7
  7. data/RESOLVING_VIOLATIONS.md +3 -8
  8. data/TROUBLESHOOT.md +2 -25
  9. data/UPGRADING.md +12 -0
  10. data/USAGE.md +136 -54
  11. data/dev.yml +1 -1
  12. data/exe/packwerk +4 -0
  13. data/gemfiles/Gemfile-rails-6-1 +1 -1
  14. data/lib/packwerk/application_validator.rb +54 -285
  15. data/lib/packwerk/association_inspector.rb +2 -0
  16. data/lib/packwerk/cache.rb +6 -5
  17. data/lib/packwerk/checker.rb +54 -0
  18. data/lib/packwerk/cli/result.rb +11 -0
  19. data/lib/packwerk/cli.rb +55 -40
  20. data/lib/packwerk/configuration.rb +61 -40
  21. data/lib/packwerk/const_node_inspector.rb +2 -0
  22. data/lib/packwerk/constant_context.rb +8 -0
  23. data/lib/packwerk/constant_discovery.rb +5 -6
  24. data/lib/packwerk/constant_name_inspector.rb +2 -0
  25. data/lib/packwerk/disable_sorbet.rb +41 -0
  26. data/lib/packwerk/extension_loader.rb +24 -0
  27. data/lib/packwerk/file_processor.rb +3 -1
  28. data/lib/packwerk/files_for_processing.rb +25 -12
  29. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  30. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  31. data/lib/packwerk/generators/configuration_file.rb +7 -2
  32. data/lib/packwerk/generators/root_package.rb +5 -1
  33. data/lib/packwerk/generators/templates/package.yml +0 -10
  34. data/lib/packwerk/graph.rb +10 -2
  35. data/lib/packwerk/node.rb +1 -1
  36. data/lib/packwerk/node_helpers.rb +14 -7
  37. data/lib/packwerk/node_processor.rb +2 -0
  38. data/lib/packwerk/node_processor_factory.rb +6 -4
  39. data/lib/packwerk/node_visitor.rb +10 -1
  40. data/lib/packwerk/offense_collection.rb +26 -18
  41. data/lib/packwerk/offenses_formatter.rb +59 -2
  42. data/lib/packwerk/package.rb +7 -35
  43. data/lib/packwerk/package_set.rb +1 -1
  44. data/lib/packwerk/package_todo.rb +19 -20
  45. data/lib/packwerk/parse_run.rb +27 -34
  46. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  47. data/lib/packwerk/parsers/erb.rb +23 -4
  48. data/lib/packwerk/parsers/factory.rb +11 -2
  49. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  50. data/lib/packwerk/parsers/ruby.rb +13 -3
  51. data/lib/packwerk/parsers.rb +6 -2
  52. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  53. data/lib/packwerk/reference.rb +7 -1
  54. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  55. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  56. data/lib/packwerk/reference_extractor.rb +24 -12
  57. data/lib/packwerk/reference_offense.rb +2 -2
  58. data/lib/packwerk/run_context.rb +7 -10
  59. data/lib/packwerk/spring_command.rb +9 -2
  60. data/lib/packwerk/unresolved_reference.rb +9 -1
  61. data/lib/packwerk/validator/result.rb +18 -0
  62. data/lib/packwerk/validator.rb +90 -0
  63. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  64. data/lib/packwerk/version.rb +1 -1
  65. data/lib/packwerk.rb +64 -26
  66. data/packwerk.gemspec +4 -2
  67. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  68. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  69. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  70. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  71. data/sorbet/rbi/shims/parser.rbi +13 -0
  72. metadata +34 -15
  73. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  74. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  75. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  76. data/lib/packwerk/result.rb +0 -9
  77. data/lib/packwerk/sanity_checker.rb +0 -8
  78. data/lib/packwerk/violation_type.rb +0 -11
  79. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  80. 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: 94a6e793dd2c071a7bfb9a739fb6aaf2e07b0d2183bf743afb38da5e3847f0e2
4
- data.tar.gz: dd779094dad5572746476d25d63e26f29819cf0b57519f73a4f693610cfd238e
3
+ metadata.gz: 9af43a5e2d624ca5d6b38bfa3c9e65d1d12b1b9cc148a153563ebd55d3b6c82a
4
+ data.tar.gz: 6dee0dac16640632fb272c72ebe024a36c6a85ba23748b2ab5886279cccd863c
5
5
  SHA512:
6
- metadata.gz: 9ff4d2a2aa7b6cb34e59362321ea3fb22826d10430addeef1b6143408d1a1b510ff8915105f2e74e9173334dfcfbd0a76610ea00242a2720c6feb9c75b53556f
7
- data.tar.gz: 7ed4cbe3376821d54a5a58e2bc25ac3baf1f03a84928e027c0e1e1f39d52a14df4e18cc8d9ecfa50de3756507f46c0e9687d04464d3ec149037a7fdecb8eed0e
6
+ metadata.gz: 197f933a60a46aba470ee66a2c04bbabcf1ff3c3386665551dbb7b8241b9357d73e7576de7edd7c322249b8d45cfc0b282616e0f9a5a665d4560964632a66806
7
+ data.tar.gz: ec903a206f47df277d6187afc5c17a9cfa1591446afb249f2ba62171f7c56ba8687ce403041db451eb8ea0dba842feb26cc7c32e9ff600b93e60e49eafb4584f
@@ -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
- exclude:
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.0.0
1
+ 3.2.1
data/Gemfile CHANGED
@@ -8,7 +8,6 @@ gemspec
8
8
  # Specify the same dependency sources as the application Gemfile
9
9
 
10
10
  gem("spring")
11
- gem("rails")
12
11
  gem("constant_resolver", require: false)
13
12
  gem("rubocop-performance", require: false)
14
13
  gem("rubocop-sorbet", require: false)
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- packwerk (2.3.0)
5
- activesupport (>= 5.2)
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.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
- rails
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.3.5
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.6 and above.
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
 
@@ -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 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.
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 and privacy violations are Packwerk's signal that what we've stated about the desired system design (what
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 [Privacy Violations](#privacy-violations) or [Dependency Violations](#dependency-violations).
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
- bin/packwerk update-todo components/your_package
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 can perform two types of boundary checks: privacy and dependency.
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 or privacy violation, what do you do?
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, either dependency or privacy
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. 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.
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
@@ -3,7 +3,7 @@ name: packwerk
3
3
  type: ruby
4
4
 
5
5
  up:
6
- - ruby: 3.0.0
6
+ - ruby: 3.2.1
7
7
  - bundler
8
8
 
9
9
  commands:
data/exe/packwerk CHANGED
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ unless defined?(Spring)
5
+ require "packwerk/disable_sorbet"
6
+ end
7
+
4
8
  require "packwerk"
5
9
 
6
10
  # Needs to be run in test environment in order to have test helper paths available in the autoload paths
@@ -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.0")
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)