packwerk-extensions 0.0.1

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 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: []