packwerk 3.0.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/exe/packwerk +4 -1
- data/lib/packwerk/application_validator.rb +3 -0
- data/lib/packwerk/association_inspector.rb +17 -4
- data/lib/packwerk/checker.rb +16 -7
- data/lib/packwerk/cli.rb +14 -177
- data/lib/packwerk/commands/base_command.rb +69 -0
- data/lib/packwerk/commands/check_command.rb +62 -0
- data/lib/packwerk/commands/help_command.rb +33 -0
- data/lib/packwerk/commands/init_command.rb +42 -0
- data/lib/packwerk/commands/lazy_loaded_entry.rb +37 -0
- data/lib/packwerk/commands/update_todo_command.rb +60 -0
- data/lib/packwerk/commands/uses_parse_run.rb +92 -0
- data/lib/packwerk/commands/validate_command.rb +46 -0
- data/lib/packwerk/commands/version_command.rb +18 -0
- data/lib/packwerk/commands.rb +54 -0
- data/lib/packwerk/configuration.rb +8 -1
- data/lib/packwerk/const_node_inspector.rb +2 -2
- data/lib/packwerk/constant_name_inspector.rb +2 -2
- data/lib/packwerk/file_processor.rb +12 -1
- data/lib/packwerk/formatters/default_offenses_formatter.rb +3 -3
- data/lib/packwerk/formatters/progress_formatter.rb +11 -0
- data/lib/packwerk/generators/templates/package.yml +2 -2
- data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
- data/lib/packwerk/offense_collection.rb +32 -12
- data/lib/packwerk/offenses_formatter.rb +14 -5
- data/lib/packwerk/package.rb +1 -1
- data/lib/packwerk/package_todo.rb +86 -69
- data/lib/packwerk/parse_run.rb +42 -82
- data/lib/packwerk/parsers/factory.rb +3 -3
- data/lib/packwerk/parsers/ruby.rb +9 -2
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +3 -3
- data/lib/packwerk/reference_extractor.rb +29 -1
- data/lib/packwerk/reference_offense.rb +1 -1
- data/lib/packwerk/run_context.rb +15 -4
- data/lib/packwerk/spring_command.rb +0 -2
- data/lib/packwerk/validator.rb +19 -5
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +5 -28
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +3280 -3450
- data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +2322 -1782
- data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +2654 -3268
- data/sorbet/rbi/gems/ast@2.4.2.rbi +535 -6
- data/sorbet/rbi/gems/better_html@2.0.1.rbi +529 -0
- data/sorbet/rbi/gems/builder@3.2.4.rbi +4 -4
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +32 -4
- data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +1750 -1840
- data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +15 -15
- data/sorbet/rbi/gems/crass@1.0.6.rbi +489 -5
- data/sorbet/rbi/gems/erubi@1.11.0.rbi +24 -21
- data/sorbet/rbi/gems/i18n@1.12.0.rbi +395 -395
- data/sorbet/rbi/gems/json@2.6.2.rbi +70 -77
- data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +1 -1
- data/sorbet/rbi/gems/loofah@2.18.0.rbi +134 -134
- data/sorbet/rbi/gems/m@1.6.0.rbi +60 -60
- data/sorbet/rbi/gems/method_source@1.1.0.rbi +303 -0
- data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +22 -28
- data/sorbet/rbi/gems/minitest@5.16.2.rbi +384 -396
- data/sorbet/rbi/gems/mocha@1.14.0.rbi +589 -589
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +37 -32
- data/sorbet/rbi/gems/{nokogiri@1.13.8.rbi → nokogiri@1.15.3.rbi} +1869 -1030
- data/sorbet/rbi/gems/{parallel@1.22.1.rbi → parallel@1.24.0.rbi} +85 -82
- data/sorbet/rbi/gems/parser@3.3.1.0.rbi +7320 -0
- data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +1 -1
- data/sorbet/rbi/gems/prism@0.27.0.rbi +36983 -0
- data/sorbet/rbi/gems/{racc@1.6.0.rbi → racc@1.7.1.rbi} +42 -33
- data/sorbet/rbi/gems/rack-test@2.0.2.rbi +148 -338
- data/sorbet/rbi/gems/rack@2.2.4.rbi +1079 -1130
- data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +354 -22
- data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +113 -259
- data/sorbet/rbi/gems/railties@7.0.3.1.rbi +642 -638
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +109 -99
- data/sorbet/rbi/gems/rake@13.0.6.rbi +714 -599
- data/sorbet/rbi/gems/{rbi@0.0.15.rbi → rbi@0.1.12.rbi} +865 -801
- data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +853 -870
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +480 -477
- data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +1621 -1622
- data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +507 -526
- data/sorbet/rbi/gems/rubocop-shopify@2.9.0.rbi +1 -1
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +186 -203
- data/sorbet/rbi/gems/rubocop@1.34.1.rbi +8126 -8367
- data/sorbet/rbi/gems/{ruby-lsp@0.2.1.rbi → ruby-lsp@0.2.3.rbi} +2 -2
- data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1235 -4
- data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +90 -90
- data/sorbet/rbi/gems/spoom@1.3.2.rbi +4420 -0
- data/sorbet/rbi/gems/spring@4.0.0.rbi +104 -104
- data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +1 -1
- data/sorbet/rbi/gems/{tapioca@0.9.2.rbi → tapioca@0.13.3.rbi} +1596 -1253
- data/sorbet/rbi/gems/{thor@1.2.1.rbi → thor@1.3.1.rbi} +1047 -652
- data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +531 -513
- data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +13 -13
- data/sorbet/rbi/gems/{yard-sorbet@0.6.1.rbi → yard-sorbet@0.8.1.rbi} +132 -92
- data/sorbet/rbi/gems/{yard@0.9.28.rbi → yard@0.9.36.rbi} +3158 -3067
- data/sorbet/rbi/gems/zeitwerk@2.6.4.rbi +149 -145
- metadata +36 -84
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- data/.github/pull_request_template.md +0 -28
- data/.github/workflows/ci.yml +0 -65
- data/.github/workflows/cla.yml +0 -22
- data/.gitignore +0 -13
- data/.rubocop.yml +0 -75
- data/.ruby-version +0 -1
- data/CODEOWNERS +0 -1
- data/CODE_OF_CONDUCT.md +0 -76
- data/CONTRIBUTING.md +0 -17
- data/Gemfile +0 -27
- data/Gemfile.lock +0 -201
- data/RESOLVING_VIOLATIONS.md +0 -81
- data/Rakefile +0 -13
- data/TROUBLESHOOT.md +0 -45
- data/UPGRADING.md +0 -54
- data/USAGE.md +0 -367
- data/bin/console +0 -15
- data/bin/m +0 -29
- data/bin/rake +0 -29
- data/bin/rubocop +0 -29
- data/bin/setup +0 -8
- data/bin/srb +0 -29
- data/bin/tapioca +0 -29
- data/dev.yml +0 -32
- data/docs/cohesion.png +0 -0
- data/gemfiles/Gemfile-rails-6-0 +0 -22
- data/gemfiles/Gemfile-rails-6-1 +0 -22
- data/lib/packwerk/cli/result.rb +0 -11
- data/packwerk.gemspec +0 -58
- data/shipit.rubygems.yml +0 -5
- data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +0 -2754
- data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +0 -1496
- data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +0 -2362
- data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +0 -1569
- data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +0 -2553
- data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +0 -5999
- data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +0 -37832
- data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +0 -2321
- data/sorbet/rbi/gems/better_html@1.0.16.rbi +0 -317
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +0 -8
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +0 -1079
- data/sorbet/rbi/gems/digest@3.1.0.rbi +0 -189
- data/sorbet/rbi/gems/globalid@1.0.0.rbi +0 -572
- data/sorbet/rbi/gems/mail@2.7.1.rbi +0 -2490
- data/sorbet/rbi/gems/marcel@1.0.2.rbi +0 -220
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +0 -76
- data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +0 -170
- data/sorbet/rbi/gems/net-imap@0.2.3.rbi +0 -2147
- data/sorbet/rbi/gems/net-pop@0.1.1.rbi +0 -926
- data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +0 -11
- data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +0 -1108
- data/sorbet/rbi/gems/nio4r@2.5.8.rbi +0 -292
- data/sorbet/rbi/gems/parser@3.1.2.1.rbi +0 -9029
- data/sorbet/rbi/gems/pry@0.14.1.rbi +0 -8
- data/sorbet/rbi/gems/rails@7.0.3.1.rbi +0 -8
- data/sorbet/rbi/gems/spoom@1.1.11.rbi +0 -2181
- data/sorbet/rbi/gems/strscan@3.0.4.rbi +0 -8
- data/sorbet/rbi/gems/timeout@0.3.0.rbi +0 -142
- data/sorbet/rbi/gems/unparser@0.6.5.rbi +0 -4529
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +0 -2582
- data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +0 -993
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9172cfed0b1190af4ece801d73138cdaba7cd9aaad8fdb90537b2d6a03d597bb
|
|
4
|
+
data.tar.gz: 946267b723dff18a00a2948d4f9d5c8766018ae815b50fa542ba450bdc29c718
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
62
|
-
- https://github.com/
|
|
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
|
-
|
|
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
|
|
23
|
-
|
|
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)
|
data/lib/packwerk/checker.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
46
|
-
def strict_mode_violation?(
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|