package_protections 3.2.0 → 4.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 +4 -4
- data/README.md +0 -20
- data/lib/package_protections/private/configuration.rb +0 -1
- data/lib/package_protections/private.rb +0 -1
- data/lib/package_protections/protected_package.rb +0 -5
- data/lib/package_protections/rspec/application_fixture_helper.rb +1 -7
- data/lib/rubocop/cop/package_protections/namespaced_under_package_name.rb +1 -1
- data/lib/rubocop/cop/package_protections/require_documented_public_apis.rb +1 -1
- data/lib/rubocop/cop/package_protections/typed_public_api.rb +1 -1
- metadata +2 -3
- data/lib/package_protections/private/visibility_protection.rb +0 -186
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99c4366393e74fc56c761febc61c96ba3c9e2a235404fa79cc7ca4a884f7702c
|
4
|
+
data.tar.gz: f5050c0f1a511ef5e3a9a4718032fdae9ecb0a0d8e647f0ff56560c29b430b57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23eb8126f636d8bd9552d08d85170fbb3076cbdc79bf1ae9183a882dbdbb1fb26c8df6b9f055964ec7c07f12470528955da931d26dd90d085ecdbcf408723463
|
7
|
+
data.tar.gz: 5e34e9bcfd66b16fc820d9f9b56b8078b8800639d17ff24595ee6e8a8387ed3cc38c411446f182a7c615e06c6939940112a8ed668dc651535724e9fd26c1c6bc
|
data/README.md
CHANGED
@@ -10,7 +10,6 @@ This gem ships with the following checks
|
|
10
10
|
2) Other packages are not using the private API of your package (via `packwerk` `enforce_privacy`)
|
11
11
|
3) Your package has a typed public API (via the `rubocop` `PackageProtections/TypedPublicApi` cop)
|
12
12
|
4) Your package only creates a single namespace (via the `rubocop` `PackageProtections/NamespacedUnderPackageName` cop)
|
13
|
-
4) Your package is only visible to a select number of packages (via the `packwerk` `enforce_privacy` cop)
|
14
13
|
|
15
14
|
## Initial Configuration
|
16
15
|
Package protections first requires that your application is using [`packwerk`](https://github.com/Shopify/packwerk), [`rubocop`](https://github.com/rubocop/rubocop), and [`rubocop-sorbet`](https://github.com/Shopify/rubocop-sorbet). Follow the regular setup instructions for those tools before proceeding.
|
@@ -63,25 +62,6 @@ end
|
|
63
62
|
|
64
63
|
If you've worked through all of the TODOs for this cop and are able to set the value to `fail_on_any`, you can also set `automatic_pack_namespace` which will support your pack having one global namespace without extra subdirectories. That is, instead of `packs/foo/app/services/foo/bar.rb`, you can use `packs/foo/app/services/bar.rb` and still have it define `Foo::Bar`. [See the `stimpack` README.md](https://github.com/rubyatscale/stimpack#readme) for more information.
|
65
64
|
|
66
|
-
### `prevent_other_packages_from_using_this_package_without_explicit_visibility`
|
67
|
-
*This is only available if your package has `enforce_privacy` set to `true`!*
|
68
|
-
This protection exists to help packages have control over who their clients are. When turning on this protection, only clients who are listed in your `visible_to` metadata will be allowed to consume your package. Here is an example in `packs/apples/package.yml`:
|
69
|
-
```yml
|
70
|
-
enforce_privacy: true
|
71
|
-
enforce_dependencies: true
|
72
|
-
metadata:
|
73
|
-
protections:
|
74
|
-
prevent_other_packages_from_using_this_package_without_explicit_visibility: fail_on_new
|
75
|
-
# ... other protections are the same
|
76
|
-
visible_to:
|
77
|
-
- packs/other_pack
|
78
|
-
- packs/another_pack
|
79
|
-
```
|
80
|
-
In this package, only `packs/other_pack` and `packs/another_pack` can use `packs/apples`. With both the `fail_on_new` and `fail_on_any` setting, only those packs can state a dependency on `packs/apples` in their `package.yml`. If any other packs state a dependency on `packs/apples`, the build will fail, even with violations. With the `fail_on_new` setting, a pack can create a dependency or privacy violation on `packs/apples` even if it's not listed. With `fail_on_any`, no violations are allowed.
|
81
|
-
If `visible_to` is not set and the protection is turned on, then the package cannot be consumed by any package (a top-level package might be a good candidate for this).
|
82
|
-
|
83
|
-
Note that this protection's default behavior is `fail_never`, so it can remain unset in the `package.yml`.
|
84
|
-
|
85
65
|
## Violation Behaviors
|
86
66
|
#### `fail_on_any`
|
87
67
|
If this behavior is selected, the build will fail if there is *any* issue, new or old.
|
@@ -34,7 +34,6 @@ module PackageProtections
|
|
34
34
|
Private::IncomingPrivacyProtection.new,
|
35
35
|
RuboCop::Cop::PackageProtections::TypedPublicApi.new,
|
36
36
|
RuboCop::Cop::PackageProtections::NamespacedUnderPackageName.new,
|
37
|
-
Private::VisibilityProtection.new,
|
38
37
|
RuboCop::Cop::PackageProtections::OnlyClassMethods.new,
|
39
38
|
RuboCop::Cop::PackageProtections::RequireDocumentedPublicApis.new
|
40
39
|
]
|
@@ -6,7 +6,6 @@ require 'package_protections/private/output'
|
|
6
6
|
require 'package_protections/private/incoming_privacy_protection'
|
7
7
|
require 'package_protections/private/outgoing_dependency_protection'
|
8
8
|
require 'package_protections/private/metadata_modifiers'
|
9
|
-
require 'package_protections/private/visibility_protection'
|
10
9
|
require 'package_protections/private/configuration'
|
11
10
|
|
12
11
|
module PackageProtections
|
@@ -66,11 +66,6 @@ module PackageProtections
|
|
66
66
|
original_package.dependencies
|
67
67
|
end
|
68
68
|
|
69
|
-
sig { returns(T::Set[String]) }
|
70
|
-
def visible_to
|
71
|
-
Set.new(metadata['visible_to'] || [])
|
72
|
-
end
|
73
|
-
|
74
69
|
sig { returns(T::Array[ParsePackwerk::Violation]) }
|
75
70
|
def violations
|
76
71
|
deprecated_references.violations
|
@@ -16,23 +16,17 @@ module ApplicationFixtureHelper
|
|
16
16
|
dependencies: [],
|
17
17
|
enforce_dependencies: true,
|
18
18
|
enforce_privacy: true,
|
19
|
-
protections: {}
|
20
|
-
visible_to: []
|
19
|
+
protections: {}
|
21
20
|
)
|
22
21
|
defaults = {
|
23
22
|
'prevent_this_package_from_violating_its_stated_dependencies' => 'fail_on_new',
|
24
23
|
'prevent_other_packages_from_using_this_packages_internals' => 'fail_on_new',
|
25
24
|
'prevent_this_package_from_exposing_an_untyped_api' => 'fail_on_new',
|
26
25
|
'prevent_this_package_from_creating_other_namespaces' => 'fail_on_new',
|
27
|
-
'prevent_other_packages_from_using_this_package_without_explicit_visibility' => 'fail_never',
|
28
26
|
'prevent_this_package_from_exposing_instance_method_public_apis' => 'fail_never'
|
29
27
|
}
|
30
28
|
protections_with_defaults = defaults.merge(protections)
|
31
29
|
metadata = { 'protections' => protections_with_defaults }
|
32
|
-
if visible_to.any?
|
33
|
-
metadata.merge!('visible_to' => visible_to)
|
34
|
-
end
|
35
|
-
|
36
30
|
package = ParsePackwerk::Package.new(
|
37
31
|
name: pack_name,
|
38
32
|
dependencies: dependencies,
|
@@ -6,7 +6,7 @@ require 'active_support/core_ext/string/inflections'
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
8
8
|
module PackageProtections
|
9
|
-
class NamespacedUnderPackageName < Packs::
|
9
|
+
class NamespacedUnderPackageName < Packs::RootNamespaceIsPackName
|
10
10
|
extend T::Sig
|
11
11
|
include ::PackageProtections::RubocopProtectionInterface
|
12
12
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module PackageProtections
|
6
|
-
class RequireDocumentedPublicApis < Packs::
|
6
|
+
class RequireDocumentedPublicApis < Packs::DocumentedPublicApis
|
7
7
|
extend T::Sig
|
8
8
|
include ::PackageProtections::RubocopProtectionInterface
|
9
9
|
|
@@ -15,7 +15,7 @@ module RuboCop
|
|
15
15
|
#
|
16
16
|
# We can apply this same pattern if we want to use other cops in the context of package protections and prevent clashing.
|
17
17
|
#
|
18
|
-
class TypedPublicApi < Packs::
|
18
|
+
class TypedPublicApi < Packs::TypedPublicApis
|
19
19
|
extend T::Sig
|
20
20
|
|
21
21
|
include ::PackageProtections::ProtectionInterface
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: package_protections
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -197,7 +197,6 @@ files:
|
|
197
197
|
- lib/package_protections/private/metadata_modifiers.rb
|
198
198
|
- lib/package_protections/private/outgoing_dependency_protection.rb
|
199
199
|
- lib/package_protections/private/output.rb
|
200
|
-
- lib/package_protections/private/visibility_protection.rb
|
201
200
|
- lib/package_protections/protected_package.rb
|
202
201
|
- lib/package_protections/protection_interface.rb
|
203
202
|
- lib/package_protections/rspec/application_fixture_helper.rb
|
@@ -1,186 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module PackageProtections
|
5
|
-
module Private
|
6
|
-
class VisibilityProtection
|
7
|
-
extend T::Sig
|
8
|
-
|
9
|
-
include ProtectionInterface
|
10
|
-
|
11
|
-
IDENTIFIER = 'prevent_other_packages_from_using_this_package_without_explicit_visibility'
|
12
|
-
|
13
|
-
sig { override.returns(String) }
|
14
|
-
def identifier
|
15
|
-
IDENTIFIER
|
16
|
-
end
|
17
|
-
|
18
|
-
sig { override.params(behavior: ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
|
19
|
-
def unmet_preconditions_for_behavior(behavior, package)
|
20
|
-
# This protection relies on seeing privacy violations in other packages.
|
21
|
-
# We also require that the other package enforces dependencies, as otherwise, if the client is using public API, it won't show up
|
22
|
-
# as a privacy OR dependency violation. For now, we don't have the system structure to support that requirement.
|
23
|
-
if behavior.enabled? && !package.enforces_privacy?
|
24
|
-
"Package #{package.name} must have `enforce_privacy: true` to use this protection"
|
25
|
-
elsif !behavior.enabled? && !package.metadata['visible_to'].nil?
|
26
|
-
"Invalid configuration for package `#{package.name}`. `#{identifier}` must be turned on to use `visible_to` configuration."
|
27
|
-
else
|
28
|
-
nil
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
#
|
33
|
-
# By default, this protection does not show up when creating a new package, and its default behavior is FailNever
|
34
|
-
# A package that uses this protection is not considered strictly better -- in general, we want to design packages that
|
35
|
-
# are consumable by all packages. Therefore, a package that is consumable by all packages is the happy path.
|
36
|
-
#
|
37
|
-
# If a user wants to turn on package visibility, they must do it explicitly.
|
38
|
-
#
|
39
|
-
sig { returns(ViolationBehavior) }
|
40
|
-
def default_behavior
|
41
|
-
ViolationBehavior::FailNever
|
42
|
-
end
|
43
|
-
|
44
|
-
sig { override.returns(String) }
|
45
|
-
def humanized_protection_name
|
46
|
-
'Visibility Violations'
|
47
|
-
end
|
48
|
-
|
49
|
-
sig { override.returns(String) }
|
50
|
-
def humanized_protection_description
|
51
|
-
<<~MESSAGE
|
52
|
-
These files are using a constant from a package that restricts its usage through the `visible_to` flag in its `package.yml`
|
53
|
-
To resolve these violations, work with the team who owns the package you are trying to use and to figure out the
|
54
|
-
preferred public API for the behavior you want.
|
55
|
-
|
56
|
-
See https://go/packwerk_cheatsheet_visibility for more info.
|
57
|
-
MESSAGE
|
58
|
-
end
|
59
|
-
|
60
|
-
sig do
|
61
|
-
override.params(
|
62
|
-
new_violations: T::Array[PerFileViolation]
|
63
|
-
).returns(T::Array[Offense])
|
64
|
-
end
|
65
|
-
def get_offenses_for_new_violations(new_violations)
|
66
|
-
new_violations.flat_map do |per_file_violation|
|
67
|
-
depended_on_package = Private.get_package_with_name(per_file_violation.constant_source_package)
|
68
|
-
violation_behavior = depended_on_package.violation_behavior_for(identifier)
|
69
|
-
visible_to = depended_on_package.visible_to
|
70
|
-
next [] if visible_to.include?(per_file_violation.reference_source_package.name)
|
71
|
-
|
72
|
-
case violation_behavior
|
73
|
-
when ViolationBehavior::FailNever
|
74
|
-
next []
|
75
|
-
when ViolationBehavior::FailOnNew
|
76
|
-
message = message_for_fail_on_new(per_file_violation)
|
77
|
-
when ViolationBehavior::FailOnAny
|
78
|
-
message = message_for_fail_on_any(per_file_violation)
|
79
|
-
else
|
80
|
-
T.absurd(violation_behavior)
|
81
|
-
end
|
82
|
-
|
83
|
-
Offense.new(
|
84
|
-
file: per_file_violation.filepath,
|
85
|
-
message: message,
|
86
|
-
violation_type: identifier,
|
87
|
-
package: depended_on_package.original_package
|
88
|
-
)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
sig do
|
93
|
-
override.params(
|
94
|
-
protected_packages: T::Array[ProtectedPackage]
|
95
|
-
).returns(T::Array[Offense])
|
96
|
-
end
|
97
|
-
def get_offenses_for_existing_violations(protected_packages)
|
98
|
-
all_offenses = T.let([], T::Array[Offense])
|
99
|
-
|
100
|
-
all_listed_violations = protected_packages.flat_map do |protected_package|
|
101
|
-
protected_package.violations.flat_map do |violation|
|
102
|
-
PerFileViolation.from(violation, protected_package.original_package)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
#
|
107
|
-
# First we get offenses related to violations between packages, looking at all dependency and privacy
|
108
|
-
# violations between packages.
|
109
|
-
#
|
110
|
-
|
111
|
-
# We only care about looking at each edge once. Since an edge can show up twice if its both a privacy and dependency violation,
|
112
|
-
# we only look at each combination of class from package (A) that's referenced in package (B)
|
113
|
-
unique_per_file_violations = all_listed_violations.uniq do |per_file_violation|
|
114
|
-
[per_file_violation.reference_source_package.name, per_file_violation.constant_source_package, per_file_violation.class_name]
|
115
|
-
end
|
116
|
-
|
117
|
-
all_offenses += unique_per_file_violations.flat_map do |per_file_violation|
|
118
|
-
depended_on_package = Private.get_package_with_name(per_file_violation.constant_source_package)
|
119
|
-
violation_behavior = depended_on_package.violation_behavior_for(identifier)
|
120
|
-
visible_to = depended_on_package.visible_to
|
121
|
-
next [] if visible_to.include?(per_file_violation.reference_source_package.name)
|
122
|
-
|
123
|
-
case violation_behavior
|
124
|
-
when ViolationBehavior::FailNever, ViolationBehavior::FailOnNew
|
125
|
-
next []
|
126
|
-
when ViolationBehavior::FailOnAny
|
127
|
-
message = message_for_fail_on_any(per_file_violation)
|
128
|
-
else
|
129
|
-
T.absurd(violation_behavior)
|
130
|
-
end
|
131
|
-
|
132
|
-
Offense.new(
|
133
|
-
file: per_file_violation.filepath,
|
134
|
-
message: message,
|
135
|
-
violation_type: identifier,
|
136
|
-
package: depended_on_package.original_package
|
137
|
-
)
|
138
|
-
end
|
139
|
-
|
140
|
-
#
|
141
|
-
# Then we get offenses from stated dependencies
|
142
|
-
#
|
143
|
-
all_offenses += protected_packages.flat_map do |protected_package|
|
144
|
-
protected_package.dependencies.flat_map do |package_dependency_name|
|
145
|
-
depended_on_package = Private.get_package_with_name(package_dependency_name)
|
146
|
-
visible_to = depended_on_package.visible_to
|
147
|
-
next [] if visible_to.include?(protected_package.name)
|
148
|
-
|
149
|
-
violation_behavior = depended_on_package.violation_behavior_for(identifier)
|
150
|
-
|
151
|
-
case violation_behavior
|
152
|
-
when ViolationBehavior::FailNever
|
153
|
-
next []
|
154
|
-
when ViolationBehavior::FailOnAny, ViolationBehavior::FailOnNew
|
155
|
-
# continue
|
156
|
-
else
|
157
|
-
T.absurd(violation_behavior)
|
158
|
-
end
|
159
|
-
|
160
|
-
message = "`#{protected_package.name}` cannot state a dependency on `#{depended_on_package.name}`, as it violates package visibility in `#{depended_on_package.yml}`"
|
161
|
-
Offense.new(
|
162
|
-
file: protected_package.yml.to_s,
|
163
|
-
message: message,
|
164
|
-
violation_type: identifier,
|
165
|
-
package: depended_on_package.original_package
|
166
|
-
)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
all_offenses
|
171
|
-
end
|
172
|
-
|
173
|
-
private
|
174
|
-
|
175
|
-
sig { params(per_file_violation: PerFileViolation).returns(String) }
|
176
|
-
def message_for_fail_on_any(per_file_violation)
|
177
|
-
"#{message_for_fail_on_new(per_file_violation)} (`#{per_file_violation.constant_source_package}` set to `fail_on_any`)"
|
178
|
-
end
|
179
|
-
|
180
|
-
sig { params(per_file_violation: PerFileViolation).returns(String) }
|
181
|
-
def message_for_fail_on_new(per_file_violation)
|
182
|
-
"`#{per_file_violation.filepath}` references non-visible `#{per_file_violation.class_name}` from `#{per_file_violation.constant_source_package}`"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|