packwerk 3.0.0 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/exe/packwerk +4 -1
  4. data/lib/packwerk/application_validator.rb +3 -0
  5. data/lib/packwerk/association_inspector.rb +17 -4
  6. data/lib/packwerk/checker.rb +16 -7
  7. data/lib/packwerk/cli.rb +14 -177
  8. data/lib/packwerk/commands/base_command.rb +69 -0
  9. data/lib/packwerk/commands/check_command.rb +62 -0
  10. data/lib/packwerk/commands/help_command.rb +33 -0
  11. data/lib/packwerk/commands/init_command.rb +42 -0
  12. data/lib/packwerk/commands/lazy_loaded_entry.rb +37 -0
  13. data/lib/packwerk/commands/update_todo_command.rb +60 -0
  14. data/lib/packwerk/commands/uses_parse_run.rb +92 -0
  15. data/lib/packwerk/commands/validate_command.rb +46 -0
  16. data/lib/packwerk/commands/version_command.rb +18 -0
  17. data/lib/packwerk/commands.rb +54 -0
  18. data/lib/packwerk/configuration.rb +8 -1
  19. data/lib/packwerk/const_node_inspector.rb +2 -2
  20. data/lib/packwerk/constant_name_inspector.rb +2 -2
  21. data/lib/packwerk/file_processor.rb +12 -1
  22. data/lib/packwerk/formatters/default_offenses_formatter.rb +3 -3
  23. data/lib/packwerk/formatters/progress_formatter.rb +11 -0
  24. data/lib/packwerk/generators/templates/package.yml +2 -2
  25. data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
  26. data/lib/packwerk/offense_collection.rb +32 -12
  27. data/lib/packwerk/offenses_formatter.rb +14 -5
  28. data/lib/packwerk/package.rb +1 -1
  29. data/lib/packwerk/package_todo.rb +86 -69
  30. data/lib/packwerk/parse_run.rb +42 -82
  31. data/lib/packwerk/parsers/factory.rb +3 -3
  32. data/lib/packwerk/parsers/ruby.rb +9 -2
  33. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +3 -3
  34. data/lib/packwerk/reference_extractor.rb +29 -1
  35. data/lib/packwerk/reference_offense.rb +1 -1
  36. data/lib/packwerk/run_context.rb +15 -4
  37. data/lib/packwerk/spring_command.rb +0 -2
  38. data/lib/packwerk/validator.rb +19 -5
  39. data/lib/packwerk/version.rb +1 -1
  40. data/lib/packwerk.rb +5 -28
  41. data/sorbet/config +1 -0
  42. data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +3280 -3450
  43. data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +2322 -1782
  44. data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +2654 -3268
  45. data/sorbet/rbi/gems/ast@2.4.2.rbi +535 -6
  46. data/sorbet/rbi/gems/better_html@2.0.1.rbi +529 -0
  47. data/sorbet/rbi/gems/builder@3.2.4.rbi +4 -4
  48. data/sorbet/rbi/gems/byebug@11.1.3.rbi +32 -4
  49. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +1750 -1840
  50. data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +15 -15
  51. data/sorbet/rbi/gems/crass@1.0.6.rbi +489 -5
  52. data/sorbet/rbi/gems/erubi@1.11.0.rbi +24 -21
  53. data/sorbet/rbi/gems/i18n@1.12.0.rbi +395 -395
  54. data/sorbet/rbi/gems/json@2.6.2.rbi +70 -77
  55. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +1 -1
  56. data/sorbet/rbi/gems/loofah@2.18.0.rbi +134 -134
  57. data/sorbet/rbi/gems/m@1.6.0.rbi +60 -60
  58. data/sorbet/rbi/gems/method_source@1.1.0.rbi +303 -0
  59. data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +22 -28
  60. data/sorbet/rbi/gems/minitest@5.16.2.rbi +384 -396
  61. data/sorbet/rbi/gems/mocha@1.14.0.rbi +589 -589
  62. data/sorbet/rbi/gems/netrc@0.11.0.rbi +37 -32
  63. data/sorbet/rbi/gems/{nokogiri@1.13.8.rbi → nokogiri@1.15.3.rbi} +1869 -1030
  64. data/sorbet/rbi/gems/{parallel@1.22.1.rbi → parallel@1.24.0.rbi} +85 -82
  65. data/sorbet/rbi/gems/parser@3.3.1.0.rbi +7320 -0
  66. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +1 -1
  67. data/sorbet/rbi/gems/prism@0.27.0.rbi +36983 -0
  68. data/sorbet/rbi/gems/{racc@1.6.0.rbi → racc@1.7.1.rbi} +42 -33
  69. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +148 -338
  70. data/sorbet/rbi/gems/rack@2.2.4.rbi +1079 -1130
  71. data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +354 -22
  72. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +113 -259
  73. data/sorbet/rbi/gems/railties@7.0.3.1.rbi +642 -638
  74. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +109 -99
  75. data/sorbet/rbi/gems/rake@13.0.6.rbi +714 -599
  76. data/sorbet/rbi/gems/{rbi@0.0.15.rbi → rbi@0.1.12.rbi} +865 -801
  77. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +853 -870
  78. data/sorbet/rbi/gems/rexml@3.2.5.rbi +480 -477
  79. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +1621 -1622
  80. data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +507 -526
  81. data/sorbet/rbi/gems/rubocop-shopify@2.9.0.rbi +1 -1
  82. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +186 -203
  83. data/sorbet/rbi/gems/rubocop@1.34.1.rbi +8126 -8367
  84. data/sorbet/rbi/gems/{ruby-lsp@0.2.1.rbi → ruby-lsp@0.2.3.rbi} +2 -2
  85. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1235 -4
  86. data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +90 -90
  87. data/sorbet/rbi/gems/spoom@1.3.2.rbi +4420 -0
  88. data/sorbet/rbi/gems/spring@4.0.0.rbi +104 -104
  89. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +1 -1
  90. data/sorbet/rbi/gems/{tapioca@0.9.2.rbi → tapioca@0.13.3.rbi} +1596 -1253
  91. data/sorbet/rbi/gems/{thor@1.2.1.rbi → thor@1.3.1.rbi} +1047 -652
  92. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +531 -513
  93. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +13 -13
  94. data/sorbet/rbi/gems/{yard-sorbet@0.6.1.rbi → yard-sorbet@0.8.1.rbi} +132 -92
  95. data/sorbet/rbi/gems/{yard@0.9.28.rbi → yard@0.9.36.rbi} +3158 -3067
  96. data/sorbet/rbi/gems/zeitwerk@2.6.4.rbi +149 -145
  97. metadata +36 -84
  98. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  99. data/.github/pull_request_template.md +0 -28
  100. data/.github/workflows/ci.yml +0 -65
  101. data/.github/workflows/cla.yml +0 -22
  102. data/.gitignore +0 -13
  103. data/.rubocop.yml +0 -75
  104. data/.ruby-version +0 -1
  105. data/CODEOWNERS +0 -1
  106. data/CODE_OF_CONDUCT.md +0 -76
  107. data/CONTRIBUTING.md +0 -17
  108. data/Gemfile +0 -27
  109. data/Gemfile.lock +0 -201
  110. data/RESOLVING_VIOLATIONS.md +0 -81
  111. data/Rakefile +0 -13
  112. data/TROUBLESHOOT.md +0 -45
  113. data/UPGRADING.md +0 -54
  114. data/USAGE.md +0 -367
  115. data/bin/console +0 -15
  116. data/bin/m +0 -29
  117. data/bin/rake +0 -29
  118. data/bin/rubocop +0 -29
  119. data/bin/setup +0 -8
  120. data/bin/srb +0 -29
  121. data/bin/tapioca +0 -29
  122. data/dev.yml +0 -32
  123. data/docs/cohesion.png +0 -0
  124. data/gemfiles/Gemfile-rails-6-0 +0 -22
  125. data/gemfiles/Gemfile-rails-6-1 +0 -22
  126. data/lib/packwerk/cli/result.rb +0 -11
  127. data/packwerk.gemspec +0 -58
  128. data/shipit.rubygems.yml +0 -5
  129. data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +0 -2754
  130. data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +0 -1496
  131. data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +0 -2362
  132. data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +0 -1569
  133. data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +0 -2553
  134. data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +0 -5999
  135. data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +0 -37832
  136. data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +0 -2321
  137. data/sorbet/rbi/gems/better_html@1.0.16.rbi +0 -317
  138. data/sorbet/rbi/gems/coderay@1.1.3.rbi +0 -8
  139. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +0 -1079
  140. data/sorbet/rbi/gems/digest@3.1.0.rbi +0 -189
  141. data/sorbet/rbi/gems/globalid@1.0.0.rbi +0 -572
  142. data/sorbet/rbi/gems/mail@2.7.1.rbi +0 -2490
  143. data/sorbet/rbi/gems/marcel@1.0.2.rbi +0 -220
  144. data/sorbet/rbi/gems/method_source@1.0.0.rbi +0 -76
  145. data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +0 -170
  146. data/sorbet/rbi/gems/net-imap@0.2.3.rbi +0 -2147
  147. data/sorbet/rbi/gems/net-pop@0.1.1.rbi +0 -926
  148. data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +0 -11
  149. data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +0 -1108
  150. data/sorbet/rbi/gems/nio4r@2.5.8.rbi +0 -292
  151. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +0 -9029
  152. data/sorbet/rbi/gems/pry@0.14.1.rbi +0 -8
  153. data/sorbet/rbi/gems/rails@7.0.3.1.rbi +0 -8
  154. data/sorbet/rbi/gems/spoom@1.1.11.rbi +0 -2181
  155. data/sorbet/rbi/gems/strscan@3.0.4.rbi +0 -8
  156. data/sorbet/rbi/gems/timeout@0.3.0.rbi +0 -142
  157. data/sorbet/rbi/gems/unparser@0.6.5.rbi +0 -4529
  158. data/sorbet/rbi/gems/webrick@1.7.0.rbi +0 -2582
  159. data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +0 -993
  160. data/sorbet/rbi/gems/websocket-extensions@0.1.5.rbi +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11206a06e6ac99c1fb8ef4cdefbe334133317d0599fce6480d4dfe10f694c747
4
- data.tar.gz: 606778530b8a872d331e5f331b9c6e14cb37ff6e0a5e13247bb0aebe1ca9e0cc
3
+ metadata.gz: 9172cfed0b1190af4ece801d73138cdaba7cd9aaad8fdb90537b2d6a03d597bb
4
+ data.tar.gz: 946267b723dff18a00a2948d4f9d5c8766018ae815b50fa542ba450bdc29c718
5
5
  SHA512:
6
- metadata.gz: 66166a9c683483fda99341adc79c807c40f8190c349c8650b8646810e06d11f9b4728e2e938ef0abf640967d0764d78b66e956aba3bdaef830c4a15253711542
7
- data.tar.gz: c1cce5df16246f1561cd0954abca4685e21adcaf52d3f6d164acd776f9466a0ad276a65b9a2f5cc9e358b058fcdc2cef265c2b3948043f9f3e35b1d4798ec5e9
6
+ metadata.gz: a7ec7b0f03a55f713b24d9aec7e6cadfb7138087685ee698f7a35c64209638168d91ee7de44a174543271b4252173007d64a16b0bd5f00348dd0554a76c34bb1
7
+ data.tar.gz: ccc7785fcef8f061923c72f122dbf7f7c9ebab1e7c4cc9d2e6c2a8a643ac0da8f7594c17c2823b2bcc7845dd255d886a6b85eabe0ce68176a8b1ff57097c7278
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
 
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)
@@ -19,19 +19,27 @@ module Packwerk
19
19
  CustomAssociations
20
20
  )
21
21
 
22
- sig { params(inflector: T.class_of(ActiveSupport::Inflector), custom_associations: CustomAssociations).void }
23
- def initialize(inflector:, custom_associations: Set.new)
22
+ sig do
23
+ params(
24
+ inflector: T.class_of(ActiveSupport::Inflector),
25
+ custom_associations: CustomAssociations,
26
+ excluded_files: T::Set[String]
27
+ ).void
28
+ end
29
+ def initialize(inflector:, custom_associations: Set.new, excluded_files: Set.new)
24
30
  @inflector = inflector
25
31
  @associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)
32
+ @excluded_files = T.let(excluded_files, T::Set[String])
26
33
  end
27
34
 
28
35
  sig do
29
36
  override
30
- .params(node: AST::Node, ancestors: T::Array[AST::Node])
37
+ .params(node: AST::Node, ancestors: T::Array[AST::Node], relative_file: String)
31
38
  .returns(T.nilable(String))
32
39
  end
33
- def constant_name_from_node(node, ancestors:)
40
+ def constant_name_from_node(node, ancestors:, relative_file:)
34
41
  return unless NodeHelpers.method_call?(node)
42
+ return if excluded?(relative_file)
35
43
  return unless association?(node)
36
44
 
37
45
  arguments = NodeHelpers.method_arguments(node)
@@ -48,6 +56,11 @@ module Packwerk
48
56
 
49
57
  private
50
58
 
59
+ sig { params(relative_file: String).returns(T::Boolean) }
60
+ def excluded?(relative_file)
61
+ @excluded_files.include?(relative_file)
62
+ end
63
+
51
64
  sig { params(node: AST::Node).returns(T::Boolean) }
52
65
  def association?(node)
53
66
  method_name = NodeHelpers.method_name(node)
@@ -11,16 +11,15 @@ module Packwerk
11
11
  class << self
12
12
  extend T::Sig
13
13
 
14
- sig { params(base: Class).void }
14
+ sig { params(base: T::Class[T.anything]).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[T::Class[T.anything]]) }
38
+ def checkers
39
+ @checkers ||= T.let([], T.nilable(T::Array[T::Class[T.anything]]))
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|
@@ -42,8 +51,8 @@ module Packwerk
42
51
  sig { abstract.returns(String) }
43
52
  def violation_type; end
44
53
 
45
- sig { abstract.params(listed_offense: ReferenceOffense).returns(T::Boolean) }
46
- def strict_mode_violation?(listed_offense); end
54
+ sig { abstract.params(offense: ReferenceOffense).returns(T::Boolean) }
55
+ def strict_mode_violation?(offense); end
47
56
 
48
57
  sig { abstract.params(reference: Reference).returns(T::Boolean) }
49
58
  def invalid_reference?(reference); end
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
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class CheckCommand < BaseCommand
7
+ extend T::Sig
8
+ include UsesParseRun
9
+
10
+ description "run all checks"
11
+
12
+ sig { override.returns(T::Boolean) }
13
+ def run
14
+ if @files_for_processing.files.empty?
15
+ out.puts(<<~MSG.squish)
16
+ No files found or given.
17
+ Specify files or check the include and exclude glob in the config file.
18
+ MSG
19
+
20
+ true
21
+ end
22
+
23
+ all_offenses = T.let([], T::Array[Offense])
24
+ on_interrupt = T.let(-> { progress_formatter.interrupted }, T.proc.void)
25
+
26
+ progress_formatter.started_inspection(@files_for_processing.files) do
27
+ all_offenses = parse_run.find_offenses(run_context, on_interrupt: on_interrupt) do |offenses|
28
+ failed = offenses.any? { |offense| !offense_collection.listed?(offense) }
29
+ progress_formatter.increment_progress(failed)
30
+ end
31
+ end
32
+ offense_collection.add_offenses(all_offenses)
33
+
34
+ unlisted_strict_mode_violations = offense_collection.unlisted_strict_mode_violations
35
+
36
+ messages = [
37
+ offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
38
+ offenses_formatter.show_stale_violations(offense_collection, @files_for_processing.files),
39
+ offenses_formatter.show_strict_mode_violations(unlisted_strict_mode_violations),
40
+ ]
41
+
42
+ out.puts(messages.select(&:present?).join("\n") + "\n")
43
+
44
+ offense_collection.outstanding_offenses.empty? &&
45
+ !offense_collection.stale_violations?(@files_for_processing.files) &&
46
+ unlisted_strict_mode_violations.empty?
47
+ end
48
+
49
+ private
50
+
51
+ sig { returns(RunContext) }
52
+ def run_context
53
+ @run_context ||= T.let(RunContext.from_configuration(configuration), T.nilable(RunContext))
54
+ end
55
+
56
+ sig { returns(OffenseCollection) }
57
+ def offense_collection
58
+ @offense_collection ||= T.let(OffenseCollection.new(configuration.root_path), T.nilable(OffenseCollection))
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class HelpCommand < BaseCommand
7
+ extend T::Sig
8
+
9
+ description "display help information about packwerk"
10
+
11
+ sig { override.returns(T::Boolean) }
12
+ def run
13
+ err_out.puts(<<~USAGE)
14
+ Usage: #{$PROGRAM_NAME} <subcommand>
15
+
16
+ Subcommands:
17
+ #{command_help_lines}
18
+ USAGE
19
+
20
+ true
21
+ end
22
+
23
+ private
24
+
25
+ sig { returns(String) }
26
+ def command_help_lines
27
+ Commands.all.map do |command|
28
+ " #{command.name} - #{command.description}"
29
+ end.join("\n")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class InitCommand < BaseCommand
7
+ extend T::Sig
8
+
9
+ description "set up packwerk"
10
+
11
+ sig { override.returns(T::Boolean) }
12
+ def run
13
+ out.puts("📦 Initializing Packwerk...")
14
+
15
+ configuration_file = Generators::ConfigurationFile.generate(
16
+ root: configuration.root_path,
17
+ out: out
18
+ )
19
+
20
+ root_package = Generators::RootPackage.generate(root: configuration.root_path, out: out)
21
+
22
+ success = configuration_file && root_package
23
+
24
+ if success
25
+ out.puts(<<~EOS)
26
+
27
+ 🎉 Packwerk is ready to be used. You can start defining packages and run `bin/packwerk check`.
28
+ For more information on how to use Packwerk, see: https://github.com/Shopify/packwerk/blob/main/USAGE.md
29
+ EOS
30
+ else
31
+ out.puts(<<~EOS)
32
+
33
+ ⚠️ Packwerk is not ready to be used.
34
+ Please check output and refer to https://github.com/Shopify/packwerk/blob/main/USAGE.md for more information.
35
+ EOS
36
+ end
37
+
38
+ success
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class LazyLoadedEntry
7
+ extend T::Sig
8
+
9
+ sig { returns(String) }
10
+ attr_reader :name
11
+
12
+ sig { params(name: String, aliases: T::Array[String]).void }
13
+ def initialize(name, aliases: [])
14
+ @name = name
15
+ @aliases = aliases
16
+ end
17
+
18
+ sig { returns(T.class_of(BaseCommand)) }
19
+ def command_class
20
+ classname = @name.sub(" ", "_").underscore.classify + "Command"
21
+ Commands.const_get(classname) # rubocop:disable Sorbet/ConstantsFromStrings
22
+ end
23
+
24
+ sig { returns(String) }
25
+ def description
26
+ command_class.description
27
+ end
28
+
29
+ sig { params(name_or_alias: String).returns(T::Boolean) }
30
+ def matches_command?(name_or_alias)
31
+ @name == name_or_alias || @aliases.include?(name_or_alias)
32
+ end
33
+ end
34
+
35
+ private_constant :LazyLoadedEntry
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class UpdateTodoCommand < BaseCommand
7
+ extend T::Sig
8
+ include UsesParseRun
9
+
10
+ description "update package_todo.yml files"
11
+
12
+ sig { override.returns(T::Boolean) }
13
+ def run
14
+ if @files_for_processing.files_specified?
15
+ out.puts(<<~MSG.squish)
16
+ ⚠️ update-todo must be called without any file arguments.
17
+ MSG
18
+
19
+ return false
20
+ end
21
+ if @files_for_processing.files.empty?
22
+ out.puts(<<~MSG.squish)
23
+ No files found or given.
24
+ Specify files or check the include and exclude glob in the config file.
25
+ MSG
26
+
27
+ return true
28
+ end
29
+
30
+ run_context = RunContext.from_configuration(configuration)
31
+ offenses = T.let([], T::Array[Offense])
32
+ progress_formatter.started_inspection(@files_for_processing.files) do
33
+ offenses = parse_run.find_offenses(run_context, on_interrupt: -> { progress_formatter.interrupted }) do
34
+ progress_formatter.increment_progress
35
+ end
36
+ end
37
+
38
+ offense_collection = OffenseCollection.new(configuration.root_path)
39
+ offense_collection.add_offenses(offenses)
40
+ offense_collection.persist_package_todo_files(run_context.package_set)
41
+
42
+ unlisted_strict_mode_violations = offense_collection.unlisted_strict_mode_violations
43
+
44
+ messages = [
45
+ offenses_formatter.show_offenses(offense_collection.errors + unlisted_strict_mode_violations),
46
+ ]
47
+
48
+ messages << if unlisted_strict_mode_violations.any?
49
+ "⚠️ `package_todo.yml` has been updated, but unlisted strict mode violations were not added."
50
+ else
51
+ "✅ `package_todo.yml` has been updated."
52
+ end
53
+
54
+ out.puts(messages.select(&:present?).join("\n") + "\n")
55
+
56
+ unlisted_strict_mode_violations.empty? && offense_collection.errors.empty?
57
+ end
58
+ end
59
+ end
60
+ end