package_protections 1.1.1 → 1.4.0
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/lib/package_protections/private.rb +0 -21
- data/lib/package_protections/rspec/application_fixture_helper.rb +54 -0
- data/lib/package_protections/rspec/matchers.rb +99 -0
- data/lib/package_protections/rspec/support.rb +21 -0
- data/lib/package_protections.rb +0 -10
- data/lib/rubocop/cop/package_protections/namespaced_under_package_name.rb +6 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b99c93d81087956b947c89fbede35844a2c973f20e2bee3c8f474383ff99a88
|
4
|
+
data.tar.gz: 64ad87cf10597e5fceeefa315dd3e665f7a684ac36db30c206012c15c2a0f3f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a5a70b90e8ecf6a00e8b22db867e6cec3a83ba34a8fa6f18b45449c96f3eb9fd248c80f9a047d9462e2976d233c8f6b29957837feef224a94d8ce93e008a55d
|
7
|
+
data.tar.gz: 500e49a77a3ee17aa2c4cb05edf2806e37010b5168ebb9cf1a13e1addbb5db5eb11c0caaa82f4a10451dbf96d304c7e010400b19ba7240b920b29a4c93780a90
|
@@ -88,27 +88,6 @@ module PackageProtections
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
-
sig do
|
92
|
-
params(
|
93
|
-
package_names: T::Array[String],
|
94
|
-
all_packages: T::Array[ParsePackwerk::Package]
|
95
|
-
).returns(T::Array[ParsePackwerk::Package])
|
96
|
-
end
|
97
|
-
def self.packages_for_names(package_names, all_packages)
|
98
|
-
all_packages_indexed_by_name = {}
|
99
|
-
all_packages.each { |package| all_packages_indexed_by_name[package.name] = package }
|
100
|
-
|
101
|
-
package_names.map do |package_name|
|
102
|
-
clean_pack_name = package_name.gsub(%r{/$}, '')
|
103
|
-
package = all_packages_indexed_by_name[clean_pack_name]
|
104
|
-
if package.nil?
|
105
|
-
raise "Sorry, we couldn't find a package with name #{package_name}. Here are all of the package names we know about: #{all_packages.map(&:name).sort.inspect}"
|
106
|
-
end
|
107
|
-
|
108
|
-
package
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
91
|
sig { params(root_pathname: Pathname).returns(String) }
|
113
92
|
def self.rubocop_yml(root_pathname:)
|
114
93
|
protected_packages = Dir.chdir(root_pathname) { all_protected_packages }
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module ApplicationFixtureHelper
|
7
|
+
def write_file(path, content = '')
|
8
|
+
pathname = Pathname.new(path)
|
9
|
+
FileUtils.mkdir_p(pathname.dirname)
|
10
|
+
pathname.write(content)
|
11
|
+
path
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_package_yml(
|
15
|
+
pack_name,
|
16
|
+
dependencies: [],
|
17
|
+
enforce_dependencies: true,
|
18
|
+
enforce_privacy: true,
|
19
|
+
protections: {},
|
20
|
+
global_namespaces: [],
|
21
|
+
visible_to: []
|
22
|
+
)
|
23
|
+
defaults = {
|
24
|
+
'prevent_this_package_from_violating_its_stated_dependencies' => 'fail_on_new',
|
25
|
+
'prevent_other_packages_from_using_this_packages_internals' => 'fail_on_new',
|
26
|
+
'prevent_this_package_from_exposing_an_untyped_api' => 'fail_on_new',
|
27
|
+
'prevent_this_package_from_creating_other_namespaces' => 'fail_on_new',
|
28
|
+
'prevent_other_packages_from_using_this_package_without_explicit_visibility' => 'fail_never'
|
29
|
+
}
|
30
|
+
protections_with_defaults = defaults.merge(protections)
|
31
|
+
metadata = { 'protections' => protections_with_defaults }
|
32
|
+
if visible_to.any?
|
33
|
+
metadata.merge!('visible_to' => visible_to)
|
34
|
+
end
|
35
|
+
|
36
|
+
if global_namespaces.any?
|
37
|
+
metadata.merge!('global_namespaces' => global_namespaces)
|
38
|
+
end
|
39
|
+
|
40
|
+
package = ParsePackwerk::Package.new(
|
41
|
+
name: pack_name,
|
42
|
+
dependencies: dependencies,
|
43
|
+
enforce_dependencies: enforce_dependencies,
|
44
|
+
enforce_privacy: enforce_privacy,
|
45
|
+
metadata: metadata
|
46
|
+
)
|
47
|
+
|
48
|
+
ParsePackwerk.write_package_yml!(package)
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_app_file(path)
|
52
|
+
File.delete(path)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
def offense(
|
2
|
+
package_name, message, file, violation_type
|
3
|
+
)
|
4
|
+
|
5
|
+
package = ParsePackwerk.all.find { |p| p.name == package_name }
|
6
|
+
PackageProtections::Offense.new(
|
7
|
+
package: package,
|
8
|
+
message: message,
|
9
|
+
file: file,
|
10
|
+
violation_type: violation_type
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def serialize_offenses_diff(actual_offenses, expected_offense)
|
15
|
+
color_by_match = ->(actual, expected) { actual == expected ? Rainbow(actual).green : "#{Rainbow(actual).red} (expected: #{expected})" }
|
16
|
+
|
17
|
+
actual_offenses.map do |offense|
|
18
|
+
# We color each field red or green depending on if the attributes match our expected
|
19
|
+
<<~SERIALIZED_OFFENSE
|
20
|
+
File: #{color_by_match.call(offense.file, expected_offense.file)}
|
21
|
+
Message: #{color_by_match.call(offense.message, expected_offense.message)}
|
22
|
+
Violation Type: #{color_by_match.call(offense.violation_type, expected_offense.violation_type)}
|
23
|
+
Package: #{color_by_match.call(offense.package.name, expected_offense.package.name)}
|
24
|
+
SERIALIZED_OFFENSE
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def serialize_offenses(actual_offenses)
|
29
|
+
actual_offenses.map do |offense|
|
30
|
+
<<~SERIALIZED_OFFENSE
|
31
|
+
File: #{offense.file}
|
32
|
+
Message: #{offense.message}
|
33
|
+
Violation Type: #{offense.violation_type}
|
34
|
+
Package: #{offense.package.name}
|
35
|
+
SERIALIZED_OFFENSE
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
RSpec::Matchers.define(:include_offense) do |expected_offense|
|
40
|
+
match do |actual_offenses|
|
41
|
+
@actual_offenses = actual_offenses
|
42
|
+
@expected_offense = expected_offense
|
43
|
+
if ENV['DEBUG']
|
44
|
+
PackageProtections.print_offenses(actual_offenses)
|
45
|
+
end
|
46
|
+
@matching_offense = actual_offenses.find do |actual_offense|
|
47
|
+
actual_offense.file == expected_offense.file &&
|
48
|
+
actual_offense.message == expected_offense.message &&
|
49
|
+
actual_offense.violation_type == expected_offense.violation_type &&
|
50
|
+
actual_offense.package.name == expected_offense.package.name
|
51
|
+
end
|
52
|
+
!@matching_offense.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
description do
|
56
|
+
"to have an offense with type `#{expected_offense.type}` tied to package `#{expected_offense.package_name}` with message `#{expected_offense.message}` and instances `#{expected_offense.submessages.join(', ')}`"
|
57
|
+
end
|
58
|
+
|
59
|
+
failure_message do
|
60
|
+
<<~MSG
|
61
|
+
Could not find offense! Here are the found offenses:
|
62
|
+
#{serialize_offenses_diff(@actual_offenses, expected_offense).join("\n\n")}
|
63
|
+
MSG
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
RSpec::Matchers.define(:contain_exactly) do |number_of_offenses|
|
68
|
+
match do |actual_offenses|
|
69
|
+
@actual_offenses = actual_offenses || []
|
70
|
+
@offenses = []
|
71
|
+
@actual_offenses.each do |offense|
|
72
|
+
@offenses << offense
|
73
|
+
end
|
74
|
+
@offenses.size == number_of_offenses
|
75
|
+
end
|
76
|
+
|
77
|
+
chain :offense, :number_of_offenses
|
78
|
+
chain :offenses, :number_of_offenses
|
79
|
+
|
80
|
+
description do
|
81
|
+
'to contain offenses'
|
82
|
+
end
|
83
|
+
|
84
|
+
failure_message_when_negated do
|
85
|
+
"Found the following offenses:\n#{@offenses.map { |r| "#{r.package_name}: #{r.message}" }}"
|
86
|
+
end
|
87
|
+
|
88
|
+
failure_message do
|
89
|
+
if @offenses.empty?
|
90
|
+
"Found #{@offenses.size} instead."
|
91
|
+
else
|
92
|
+
<<~MSG
|
93
|
+
Found #{@offenses.size} instead.
|
94
|
+
|
95
|
+
#{serialize_offenses(@offenses).join("\n")}
|
96
|
+
MSG
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Require this file to load code that supports testing using RSpec.
|
4
|
+
|
5
|
+
require_relative 'application_fixture_helper'
|
6
|
+
require_relative 'matchers'
|
7
|
+
|
8
|
+
def get_resulting_rubocop
|
9
|
+
write_file('config/default.yml', <<~YML.strip)
|
10
|
+
<%= PackageProtections.rubocop_yml(root_pathname: Pathname.pwd) %>
|
11
|
+
YML
|
12
|
+
YAML.safe_load(ERB.new(File.read('config/default.yml')).result(binding))
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.include ApplicationFixtureHelper
|
17
|
+
|
18
|
+
config.before do
|
19
|
+
PackageProtections.bust_cache!
|
20
|
+
end
|
21
|
+
end
|
data/lib/package_protections.rb
CHANGED
@@ -116,16 +116,6 @@ module PackageProtections
|
|
116
116
|
Private.private_cop_config(identifier)
|
117
117
|
end
|
118
118
|
|
119
|
-
sig do
|
120
|
-
params(
|
121
|
-
package_names: T::Array[String],
|
122
|
-
all_packages: T::Array[ParsePackwerk::Package]
|
123
|
-
).returns(T::Array[ParsePackwerk::Package])
|
124
|
-
end
|
125
|
-
def self.packages_for_names(package_names, all_packages)
|
126
|
-
Private.packages_for_names(package_names, all_packages)
|
127
|
-
end
|
128
|
-
|
129
119
|
sig { void }
|
130
120
|
def self.bust_cache!
|
131
121
|
Private.bust_cache!
|
@@ -20,9 +20,7 @@ module RuboCop
|
|
20
20
|
# This cop only works for files in `app`
|
21
21
|
return if !relative_filename.include?('app/')
|
22
22
|
|
23
|
-
|
24
|
-
package_name = match && match[1]
|
25
|
-
|
23
|
+
package_name = ParsePackwerk.package_from_path(relative_filename)&.name
|
26
24
|
return if package_name.nil?
|
27
25
|
|
28
26
|
return if relative_filepath.extname != '.rb'
|
@@ -105,7 +103,11 @@ module RuboCop
|
|
105
103
|
|
106
104
|
# The reason for this is precondition is the `MultipleNamespacesProtection` assumes this to work properly.
|
107
105
|
# To remove this precondition, we need to modify `MultipleNamespacesProtection` to be more generalized!
|
108
|
-
|
106
|
+
is_root_package = package.name == ParsePackwerk::ROOT_PACKAGE_NAME
|
107
|
+
in_allowed_directory = ::PackageProtections::EXPECTED_PACK_DIRECTORIES.any? do |expected_package_directory|
|
108
|
+
package.directory.to_s.start_with?(expected_package_directory)
|
109
|
+
end
|
110
|
+
if in_allowed_directory || is_root_package
|
109
111
|
nil
|
110
112
|
else
|
111
113
|
"Package #{package.name} must be located in one of #{::PackageProtections::EXPECTED_PACK_DIRECTORIES.join(', ')} (or be the root) to use this protection"
|
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: 1.
|
4
|
+
version: 1.4.0
|
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
|
+
date: 2022-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -186,6 +186,9 @@ files:
|
|
186
186
|
- lib/package_protections/private/visibility_protection.rb
|
187
187
|
- lib/package_protections/protected_package.rb
|
188
188
|
- lib/package_protections/protection_interface.rb
|
189
|
+
- lib/package_protections/rspec/application_fixture_helper.rb
|
190
|
+
- lib/package_protections/rspec/matchers.rb
|
191
|
+
- lib/package_protections/rspec/support.rb
|
189
192
|
- lib/package_protections/rubocop_protection_interface.rb
|
190
193
|
- lib/package_protections/violation_behavior.rb
|
191
194
|
- lib/rubocop/cop/package_protections/namespaced_under_package_name.rb
|