packwerk-extensions 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dcb5ef4db4ff6cf134b8707feb0cb4009c8e38db338c407bcbf1fa88da19f79e
4
+ data.tar.gz: 59e1d1382dba41b33924eb82e679a830ccd642446a52748afabcd2d86b7f64b2
5
+ SHA512:
6
+ metadata.gz: 3873288311271201955075edbf6fe17af585cf62efd1b5d925908e1f7791a45cf1b19e6c4bce29ab5d2bc624ed3eaacece6bf0a153f98051261408a8c2ec1d58
7
+ data.tar.gz: 4651d546fa1aff7e1c4521c5311711d7598d9f2defb621132172d1a3058a3d7b15bddfde40e67f8e658490df1616ff90ad14e2fc45e18d0383a7f67f3331c4a2
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # packwerk-extensions
2
+
3
+ `packwerk-extensions` is a home for checker extensions for packwerk.
4
+
5
+ #### Enforcing privacy boundary
6
+
7
+ The privacy checker extension was originally extracted from [packwerk](https://github.com/Shopify/packwerk).
8
+
9
+ A package's privacy boundary is violated when there is a reference to the package's private constants from a source outside the package.
10
+
11
+ There are two ways you can enforce privacy for your package:
12
+
13
+ 1. Enforce privacy for all external sources
14
+
15
+ ```yaml
16
+ # components/merchandising/package.yml
17
+ enforce_privacy: true # will make everything private that is not in
18
+ # the components/merchandising/app/public folder
19
+ ```
20
+
21
+ Setting `enforce_privacy` to true will make all references to private constants in your package a violation.
22
+
23
+ 2. Enforce privacy for specific constants
24
+
25
+ ```yaml
26
+ # components/merchandising/package.yml
27
+ enforce_privacy:
28
+ - "::Merchandising::Product"
29
+ - "::SomeNamespace" # enforces privacy for the namespace and
30
+ # everything nested in it
31
+ ```
32
+
33
+ It will be a privacy violation when a file outside of the `components/merchandising` package tries to reference `Merchandising::Product`.
34
+
35
+ ##### Using public folders
36
+ 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.
37
+
38
+ ##### Defining your own public folder
39
+
40
+ You may prefer to override the default public folder, you can do so on a per-package basis by defining a `public_path`.
41
+
42
+ Example:
43
+
44
+ ```yaml
45
+ public_path: my/custom/path/
46
+ ```
47
+
48
+ ### Package Privacy violation
49
+ 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`.
50
+
51
+ See: [USAGE.md - Enforcing privacy boundary](USAGE.md#Enforcing-privacy-boundary)
52
+
53
+ #### Interpreting Privacy violation
54
+
55
+ > /Users/JaneDoe/src/github.com/sample-project/user/app/controllers/labels_controller.rb:170:30
56
+ > Privacy violation: '::Billing::CarrierInvoiceTransaction' is private to 'billing' but referenced from 'user'.
57
+ > Is there a public entrypoint in 'billing/app/public/' that you can use instead?
58
+ >
59
+ > Inference details: 'Billing::CarrierInvoiceTransaction' refers to ::Billing::CarrierInvoiceTransaction which seems to be defined in billing/app/models/billing/carrier_invoice_transaction.rb.
60
+
61
+ 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`.
62
+
63
+ ##### Suggestions
64
+ 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.
65
+
66
+ 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.
@@ -0,0 +1,99 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Privacy
6
+ # Checks whether a given reference references a private constant of another package.
7
+ class Checker
8
+ extend T::Sig
9
+ include Packwerk::Checker
10
+
11
+ VIOLATION_TYPE = T.let('privacy', String)
12
+
13
+ sig { override.returns(String) }
14
+ def violation_type
15
+ VIOLATION_TYPE
16
+ end
17
+
18
+ sig do
19
+ override
20
+ .params(reference: Packwerk::Reference)
21
+ .returns(T::Boolean)
22
+ end
23
+ def invalid_reference?(reference)
24
+ constant_package = reference.constant.package
25
+ privacy_package = Package.from(constant_package)
26
+
27
+ return false if privacy_package.public_path?(reference.constant.location)
28
+
29
+ privacy_option = privacy_package.enforce_privacy
30
+ return false if enforcement_disabled?(privacy_option)
31
+
32
+ return false unless privacy_option == true || privacy_option == 'strict' ||
33
+ explicitly_private_constant?(reference.constant, explicitly_private_constants: T.unsafe(privacy_option))
34
+
35
+ true
36
+ end
37
+
38
+ sig do
39
+ override
40
+ .params(listed_offense: Packwerk::ReferenceOffense)
41
+ .returns(T::Boolean)
42
+ end
43
+ def strict_mode_violation?(listed_offense)
44
+ publishing_package = listed_offense.reference.constant.package
45
+ publishing_package.config['enforce_privacy'] == 'strict'
46
+ end
47
+
48
+ sig do
49
+ override
50
+ .params(reference: Packwerk::Reference)
51
+ .returns(String)
52
+ end
53
+ def message(reference)
54
+ source_desc = "'#{reference.package}'"
55
+
56
+ message = <<~MESSAGE
57
+ Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but referenced from #{source_desc}.
58
+ Is there a public entrypoint in '#{Package.from(reference.constant.package).public_path}' that you can use instead?
59
+
60
+ #{standard_help_message(reference)}
61
+ MESSAGE
62
+
63
+ message.chomp
64
+ end
65
+
66
+ private
67
+
68
+ sig do
69
+ params(
70
+ constant: ConstantDiscovery::ConstantContext,
71
+ explicitly_private_constants: T::Array[String]
72
+ ).returns(T::Boolean)
73
+ end
74
+ def explicitly_private_constant?(constant, explicitly_private_constants:)
75
+ explicitly_private_constants.include?(constant.name) ||
76
+ # nested constants
77
+ explicitly_private_constants.any? { |epc| constant.name.start_with?("#{epc}::") }
78
+ end
79
+
80
+ sig do
81
+ params(privacy_option: T.nilable(T.any(T::Boolean, String, T::Array[String])))
82
+ .returns(T::Boolean)
83
+ end
84
+ def enforcement_disabled?(privacy_option)
85
+ [false, nil].include?(privacy_option)
86
+ end
87
+
88
+ sig { params(reference: Reference).returns(String) }
89
+ def standard_help_message(reference)
90
+ standard_message = <<~MESSAGE.chomp
91
+ Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
92
+ To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
93
+ MESSAGE
94
+
95
+ standard_message.chomp
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Privacy
6
+ class Package < T::Struct
7
+ extend T::Sig
8
+
9
+ const :public_path, String
10
+ const :user_defined_public_path, T.nilable(String)
11
+ const :enforce_privacy, T.nilable(T.any(T::Boolean, String, T::Array[String]))
12
+
13
+ sig { params(path: String).returns(T::Boolean) }
14
+ def public_path?(path)
15
+ path.start_with?(public_path)
16
+ end
17
+
18
+ class << self
19
+ extend T::Sig
20
+
21
+ sig { params(package: ::Packwerk::Package).returns(Package) }
22
+ def from(package)
23
+ Package.new(
24
+ public_path: public_path_for(package),
25
+ user_defined_public_path: user_defined_public_path(package),
26
+ enforce_privacy: package.config['enforce_privacy']
27
+ )
28
+ end
29
+
30
+ sig { params(package: ::Packwerk::Package).returns(T.nilable(String)) }
31
+ def user_defined_public_path(package)
32
+ return unless package.config['public_path']
33
+ return package.config['public_path'] if package.config['public_path'].end_with?('/')
34
+
35
+ "#{package.config['public_path']}/"
36
+ end
37
+
38
+ sig { params(package: ::Packwerk::Package).returns(String) }
39
+ def public_path_for(package)
40
+ unprefixed_public_path = user_defined_public_path(package) || 'app/public/'
41
+
42
+ if package.root?
43
+ unprefixed_public_path
44
+ else
45
+ File.join(package.name, unprefixed_public_path)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,133 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Privacy
6
+ class Validator
7
+ extend T::Sig
8
+ include Packwerk::Validator
9
+
10
+ sig { override.params(package_set: PackageSet, configuration: Configuration).returns(ApplicationValidator::Result) }
11
+ def call(package_set, configuration)
12
+ privacy_settings = package_manifests_settings_for(configuration, 'enforce_privacy')
13
+
14
+ resolver = ConstantResolver.new(
15
+ root_path: configuration.root_path,
16
+ load_paths: configuration.load_paths
17
+ )
18
+
19
+ results = T.let([], T::Array[ApplicationValidator::Result])
20
+
21
+ privacy_settings.each do |config_file_path, setting|
22
+ results << check_enforce_privacy_setting(config_file_path, setting)
23
+ next unless setting.is_a?(Array)
24
+
25
+ constants = setting
26
+
27
+ results += assert_constants_can_be_loaded(constants, config_file_path)
28
+
29
+ constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
30
+
31
+ constant_locations.each do |name, location|
32
+ results << if location
33
+ check_private_constant_location(configuration, package_set, name, location, config_file_path)
34
+ else
35
+ private_constant_unresolvable(name, config_file_path)
36
+ end
37
+ end
38
+ end
39
+
40
+ public_path_settings = package_manifests_settings_for(configuration, 'public_path')
41
+ public_path_settings.each do |config_file_path, setting|
42
+ results << check_public_path(config_file_path, setting)
43
+ end
44
+
45
+ merge_results(results, separator: "\n---\n")
46
+ end
47
+
48
+ sig { override.returns(T::Array[String]) }
49
+ def permitted_keys
50
+ %w[public_path enforce_privacy]
51
+ end
52
+
53
+ private
54
+
55
+ sig do
56
+ params(config_file_path: String, setting: T.untyped).returns(ApplicationValidator::Result)
57
+ end
58
+ def check_public_path(config_file_path, setting)
59
+ if setting.is_a?(String) || setting.nil?
60
+ ApplicationValidator::Result.new(ok: true)
61
+ else
62
+ ApplicationValidator::Result.new(
63
+ ok: false,
64
+ error_value: "'public_path' option must be a string in #{config_file_path.inspect}: #{setting.inspect}"
65
+ )
66
+ end
67
+ end
68
+
69
+ sig do
70
+ params(config_file_path: String, setting: T.untyped).returns(ApplicationValidator::Result)
71
+ end
72
+ def check_enforce_privacy_setting(config_file_path, setting)
73
+ if [TrueClass, FalseClass, Array, NilClass].include?(setting.class)
74
+ ApplicationValidator::Result.new(ok: true)
75
+ else
76
+ ApplicationValidator::Result.new(
77
+ ok: false,
78
+ error_value: "Invalid 'enforce_privacy' option in #{config_file_path.inspect}: #{setting.inspect}"
79
+ )
80
+ end
81
+ end
82
+
83
+ sig do
84
+ params(configuration: Configuration, package_set: PackageSet, name: T.untyped, location: T.untyped,
85
+ config_file_path: T.untyped).returns(ApplicationValidator::Result)
86
+ end
87
+ def check_private_constant_location(configuration, package_set, name, location, config_file_path)
88
+ declared_package = package_set.package_from_path(relative_path(configuration, config_file_path))
89
+ constant_package = package_set.package_from_path(location)
90
+
91
+ if constant_package == declared_package
92
+ ApplicationValidator::Result.new(ok: true)
93
+ else
94
+ ApplicationValidator::Result.new(
95
+ ok: false,
96
+ error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be " \
97
+ "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
98
+ )
99
+ end
100
+ end
101
+
102
+ sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[ApplicationValidator::Result]) }
103
+ def assert_constants_can_be_loaded(constants, config_file_path)
104
+ constants.map do |constant|
105
+ if constant.start_with?('::')
106
+ constant.try(&:constantize) && ApplicationValidator::Result.new(ok: true)
107
+ else
108
+ error_value = "'#{constant}', listed in the 'enforce_privacy' option " \
109
+ "in #{config_file_path}, is invalid.\nPrivate constants need to be " \
110
+ 'prefixed with the top-level namespace operator `::`.'
111
+ ApplicationValidator::Result.new(
112
+ ok: false,
113
+ error_value: error_value
114
+ )
115
+ end
116
+ end
117
+ end
118
+
119
+ sig { params(name: T.untyped, config_file_path: T.untyped).returns(ApplicationValidator::Result) }
120
+ def private_constant_unresolvable(name, config_file_path)
121
+ explicit_filepath = "#{(name.start_with?('::') ? name[2..] : name).underscore}.rb"
122
+
123
+ ApplicationValidator::Result.new(
124
+ ok: false,
125
+ error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n" \
126
+ "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n" \
127
+ "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n" \
128
+ "private. Add a #{explicit_filepath} file to explicitly define the constant."
129
+ )
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,93 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Visibility
6
+ # Checks whether a given reference references a constant from a package that does not permit visibility
7
+ class Checker
8
+ extend T::Sig
9
+ include Packwerk::Checker
10
+
11
+ VIOLATION_TYPE = T.let('visibility', String)
12
+
13
+ sig { override.returns(String) }
14
+ def violation_type
15
+ VIOLATION_TYPE
16
+ end
17
+
18
+ sig do
19
+ override
20
+ .params(reference: Packwerk::Reference)
21
+ .returns(T::Boolean)
22
+ end
23
+ def invalid_reference?(reference)
24
+ constant_package = reference.constant.package
25
+ visibility_package = Package.from(constant_package)
26
+ visibility_option = visibility_package.enforce_visibility
27
+ return false if enforcement_disabled?(visibility_option)
28
+
29
+ !visibility_package.visible_to.include?(reference.package.name)
30
+ end
31
+
32
+ sig do
33
+ override
34
+ .params(listed_offense: Packwerk::ReferenceOffense)
35
+ .returns(T::Boolean)
36
+ end
37
+ def strict_mode_violation?(listed_offense)
38
+ publishing_package = listed_offense.reference.constant.package
39
+ publishing_package.config['enforce_visibility'] == 'strict'
40
+ end
41
+
42
+ sig do
43
+ override
44
+ .params(reference: Packwerk::Reference)
45
+ .returns(String)
46
+ end
47
+ def message(reference)
48
+ source_desc = "'#{reference.package}'"
49
+
50
+ message = <<~MESSAGE
51
+ Visibility violation: '#{reference.constant.name}' belongs to '#{reference.constant.package}', which is not visible to #{source_desc}.
52
+ Is there a different package to use instead, or should '#{reference.constant.package}' also be visible to #{source_desc}?
53
+
54
+ #{standard_help_message(reference)}
55
+ MESSAGE
56
+
57
+ message.chomp
58
+ end
59
+
60
+ private
61
+
62
+ sig do
63
+ params(
64
+ constant: ConstantDiscovery::ConstantContext,
65
+ explicitly_private_constants: T::Array[String]
66
+ ).returns(T::Boolean)
67
+ end
68
+ def explicitly_private_constant?(constant, explicitly_private_constants:)
69
+ explicitly_private_constants.include?(constant.name) ||
70
+ # nested constants
71
+ explicitly_private_constants.any? { |epc| constant.name.start_with?("#{epc}::") }
72
+ end
73
+
74
+ sig do
75
+ params(visibility_option: T.nilable(T.any(T::Boolean, String)))
76
+ .returns(T::Boolean)
77
+ end
78
+ def enforcement_disabled?(visibility_option)
79
+ [false, nil].include?(visibility_option)
80
+ end
81
+
82
+ sig { params(reference: Reference).returns(String) }
83
+ def standard_help_message(reference)
84
+ standard_message = <<~MESSAGE.chomp
85
+ Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
86
+ To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
87
+ MESSAGE
88
+
89
+ standard_message.chomp
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Visibility
6
+ class Package < T::Struct
7
+ extend T::Sig
8
+
9
+ const :visible_to, T::Array[String]
10
+ const :enforce_visibility, T.nilable(T.any(T::Boolean, String))
11
+
12
+ class << self
13
+ extend T::Sig
14
+
15
+ sig { params(package: ::Packwerk::Package).returns(Package) }
16
+ def from(package)
17
+ Package.new(
18
+ visible_to: package.config['visible_to'] || [],
19
+ enforce_visibility: package.config['enforce_visibility']
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,57 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Visibility
6
+ class Validator
7
+ extend T::Sig
8
+ include Packwerk::Validator
9
+
10
+ sig { override.params(package_set: PackageSet, configuration: Configuration).returns(ApplicationValidator::Result) }
11
+ def call(package_set, configuration)
12
+ visible_settings = package_manifests_settings_for(configuration, 'visible_to')
13
+ results = T.let([], T::Array[ApplicationValidator::Result])
14
+
15
+ all_package_names = package_set.map(&:name).to_set
16
+
17
+ package_manifests_settings_for(configuration, 'enforce_visibility').each do |config, setting|
18
+ next if setting.nil?
19
+
20
+ next if [TrueClass, FalseClass, 'strict'].include?(setting.class)
21
+
22
+ results << ApplicationValidator::Result.new(
23
+ ok: false,
24
+ error_value: "\tInvalid 'enforce_visibility' option: #{setting.inspect} in #{config.inspect}"
25
+ )
26
+ end
27
+
28
+ visible_settings.each do |config_file_path, setting|
29
+ next if setting.nil?
30
+
31
+ if setting.is_a?(Array)
32
+ packages_not_found = setting.to_set - all_package_names
33
+
34
+ if packages_not_found.any?
35
+ results << ApplicationValidator::Result.new(
36
+ ok: false,
37
+ error_value: "'visible_to' option must only contain valid packages in #{config_file_path.inspect}. Invalid packages: #{packages_not_found.to_a.inspect}"
38
+ )
39
+ end
40
+ else
41
+ results << ApplicationValidator::Result.new(
42
+ ok: false,
43
+ error_value: "'visible_to' option must be an array in #{config_file_path.inspect}."
44
+ )
45
+ end
46
+ end
47
+
48
+ merge_results(results, separator: "\n---\n")
49
+ end
50
+
51
+ sig { override.returns(T::Array[String]) }
52
+ def permitted_keys
53
+ %w[visible_to enforce_visibility]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ require 'packwerk'
6
+
7
+ require 'packwerk/privacy/checker'
8
+ require 'packwerk/privacy/package'
9
+ require 'packwerk/privacy/validator'
10
+
11
+ require 'packwerk/visibility/checker'
12
+ require 'packwerk/visibility/package'
13
+ require 'packwerk/visibility/validator'
14
+
15
+ module Packwerk
16
+ module Extensions
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,209 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: packwerk-extensions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gusto Engineers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-12-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: packwerk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sorbet-runtime
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.10520
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.10520
55
+ - !ruby/object:Gem::Dependency
56
+ name: zeitwerk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sorbet
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.5.10520
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 0.5.10520
139
+ - !ruby/object:Gem::Dependency
140
+ name: sorbet-static
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 0.5.10520
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 0.5.10520
153
+ - !ruby/object:Gem::Dependency
154
+ name: tapioca
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: A collection of extensions for packwerk packages.
168
+ email:
169
+ - dev@gusto.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - README.md
175
+ - lib/packwerk-extensions.rb
176
+ - lib/packwerk/privacy/checker.rb
177
+ - lib/packwerk/privacy/package.rb
178
+ - lib/packwerk/privacy/validator.rb
179
+ - lib/packwerk/visibility/checker.rb
180
+ - lib/packwerk/visibility/package.rb
181
+ - lib/packwerk/visibility/validator.rb
182
+ homepage: https://github.com/rubyatscale/packwerk-extensions
183
+ licenses:
184
+ - MIT
185
+ metadata:
186
+ homepage_uri: https://github.com/rubyatscale/packwerk-extensions
187
+ source_code_uri: https://github.com/rubyatscale/packwerk-extensions
188
+ changelog_uri: https://github.com/rubyatscale/packwerk-extensions/releases
189
+ allowed_push_host: https://rubygems.org
190
+ post_install_message:
191
+ rdoc_options: []
192
+ require_paths:
193
+ - lib
194
+ required_ruby_version: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - ">="
197
+ - !ruby/object:Gem::Version
198
+ version: 2.6.0
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ requirements: []
205
+ rubygems_version: 3.1.6
206
+ signing_key:
207
+ specification_version: 4
208
+ summary: A collection of extensions for packwerk packages.
209
+ test_files: []