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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aabad25e536a226f791248e858e8db7fc67410b2d623879326165630bfd0922d
4
- data.tar.gz: 64b1d4b5ab05315e9c5ecefdd3105af7485a6f2ed0ba59b9ae456c306b824d72
3
+ metadata.gz: 99c4366393e74fc56c761febc61c96ba3c9e2a235404fa79cc7ca4a884f7702c
4
+ data.tar.gz: f5050c0f1a511ef5e3a9a4718032fdae9ecb0a0d8e647f0ff56560c29b430b57
5
5
  SHA512:
6
- metadata.gz: 58aea344a1b283458d878b224c02fd339e27ca541f2ee025a65970ca94fc717ad5b95137e577760cb512fabf4c5657ccb15f8bc57f78330960294a47c2ad1898
7
- data.tar.gz: 2700f4963e5fe0c5b987aede413458a7181ee8e471b9689bf0dcd0659bd535e99ffee83487ae88c1acfbcc245e8f62da91c070a77bcbea060a4a59bc00b21e19
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::NamespaceConvention
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::RequireDocumentedPublicApis
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::TypedPublicApi
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: 3.2.0
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-01 00:00:00.000000000 Z
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