packwerk 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +36 -8
  3. data/.ruby-version +1 -1
  4. data/Gemfile.lock +12 -7
  5. data/README.md +4 -2
  6. data/RESOLVING_VIOLATIONS.md +3 -8
  7. data/Rakefile +10 -1
  8. data/TROUBLESHOOT.md +2 -3
  9. data/UPGRADING.md +12 -0
  10. data/USAGE.md +11 -13
  11. data/dev.yml +1 -1
  12. data/exe/packwerk +4 -1
  13. data/lib/packwerk/application_validator.rb +3 -0
  14. data/lib/packwerk/checker.rb +13 -4
  15. data/lib/packwerk/cli.rb +14 -177
  16. data/lib/packwerk/commands/base_command.rb +69 -0
  17. data/lib/packwerk/commands/check_command.rb +60 -0
  18. data/lib/packwerk/commands/help_command.rb +33 -0
  19. data/lib/packwerk/commands/init_command.rb +42 -0
  20. data/lib/packwerk/commands/lazy_loaded_entry.rb +37 -0
  21. data/lib/packwerk/commands/update_todo_command.rb +60 -0
  22. data/lib/packwerk/commands/uses_parse_run.rb +92 -0
  23. data/lib/packwerk/commands/validate_command.rb +46 -0
  24. data/lib/packwerk/commands/version_command.rb +18 -0
  25. data/lib/packwerk/commands.rb +54 -0
  26. data/lib/packwerk/configuration.rb +4 -1
  27. data/lib/packwerk/file_processor.rb +12 -1
  28. data/lib/packwerk/formatters/default_offenses_formatter.rb +3 -3
  29. data/lib/packwerk/formatters/progress_formatter.rb +11 -0
  30. data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
  31. data/lib/packwerk/offense_collection.rb +32 -12
  32. data/lib/packwerk/offenses_formatter.rb +13 -4
  33. data/lib/packwerk/package_todo.rb +89 -69
  34. data/lib/packwerk/parse_run.rb +42 -82
  35. data/lib/packwerk/spring_command.rb +0 -2
  36. data/lib/packwerk/validator.rb +18 -4
  37. data/lib/packwerk/version.rb +1 -1
  38. data/lib/packwerk.rb +4 -28
  39. data/sorbet/rbi/gems/parser@3.2.2.0.rbi +7250 -0
  40. metadata +14 -5
  41. data/lib/packwerk/cli/result.rb +0 -11
  42. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +0 -9029
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11206a06e6ac99c1fb8ef4cdefbe334133317d0599fce6480d4dfe10f694c747
4
- data.tar.gz: 606778530b8a872d331e5f331b9c6e14cb37ff6e0a5e13247bb0aebe1ca9e0cc
3
+ metadata.gz: d72fbde4259baa5a5e7f8bc2ca4406d9bc6a835051efafefc23ef6ef3ae13f32
4
+ data.tar.gz: 65bff304e2be348e16800b0d4f65ab33a1b5fb84ab15ba495e03904d099be4a0
5
5
  SHA512:
6
- metadata.gz: 66166a9c683483fda99341adc79c807c40f8190c349c8650b8646810e06d11f9b4728e2e938ef0abf640967d0764d78b66e956aba3bdaef830c4a15253711542
7
- data.tar.gz: c1cce5df16246f1561cd0954abca4685e21adcaf52d3f6d164acd776f9466a0ad276a65b9a2f5cc9e358b058fcdc2cef265c2b3948043f9f3e35b1d4798ec5e9
6
+ metadata.gz: e25488e4af51932d503d4120e458fe92bc4ae2945ca8009219e01f4f2e7756e0430a0d9a6176f01d864d55644f262a06fb88494234b43aae43ba254437d7fb49
7
+ data.tar.gz: 2c821f3e11a16c52d84f7f4e68f01e788e95d5723fd77305f69e15aba07f5f1f81ea43e9c60b6c00ca4503df036eb2b3ea0e0ae1378f0c584d7252614064f9d9
@@ -1,6 +1,11 @@
1
1
  name: CI
2
2
 
3
- on: [push, pull_request]
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - '*-stable'
8
+ pull_request:
4
9
 
5
10
  jobs:
6
11
  tests:
@@ -12,10 +17,10 @@ jobs:
12
17
  - gemfiles/Gemfile-rails-6-0
13
18
  - gemfiles/Gemfile-rails-6-1
14
19
  ruby:
15
- - 2.7
16
- - 3.0
17
- - 3.1
18
- - 3.2
20
+ - "2.7"
21
+ - "3.0"
22
+ - "3.1"
23
+ - "3.2"
19
24
  env:
20
25
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
21
26
  name: "Tests: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}"
@@ -27,7 +32,30 @@ jobs:
27
32
  ruby-version: ${{ matrix.ruby }}
28
33
  bundler-cache: true
29
34
  - name: Run tests
30
- run: bin/rake
35
+ run: bin/rake test
36
+ loading-tests:
37
+ runs-on: ubuntu-latest
38
+ strategy:
39
+ matrix:
40
+ gemfile:
41
+ - Gemfile
42
+ ruby:
43
+ - "2.7"
44
+ - "3.0"
45
+ - "3.1"
46
+ - "3.2"
47
+ env:
48
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
49
+ name: "Loading Tests: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}"
50
+ steps:
51
+ - uses: actions/checkout@v2
52
+ - name: Set up Ruby ${{ matrix.ruby }}
53
+ uses: ruby/setup-ruby@v1
54
+ with:
55
+ ruby-version: ${{ matrix.ruby }}
56
+ bundler-cache: true
57
+ - name: Run tests
58
+ run: bin/rake test:loading
31
59
  lint:
32
60
  runs-on: ubuntu-latest
33
61
  name: Lint
@@ -36,7 +64,7 @@ jobs:
36
64
  - name: Set up Ruby
37
65
  uses: ruby/setup-ruby@v1
38
66
  with:
39
- ruby-version: 3.1
67
+ ruby-version: "3.1"
40
68
  bundler-cache: true
41
69
  - name: Run style checks
42
70
  run: bin/rubocop
@@ -48,7 +76,7 @@ jobs:
48
76
  - name: Set up Ruby
49
77
  uses: ruby/setup-ruby@v1
50
78
  with:
51
- ruby-version: 3.1
79
+ ruby-version: "3.1"
52
80
  # bundler-cache: true
53
81
  - name: Run static type checks
54
82
  run: |
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.1
1
+ 3.2.2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- packwerk (3.0.0)
4
+ packwerk (3.1.0)
5
5
  activesupport (>= 6.0)
6
6
  ast
7
7
  better_html
@@ -60,23 +60,27 @@ GEM
60
60
  method_source (>= 0.6.7)
61
61
  rake (>= 0.9.2.2)
62
62
  method_source (1.0.0)
63
- mini_portile2 (2.8.0)
63
+ mini_portile2 (2.8.4)
64
64
  minitest (5.16.2)
65
65
  minitest-focus (1.3.1)
66
66
  minitest (>= 4, < 6)
67
67
  mocha (1.14.0)
68
68
  netrc (0.11.0)
69
- nokogiri (1.13.8)
70
- mini_portile2 (~> 2.8.0)
69
+ nokogiri (1.15.3)
70
+ mini_portile2 (~> 2.8.2)
71
+ racc (~> 1.4)
72
+ nokogiri (1.15.3-x86_64-darwin)
73
+ racc (~> 1.4)
74
+ nokogiri (1.15.3-x86_64-linux)
71
75
  racc (~> 1.4)
72
76
  parallel (1.22.1)
73
- parser (3.1.2.1)
77
+ parser (3.2.2.0)
74
78
  ast (~> 2.4.1)
75
79
  prettier_print (0.1.0)
76
80
  pry (0.14.1)
77
81
  coderay (~> 1.1)
78
82
  method_source (~> 1.0)
79
- racc (1.6.0)
83
+ racc (1.7.1)
80
84
  rack (2.2.4)
81
85
  rack-test (2.0.2)
82
86
  rack (>= 1.3)
@@ -178,6 +182,7 @@ PLATFORMS
178
182
  ruby
179
183
  x86_64-darwin
180
184
  x86_64-darwin-20
185
+ x86_64-linux
181
186
 
182
187
  DEPENDENCIES
183
188
  byebug
@@ -198,4 +203,4 @@ DEPENDENCIES
198
203
  zeitwerk
199
204
 
200
205
  BUNDLED WITH
201
- 2.4.7
206
+ 2.4.8
data/README.md CHANGED
@@ -58,10 +58,12 @@ Read [USAGE.md](USAGE.md) for usage once Packwerk is installed on your project.
58
58
  Various third parties have built tooling on top of packwerk. Here's a selection of some that might prove useful:
59
59
 
60
60
  - https://github.com/bellroy/graphwerk draws a graph of your package dependencies
61
- - https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
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.
61
+ - https://github.com/rubyatscale/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
62
+ - https://github.com/vinted/packwerk-intellij integrates packwerk into RubyMine so you can see violations right in your editor
63
+ - https://github.com/rubyatscale/packs-rails sets up Rails autoloading, as well as `rspec` and `FactoryBot` integration, for packages arranged in a flat list. packs-rails is quite convenient, but for autoloading we recommend to use `Rails::Engine`s instead.
63
64
  - https://github.com/rubyatscale/danger-packwerk integrates packwerk with [danger.systems](https://danger.systems) to provide packwerk feedback as Github inline PR comments
64
65
  - 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.
66
+ - https://github.com/alexevanczuk/packs is a Rust implementation of packwerk that has experimental support for non-Rails, non-Zeitwerk applications.
65
67
 
66
68
  ## Development
67
69
 
@@ -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/Rakefile CHANGED
@@ -6,7 +6,16 @@ require "rake/testtask"
6
6
  Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
9
+ t.test_files = FileList["test/**/*_test.rb"].reject do |file|
10
+ file.include?("test/loading/")
11
+ end
12
+ t.warning = false
13
+ end
14
+
15
+ Rake::TestTask.new("test:loading") do |t|
16
+ t.libs << "test"
17
+ t.libs << "lib"
18
+ t.test_files = FileList["test/loading/**/*_test.rb"]
10
19
  t.warning = false
11
20
  end
12
21
 
data/TROUBLESHOOT.md CHANGED
@@ -14,9 +14,8 @@ You can specify folders or packages in Packwerk commands for a shorter run time:
14
14
 
15
15
  bin/packwerk check components/your_package
16
16
 
17
- bin/packwerk update-todo components/your_package
18
-
19
- _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._
20
19
 
21
20
  ![](static/packwerk_check_violation.gif)
22
21
 
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
@@ -144,7 +144,7 @@ Example:
144
144
 
145
145
  ## Types of boundary checks
146
146
 
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`.
147
+ Packwerk ships with dependency boundary checking only. Other checking support may be added by extension gems.
148
148
 
149
149
  #### Enforcing dependency boundary
150
150
 
@@ -172,6 +172,8 @@ Then, when you run `bin/packwerk check`, new violations will cause the following
172
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
173
  ```
174
174
 
175
+ Once the `strict` mode is enabled on a package, running `bin/packwerk update-todo` will not add new violations in the package_todo.yml file and the command will return an error.
176
+
175
177
  ## Checking for violations
176
178
 
177
179
  After enforcing the boundary checks for a package, you may execute:
@@ -188,6 +190,10 @@ You can also specify packages for a shorter run time. When checking against pack
188
190
 
189
191
  bin/packwerk check --packages=components/your_package,components/your_other_package
190
192
 
193
+ Using the following command line option you can also enable or disable parallel processing. It is enabled by default.
194
+
195
+ bin/packwerk check --[no-]parallel
196
+
191
197
  ![](static/packwerk_check.gif)
192
198
 
193
199
  In order to keep the package system valid at each version of the application, we recommend running `bin/packwerk check` in your CI pipeline.
@@ -198,7 +204,7 @@ See: [TROUBLESHOOT.md - Sample violations](TROUBLESHOOT.md#Sample-violations)
198
204
 
199
205
  ### Understanding how to respond to new violations
200
206
 
201
- When you have a new dependency or privacy violation, what do you do?
207
+ When you have a new dependency violation, what do you do?
202
208
 
203
209
  See: [RESOLVING_VIOLATIONS.md](RESOLVING_VIOLATIONS.md)
204
210
 
@@ -235,10 +241,10 @@ Above is an example of a constant violation entry in `package_todo.yml`.
235
241
 
236
242
  * `components/merchant` - package where the constant violation is found
237
243
  * `::Checkouts::Core::CheckoutId` - violated constant in question
238
- * `dependency` - type of violation, either dependency or privacy
244
+ * `dependency` - type of violation, typically dependency
239
245
  * `components/merchant/app/public/merchant/generate_order.rb` - path to the file containing the violated constant
240
246
 
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.
247
+ Violations exist within the package that makes a violating reference.
242
248
 
243
249
  # Loading Extensions
244
250
 
@@ -284,7 +290,7 @@ bin/packwerk check --offenses-formatter=my_offenses_formatter
284
290
 
285
291
  ### Custom Checkers
286
292
 
287
- Packwerk ships with a way to analyze dependencies and also supports custom checkers, such as the privacy checker listed below.
293
+ Packwerk ships with a way to analyze dependencies and also supports custom checkers from extension gems.
288
294
 
289
295
  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
296
 
@@ -327,14 +333,6 @@ require:
327
333
  - ./path/to/file.rb
328
334
  ```
329
335
 
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
336
  ### Custom Validators
339
337
 
340
338
  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.
data/dev.yml CHANGED
@@ -3,7 +3,7 @@ name: packwerk
3
3
  type: ruby
4
4
 
5
5
  up:
6
- - ruby: 3.2.1
6
+ - ruby: 3.2.2
7
7
  - bundler
8
8
 
9
9
  commands:
data/exe/packwerk CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "packwerk/disable_sorbet"
4
+ unless defined?(Spring)
5
+ require "packwerk/disable_sorbet"
6
+ end
7
+
5
8
  require "packwerk"
6
9
 
7
10
  # Needs to be run in test environment in order to have test helper paths available in the autoload paths
@@ -11,6 +11,9 @@ module Packwerk
11
11
  class ApplicationValidator
12
12
  include Validator
13
13
  extend T::Sig
14
+ extend ActiveSupport::Autoload
15
+
16
+ autoload :Helpers
14
17
 
15
18
  sig { params(package_set: PackageSet, configuration: Configuration).returns(Validator::Result) }
16
19
  def check_all(package_set, configuration)
@@ -13,14 +13,13 @@ module Packwerk
13
13
 
14
14
  sig { params(base: Class).void }
15
15
  def included(base)
16
- @checkers ||= T.let(@checkers, T.nilable(T::Array[Class]))
17
- @checkers ||= []
18
- @checkers << base
16
+ checkers << base
19
17
  end
20
18
 
21
19
  sig { returns(T::Array[Checker]) }
22
20
  def all
23
- T.unsafe(@checkers).map(&:new)
21
+ load_defaults
22
+ T.cast(checkers.map(&:new), T::Array[Checker])
24
23
  end
25
24
 
26
25
  sig { params(violation_type: String).returns(Checker) }
@@ -30,6 +29,16 @@ module Packwerk
30
29
 
31
30
  private
32
31
 
32
+ sig { void }
33
+ def load_defaults
34
+ require("packwerk/reference_checking/checkers/dependency_checker")
35
+ end
36
+
37
+ sig { returns(T::Array[Class]) }
38
+ def checkers
39
+ @checkers ||= T.let([], T.nilable(T::Array[Class]))
40
+ end
41
+
33
42
  sig { params(name: String).returns(Checker) }
34
43
  def checker_by_violation_type(name)
35
44
  @checker_by_violation_type ||= T.let(Checker.all.to_h do |checker|
data/lib/packwerk/cli.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "optparse"
5
-
6
4
  module Packwerk
7
5
  # A command-line interface to Packwerk.
8
6
  class Cli
@@ -46,184 +44,23 @@ module Packwerk
46
44
 
47
45
  sig { params(args: T::Array[String]).returns(T::Boolean) }
48
46
  def execute_command(args)
49
- subcommand = args.shift
50
- case subcommand
51
- when "init"
52
- init
53
- when "check"
54
- output_result(parse_run(args).check)
55
- when "update-todo", "update"
56
- output_result(parse_run(args).update_todo)
57
- when "validate"
58
- validate(args)
59
- when "version"
60
- @out.puts(Packwerk::VERSION)
61
- true
62
- when nil, "help"
63
- usage
64
- else
65
- @err_out.puts(
66
- "'#{subcommand}' is not a packwerk command. See `packwerk help`."
67
- )
68
- false
69
- end
70
- end
71
-
72
- private
73
-
74
- sig { returns(T::Boolean) }
75
- def init
76
- @out.puts("📦 Initializing Packwerk...")
77
-
78
- generate_configs
79
- end
80
-
81
- sig { returns(T::Boolean) }
82
- def generate_configs
83
- configuration_file = Generators::ConfigurationFile.generate(
84
- root: @configuration.root_path,
85
- out: @out
86
- )
87
-
88
- root_package = Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
89
-
90
- success = configuration_file && root_package
91
-
92
- result = if success
93
- <<~EOS
94
-
95
- 🎉 Packwerk is ready to be used. You can start defining packages and run `bin/packwerk check`.
96
- For more information on how to use Packwerk, see: https://github.com/Shopify/packwerk/blob/main/USAGE.md
97
- EOS
98
- else
99
- <<~EOS
100
-
101
- ⚠️ Packwerk is not ready to be used.
102
- Please check output and refer to https://github.com/Shopify/packwerk/blob/main/USAGE.md for more information.
103
- EOS
104
- end
105
-
106
- @out.puts(result)
107
- success
108
- end
109
-
110
- sig { returns(T::Boolean) }
111
- def usage
112
- @err_out.puts(<<~USAGE)
113
- Usage: #{$PROGRAM_NAME} <subcommand>
114
-
115
- Subcommands:
116
- init - set up packwerk
117
- check - run all checks
118
- update-todo - update package_todo.yml files
119
- validate - verify integrity of packwerk and package configuration
120
- version - output packwerk version
121
- help - display help information about packwerk
122
- USAGE
123
- true
124
- end
125
-
126
- sig { params(result: Result).returns(T::Boolean) }
127
- def output_result(result)
128
- @out.puts
129
- @out.puts(result.message)
130
- result.status
131
- end
132
-
133
- sig do
134
- params(
135
- relative_file_paths: T::Array[String],
136
- ignore_nested_packages: T::Boolean
137
- ).returns(FilesForProcessing)
138
- end
139
- def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
140
- files_for_processing = FilesForProcessing.fetch(
141
- relative_file_paths: relative_file_paths,
142
- ignore_nested_packages: ignore_nested_packages,
143
- configuration: @configuration
144
- )
145
- @out.puts(<<~MSG.squish) if files_for_processing.files.empty?
146
- No files found or given.
147
- Specify files or check the include and exclude glob in the config file.
148
- MSG
149
-
150
- files_for_processing
151
- end
152
-
153
- sig { params(_paths: T::Array[String]).returns(T::Boolean) }
154
- def validate(_paths)
155
- result = T.let(nil, T.nilable(Validator::Result))
156
-
157
- @progress_formatter.started_validation do
158
- result = validator.check_all(package_set, @configuration)
159
-
160
- list_validation_errors(result)
161
- end
162
-
163
- T.must(result).ok?
164
- end
165
-
166
- sig { returns(ApplicationValidator) }
167
- def validator
168
- ApplicationValidator.new
169
- end
170
-
171
- sig { returns(PackageSet) }
172
- def package_set
173
- PackageSet.load_all_from(
174
- @configuration.root_path,
175
- package_pathspec: @configuration.package_paths
176
- )
177
- end
178
-
179
- sig { params(result: Validator::Result).void }
180
- def list_validation_errors(result)
181
- @out.puts
182
- if result.ok?
183
- @out.puts("Validation successful 🎉")
47
+ command = args.shift || "help"
48
+ command_class = Commands.for(command)
49
+
50
+ if command_class
51
+ command_class.new(
52
+ args,
53
+ configuration: @configuration,
54
+ out: @out,
55
+ err_out: @err_out,
56
+ progress_formatter: @progress_formatter,
57
+ offenses_formatter: @offenses_formatter,
58
+ ).run
184
59
  else
185
- @out.puts("Validation failed ")
186
- @out.puts
187
- @out.puts(result.error_value)
188
- end
189
- end
190
-
191
- sig { params(args: T::Array[String]).returns(ParseRun) }
192
- def parse_run(args)
193
- relative_file_paths = T.let([], T::Array[String])
194
- ignore_nested_packages = nil
195
- formatter = @offenses_formatter
60
+ @err_out.puts("'#{command}' is not a packwerk command. See `packwerk help`.",)
196
61
 
197
- if args.any? { |arg| arg.include?("--packages") }
198
- OptionParser.new do |parser|
199
- parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
200
- relative_file_paths = p
201
- end
202
- end.parse!(args)
203
- ignore_nested_packages = true
204
- else
205
- relative_file_paths = args
206
- ignore_nested_packages = false
207
- end
208
-
209
- if args.any? { |arg| arg.include?("--offenses-formatter") }
210
- OptionParser.new do |parser|
211
- parser.on("--offenses-formatter=FORMATTER", String,
212
- "identifier of offenses formatter to use") do |formatter_identifier|
213
- formatter = OffensesFormatter.find(formatter_identifier)
214
- end
215
- end.parse!(args)
62
+ false
216
63
  end
217
-
218
- files_for_processing = fetch_files_to_process(relative_file_paths, ignore_nested_packages)
219
-
220
- ParseRun.new(
221
- relative_file_set: files_for_processing.files,
222
- file_set_specified: files_for_processing.files_specified?,
223
- configuration: @configuration,
224
- progress_formatter: @progress_formatter,
225
- offenses_formatter: formatter
226
- )
227
64
  end
228
65
  end
229
66
  end
@@ -0,0 +1,69 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class BaseCommand
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ abstract!
10
+
11
+ @description = T.let("", String)
12
+
13
+ class << self
14
+ extend T::Sig
15
+
16
+ sig { params(description: T.nilable(String)).returns(String) }
17
+ def description(description = nil)
18
+ if description
19
+ @description = description
20
+ else
21
+ @description
22
+ end
23
+ end
24
+ end
25
+
26
+ sig do
27
+ params(
28
+ args: T::Array[String],
29
+ configuration: Configuration,
30
+ out: T.any(StringIO, IO),
31
+ err_out: T.any(StringIO, IO),
32
+ progress_formatter: Formatters::ProgressFormatter,
33
+ offenses_formatter: OffensesFormatter,
34
+ ).void
35
+ end
36
+ def initialize(args, configuration:, out:, err_out:, progress_formatter:, offenses_formatter:)
37
+ @args = args
38
+ @configuration = configuration
39
+ @out = out
40
+ @err_out = err_out
41
+ @progress_formatter = progress_formatter
42
+ @offenses_formatter = offenses_formatter
43
+ end
44
+
45
+ sig { abstract.returns(T::Boolean) }
46
+ def run; end
47
+
48
+ private
49
+
50
+ sig { returns(T::Array[String]) }
51
+ attr_reader :args
52
+
53
+ sig { returns(Configuration) }
54
+ attr_reader :configuration
55
+
56
+ sig { returns(T.any(StringIO, IO)) }
57
+ attr_reader :out
58
+
59
+ sig { returns(T.any(StringIO, IO)) }
60
+ attr_reader :err_out
61
+
62
+ sig { returns(Formatters::ProgressFormatter) }
63
+ attr_reader :progress_formatter
64
+
65
+ sig { returns(OffensesFormatter) }
66
+ attr_reader :offenses_formatter
67
+ end
68
+ end
69
+ end