packwerk-extensions 0.0.8 → 0.1.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/README.md +26 -1
- data/lib/packwerk/architecture/checker.rb +2 -0
- data/lib/packwerk/architecture/validator.rb +7 -6
- data/lib/packwerk/privacy/checker.rb +20 -1
- data/lib/packwerk/privacy/package.rb +4 -2
- data/lib/packwerk/privacy/validator.rb +91 -2
- data/lib/packwerk/visibility/checker.rb +3 -0
- data/lib/packwerk-extensions.rb +0 -8
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fb13125668a6ba704b97b2a91ffd7af5daf052b32732c7cea8a0f5c7da6bc75
|
4
|
+
data.tar.gz: a82b52e56c9a7b03bcc7b1963105feb3c99225f85a3159e8793dc9d0cf19285a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a7bfa59d26c83525e1b87f0ba0d8270ff1043c42f85189668d8ec5047c534a2e249c082c6fb600ef3d3b1c8fba3351db59425ab07bd8ab372b4956aef6750f9
|
7
|
+
data.tar.gz: 9d2e225bf812bb133726667754d969e4bb2ff5c4a4ede918faf2cb9178349946f7041bc9a60b163c8a100e02d5cdacf31289ada63475af040a1432e2c3d9bcdf
|
data/README.md
CHANGED
@@ -1,12 +1,34 @@
|
|
1
1
|
# packwerk-extensions
|
2
2
|
|
3
|
-
`packwerk-extensions` is a home for checker extensions for packwerk.
|
3
|
+
`packwerk-extensions` is a home for checker extensions for [packwerk](https://github.com/Shopify/packwerk) 3.
|
4
|
+
|
5
|
+
Note that packwerk has not yet released packwerk 3. If you'd like to use `packwerk-extensions`, you'll need to point your `Gemfile` at the `packwerk` `main` branch:
|
6
|
+
```ruby
|
7
|
+
gem 'packwerk', github: 'Shopify/packwerk', branch: 'main'
|
8
|
+
```
|
4
9
|
|
5
10
|
Currently, it ships the following checkers to help improve the boundaries between packages. These checkers are:
|
6
11
|
- A `privacy` checker that ensures other packages are using your package's public API
|
7
12
|
- A `visibility` checker that allows packages to be private except to an explicit group of other packages.
|
8
13
|
- An experimental `architecture` checker that allows packages to specify their "layer" and requires that each layer only communicate with layers below it.
|
9
14
|
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
To register all checkers included in this gem, add the following to your `packwerk.yml`:
|
18
|
+
|
19
|
+
```yaml
|
20
|
+
require: packwerk-extensions
|
21
|
+
```
|
22
|
+
|
23
|
+
Alternatively, you can require individual checkers:
|
24
|
+
|
25
|
+
```yaml
|
26
|
+
require:
|
27
|
+
- packwerk/privacy/checker
|
28
|
+
- packwerk/visibility/checker
|
29
|
+
- packwerk/architecture/checker
|
30
|
+
```
|
31
|
+
|
10
32
|
## Privacy Checker
|
11
33
|
The privacy checker extension was originally extracted from [packwerk](https://github.com/Shopify/packwerk).
|
12
34
|
|
@@ -34,6 +56,9 @@ Example:
|
|
34
56
|
public_path: my/custom/path/
|
35
57
|
```
|
36
58
|
|
59
|
+
### Using specific private constants
|
60
|
+
Sometimes it is desirable to only enforce privacy on a subset of constants in a package. You can do so by defining a `private_constants` list in your package.yml. Note that `enforce_privacy` must be set to `true` or `'strict'` for this to work.
|
61
|
+
|
37
62
|
### Package Privacy violation
|
38
63
|
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`.
|
39
64
|
|
@@ -91,20 +91,21 @@ module Packwerk
|
|
91
91
|
params(config_file_path: String, setting: T.untyped).returns(Result)
|
92
92
|
end
|
93
93
|
def check_enforce_architecture_setting(config_file_path, setting)
|
94
|
+
activated_value = [true, 'strict'].include?(setting)
|
94
95
|
valid_value = [true, nil, false, 'strict'].include?(setting)
|
95
96
|
layers_set = layers.names.any?
|
96
|
-
if valid_value
|
97
|
-
Result.new(ok: true)
|
98
|
-
elsif valid_value
|
97
|
+
if !valid_value
|
99
98
|
Result.new(
|
100
99
|
ok: false,
|
101
|
-
error_value: "
|
100
|
+
error_value: "Invalid 'enforce_architecture' option in #{config_file_path.inspect}: #{setting.inspect}"
|
102
101
|
)
|
103
|
-
|
102
|
+
elsif activated_value && !layers_set
|
104
103
|
Result.new(
|
105
104
|
ok: false,
|
106
|
-
error_value: "
|
105
|
+
error_value: "Cannot set 'enforce_architecture' option in #{config_file_path.inspect} until `architectural_layers` have been specified in `packwerk.yml`"
|
107
106
|
)
|
107
|
+
else
|
108
|
+
Result.new(ok: true)
|
108
109
|
end
|
109
110
|
end
|
110
111
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'packwerk/privacy/package'
|
5
|
+
require 'packwerk/privacy/validator'
|
6
|
+
|
4
7
|
module Packwerk
|
5
8
|
module Privacy
|
6
9
|
# Checks whether a given reference references a private constant of another package.
|
@@ -27,7 +30,9 @@ module Packwerk
|
|
27
30
|
return false if privacy_package.public_path?(reference.constant.location)
|
28
31
|
|
29
32
|
privacy_option = privacy_package.enforce_privacy
|
30
|
-
|
33
|
+
return false if enforcement_disabled?(privacy_option)
|
34
|
+
|
35
|
+
explicitly_private_constant?(reference.constant, explicitly_private_constants: privacy_package.private_constants)
|
31
36
|
end
|
32
37
|
|
33
38
|
sig do
|
@@ -60,6 +65,20 @@ module Packwerk
|
|
60
65
|
|
61
66
|
private
|
62
67
|
|
68
|
+
sig do
|
69
|
+
params(
|
70
|
+
constant: ConstantContext,
|
71
|
+
explicitly_private_constants: T::Array[String]
|
72
|
+
).returns(T::Boolean)
|
73
|
+
end
|
74
|
+
def explicitly_private_constant?(constant, explicitly_private_constants:)
|
75
|
+
return true if explicitly_private_constants.empty?
|
76
|
+
|
77
|
+
explicitly_private_constants.include?(constant.name) ||
|
78
|
+
# nested constants
|
79
|
+
explicitly_private_constants.any? { |epc| constant.name.start_with?("#{epc}::") }
|
80
|
+
end
|
81
|
+
|
63
82
|
sig do
|
64
83
|
params(privacy_option: T.nilable(T.any(T::Boolean, String, T::Array[String])))
|
65
84
|
.returns(T::Boolean)
|
@@ -8,7 +8,8 @@ module Packwerk
|
|
8
8
|
|
9
9
|
const :public_path, String
|
10
10
|
const :user_defined_public_path, T.nilable(String)
|
11
|
-
const :enforce_privacy, T.nilable(T.any(T::Boolean, String
|
11
|
+
const :enforce_privacy, T.nilable(T.any(T::Boolean, String))
|
12
|
+
const :private_constants, T::Array[String]
|
12
13
|
|
13
14
|
sig { params(path: String).returns(T::Boolean) }
|
14
15
|
def public_path?(path)
|
@@ -23,7 +24,8 @@ module Packwerk
|
|
23
24
|
Package.new(
|
24
25
|
public_path: public_path_for(package),
|
25
26
|
user_defined_public_path: user_defined_public_path(package),
|
26
|
-
enforce_privacy: package.config['enforce_privacy']
|
27
|
+
enforce_privacy: package.config['enforce_privacy'],
|
28
|
+
private_constants: package.config['private_constants'] || []
|
27
29
|
)
|
28
30
|
end
|
29
31
|
|
@@ -19,6 +19,8 @@ module Packwerk
|
|
19
19
|
results << check_enforce_privacy_setting(config_file_path, setting)
|
20
20
|
end
|
21
21
|
|
22
|
+
results += verify_private_constants_setting(package_set, configuration)
|
23
|
+
|
22
24
|
public_path_settings = package_manifests_settings_for(configuration, 'public_path')
|
23
25
|
public_path_settings.each do |config_file_path, setting|
|
24
26
|
results << check_public_path(config_file_path, setting)
|
@@ -29,11 +31,49 @@ module Packwerk
|
|
29
31
|
|
30
32
|
sig { override.returns(T::Array[String]) }
|
31
33
|
def permitted_keys
|
32
|
-
%w[public_path enforce_privacy]
|
34
|
+
%w[public_path enforce_privacy private_constants]
|
33
35
|
end
|
34
36
|
|
35
37
|
private
|
36
38
|
|
39
|
+
sig { params(package_set: PackageSet, configuration: Configuration).returns(T::Array[Result]) }
|
40
|
+
def verify_private_constants_setting(package_set, configuration)
|
41
|
+
private_constants_setting = package_manifests_settings_for(configuration, 'private_constants')
|
42
|
+
results = T.let([], T::Array[Result])
|
43
|
+
resolver = ConstantResolver.new(
|
44
|
+
root_path: configuration.root_path,
|
45
|
+
load_paths: configuration.load_paths
|
46
|
+
)
|
47
|
+
|
48
|
+
private_constants_setting.each do |config_file_path, setting|
|
49
|
+
next if setting.nil?
|
50
|
+
|
51
|
+
unless setting.is_a?(Array)
|
52
|
+
results << Result.new(
|
53
|
+
ok: false,
|
54
|
+
error_value: "Invalid 'private_constants' setting: #{setting.inspect}"
|
55
|
+
)
|
56
|
+
next
|
57
|
+
end
|
58
|
+
|
59
|
+
constants = setting
|
60
|
+
|
61
|
+
results += assert_constants_can_be_loaded(constants, config_file_path)
|
62
|
+
|
63
|
+
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
64
|
+
|
65
|
+
constant_locations.each do |name, location|
|
66
|
+
results << if location
|
67
|
+
check_private_constant_location(configuration, package_set, name, location, config_file_path)
|
68
|
+
else
|
69
|
+
private_constant_unresolvable(name, config_file_path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
results
|
75
|
+
end
|
76
|
+
|
37
77
|
sig do
|
38
78
|
params(config_file_path: String, setting: T.untyped).returns(Result)
|
39
79
|
end
|
@@ -52,7 +92,7 @@ module Packwerk
|
|
52
92
|
params(config_file_path: String, setting: T.untyped).returns(Result)
|
53
93
|
end
|
54
94
|
def check_enforce_privacy_setting(config_file_path, setting)
|
55
|
-
if [TrueClass, FalseClass,
|
95
|
+
if [TrueClass, FalseClass, NilClass].include?(setting.class) || setting == 'strict'
|
56
96
|
Result.new(ok: true)
|
57
97
|
else
|
58
98
|
Result.new(
|
@@ -61,6 +101,55 @@ module Packwerk
|
|
61
101
|
)
|
62
102
|
end
|
63
103
|
end
|
104
|
+
|
105
|
+
sig do
|
106
|
+
params(configuration: Configuration, package_set: PackageSet, name: T.untyped, location: T.untyped,
|
107
|
+
config_file_path: T.untyped).returns(Result)
|
108
|
+
end
|
109
|
+
def check_private_constant_location(configuration, package_set, name, location, config_file_path)
|
110
|
+
declared_package = package_set.package_from_path(relative_path(configuration, config_file_path))
|
111
|
+
constant_package = package_set.package_from_path(location)
|
112
|
+
|
113
|
+
if constant_package == declared_package
|
114
|
+
Result.new(ok: true)
|
115
|
+
else
|
116
|
+
Result.new(
|
117
|
+
ok: false,
|
118
|
+
error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be " \
|
119
|
+
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[Result]) }
|
125
|
+
def assert_constants_can_be_loaded(constants, config_file_path)
|
126
|
+
constants.map do |constant|
|
127
|
+
if constant.start_with?('::')
|
128
|
+
constant.try(&:constantize) && Result.new(ok: true)
|
129
|
+
else
|
130
|
+
error_value = "'#{constant}', listed in the 'private_constants' option " \
|
131
|
+
"in #{config_file_path}, is invalid.\nPrivate constants need to be " \
|
132
|
+
'prefixed with the top-level namespace operator `::`.'
|
133
|
+
Result.new(
|
134
|
+
ok: false,
|
135
|
+
error_value: error_value
|
136
|
+
)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
|
142
|
+
def private_constant_unresolvable(name, config_file_path)
|
143
|
+
explicit_filepath = "#{(name.start_with?('::') ? name[2..] : name).underscore}.rb"
|
144
|
+
|
145
|
+
Result.new(
|
146
|
+
ok: false,
|
147
|
+
error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n" \
|
148
|
+
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n" \
|
149
|
+
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n" \
|
150
|
+
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
151
|
+
)
|
152
|
+
end
|
64
153
|
end
|
65
154
|
end
|
66
155
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'packwerk/visibility/package'
|
5
|
+
require 'packwerk/visibility/validator'
|
6
|
+
|
4
7
|
module Packwerk
|
5
8
|
module Visibility
|
6
9
|
# Checks whether a given reference references a constant from a package that does not permit visibility
|
data/lib/packwerk-extensions.rb
CHANGED
@@ -5,16 +5,8 @@ require 'sorbet-runtime'
|
|
5
5
|
require 'packwerk'
|
6
6
|
|
7
7
|
require 'packwerk/privacy/checker'
|
8
|
-
require 'packwerk/privacy/package'
|
9
|
-
require 'packwerk/privacy/validator'
|
10
|
-
|
11
8
|
require 'packwerk/visibility/checker'
|
12
|
-
require 'packwerk/visibility/package'
|
13
|
-
require 'packwerk/visibility/validator'
|
14
|
-
|
15
9
|
require 'packwerk/architecture/checker'
|
16
|
-
require 'packwerk/architecture/package'
|
17
|
-
require 'packwerk/architecture/validator'
|
18
10
|
|
19
11
|
module Packwerk
|
20
12
|
module Extensions
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packwerk-extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.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:
|
11
|
+
date: 2023-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: packwerk
|
@@ -25,19 +25,19 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.2.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: railties
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 6.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: sorbet-runtime
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
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'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: rubocop
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|