code_ownership 1.26.0 → 1.28.2
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 +37 -9
- data/lib/code_ownership/private/ownership_mappers/file_annotations.rb +5 -5
- data/lib/code_ownership/private/ownership_mappers/interface.rb +3 -3
- data/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb +4 -4
- data/lib/code_ownership/private/ownership_mappers/package_ownership.rb +5 -40
- data/lib/code_ownership/private/ownership_mappers/team_globs.rb +7 -7
- data/lib/code_ownership/private/parse_js_packages.rb +10 -0
- data/lib/code_ownership/private/team_plugins/github.rb +1 -1
- data/lib/code_ownership/private/team_plugins/ownership.rb +1 -1
- data/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb +49 -7
- data/lib/code_ownership/private.rb +4 -4
- data/lib/code_ownership.rb +8 -8
- data/sorbet/rbi/gems/{bigrails-teams@0.1.0.rbi → code_teams@1.0.0.rbi} +25 -25
- data/sorbet/rbi/gems/{parse_packwerk@0.7.0.rbi → parse_packwerk@0.12.0.rbi} +37 -2
- metadata +13 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf48240fd31979132287823a3f9d40d6b03776df405583c31a56cb87e6dfbe40
|
4
|
+
data.tar.gz: 1cbd9b0fee826c7a23dd71335043eab358f99157bb7f677a94b8d4abc1782f4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68721e648bcd3e8888f3f94d33baf0d38b642c3d6104db0964e6aff3194860407e72c9ad88ffec357285006bff87490b49b92a238fbbf83d37509d34868ea0fe
|
7
|
+
data.tar.gz: 0d878620986f0fe3e6168fa8ea0db1a1a530d27c0f35410f70b3f876146df2b184f9db3486ef9e4e74c0583a067b48ad4462af6877d7452e5d1574bec61ebd8f
|
data/README.md
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
# CodeOwnership
|
2
|
-
This gem helps engineering teams declare ownership of code.
|
3
2
|
|
4
|
-
|
3
|
+
This gem helps engineering teams declare ownership of code. This gem works best in large, usually monolithic code bases where many teams work together.
|
5
4
|
|
6
|
-
Check out `
|
5
|
+
Check out [`lib/code_ownership.rb`](https://github.com/rubyatscale/code_ownership/blob/main/lib/code_ownership.rb) to see the public API.
|
7
6
|
|
8
|
-
|
7
|
+
Check out [`code_ownership_spec.rb`](https://github.com/rubyatscale/code_ownership/blob/main/spec/lib/code_ownership_spec.rb) to see examples of how code ownership is used.
|
8
|
+
|
9
|
+
There is also a [companion VSCode Extension]([url](https://github.com/rubyatscale/code-ownership-vscode)) for this gem. Just search `Gusto.code-ownership-vscode` in the VSCode Extension Marketplace.
|
9
10
|
|
10
11
|
## Usage: Declaring Ownership
|
12
|
+
|
11
13
|
There are three ways to declare code ownership using this gem.
|
14
|
+
|
12
15
|
### Package-Based Ownership
|
13
16
|
Package based ownership integrates [`packwerk`](https://github.com/Shopify/packwerk) and has ownership defined per package. To define that all files within a package are owned by one team, configure your `package.yml` like this:
|
14
17
|
```yml
|
@@ -19,7 +22,7 @@ metadata:
|
|
19
22
|
```
|
20
23
|
|
21
24
|
### Glob-Based Ownership
|
22
|
-
In your team's configured YML (see [`
|
25
|
+
In your team's configured YML (see [`code_teams`](https://github.com/rubyatscale/code_teams)), you can set `owned_globs` to be a glob of files your team owns. For example, in `my_team.yml`:
|
23
26
|
```yml
|
24
27
|
name: My Team
|
25
28
|
owned_globs:
|
@@ -31,9 +34,32 @@ File annotations are a last resort if there is no clear home for your code. File
|
|
31
34
|
```ruby
|
32
35
|
# @team MyTeam
|
33
36
|
```
|
37
|
+
|
38
|
+
### Javascript Package Ownership
|
39
|
+
Javascript package based ownership allows you to specify an owenrship key in a `package.json`. To use this, configure your `package.json` like this:
|
40
|
+
|
41
|
+
```json
|
42
|
+
{
|
43
|
+
// other keys
|
44
|
+
"metadata": {
|
45
|
+
"owner": "My Team"
|
46
|
+
}
|
47
|
+
// other keys
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
51
|
+
You can also tell `code_ownership` where to find JS packages in the configuration, like this:
|
52
|
+
```yml
|
53
|
+
js_package_paths:
|
54
|
+
- frontend/javascripts/packages/*
|
55
|
+
- frontend/other_location_for_packages/*
|
56
|
+
```
|
57
|
+
|
58
|
+
This defaults `**/`, which makes it look for `package.json` files across your application.
|
59
|
+
|
34
60
|
## Usage: Reading CodeOwnership
|
35
61
|
### `for_file`
|
36
|
-
`CodeOwnership.for_file`, given a relative path to a file returns a `
|
62
|
+
`CodeOwnership.for_file`, given a relative path to a file returns a `CodeTeams::Team` if there is a team that owns the file, `nil` otherwise.
|
37
63
|
|
38
64
|
```ruby
|
39
65
|
CodeOwnership.for_file('path/to/file/relative/to/application/root.rb')
|
@@ -44,7 +70,7 @@ Contributor note: If you are making updates to this method or the methods gettin
|
|
44
70
|
See `code_ownership_spec.rb` for examples.
|
45
71
|
|
46
72
|
### `for_backtrace`
|
47
|
-
`CodeOwnership.for_backtrace` can be given a backtrace and will either return `nil`, or a `
|
73
|
+
`CodeOwnership.for_backtrace` can be given a backtrace and will either return `nil`, or a `CodeTeams::Team`.
|
48
74
|
|
49
75
|
```ruby
|
50
76
|
CodeOwnership.for_backtrace(exception.backtrace)
|
@@ -56,7 +82,7 @@ See `code_ownership_spec.rb` for an example.
|
|
56
82
|
|
57
83
|
### `for_class`
|
58
84
|
|
59
|
-
`CodeOwnership.for_class` can be given a class and will either return `nil`, or a `
|
85
|
+
`CodeOwnership.for_class` can be given a class and will either return `nil`, or a `CodeTeams::Team`.
|
60
86
|
|
61
87
|
```ruby
|
62
88
|
CodeOwnership.for_class(MyClass.name)
|
@@ -71,9 +97,11 @@ See `code_ownership_spec.rb` for an example.
|
|
71
97
|
A `CODEOWNERS` file defines who owns specific files or paths in a repository. When you run `bin/codeownership validate`, a `.github/CODEOWNERS` file will automatically be generated and updated.
|
72
98
|
|
73
99
|
## Proper Configuration & Validation
|
100
|
+
|
74
101
|
CodeOwnership comes with a validation function to ensure the following things are true:
|
102
|
+
|
75
103
|
1) Only one mechanism is defining file ownership. That is -- you can't have a file annotation on a file owned via package-based or glob-based ownership. This helps make ownership behavior more clear by avoiding concerns about precedence.
|
76
|
-
2) All teams referenced as an owner for any file or package is a valid team (i.e. it's in the list of `
|
104
|
+
2) All teams referenced as an owner for any file or package is a valid team (i.e. it's in the list of `CodeTeams.all`).
|
77
105
|
3) All files have ownership. You can specify in `unowned_globs` to represent a TODO list of files to add ownership to.
|
78
106
|
3) The `.github/CODEOWNERS` file is up to date. This is automatically corrected and staged unless specified otherwise with `bin/codeownership validate --skip-autocorrect --skip-stage`. You can turn this validation off by setting `skip_codeowners_validation: true` in `code_ownership.yml`.
|
79
107
|
|
@@ -18,13 +18,13 @@ module CodeOwnership
|
|
18
18
|
extend T::Sig
|
19
19
|
include Interface
|
20
20
|
|
21
|
-
@@map_files_to_owners = T.let({}, T.nilable(T::Hash[String, T.nilable(::
|
21
|
+
@@map_files_to_owners = T.let({}, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
|
22
22
|
|
23
23
|
TEAM_PATTERN = T.let(/\A(?:#|\/\/) @team (?<team>.*)\Z/.freeze, Regexp)
|
24
24
|
|
25
25
|
sig do
|
26
26
|
override.params(file: String).
|
27
|
-
returns(T.nilable(::
|
27
|
+
returns(T.nilable(::CodeTeams::Team))
|
28
28
|
end
|
29
29
|
def map_file_to_owner(file)
|
30
30
|
file_annotation_based_owner(file)
|
@@ -33,7 +33,7 @@ module CodeOwnership
|
|
33
33
|
sig do
|
34
34
|
override.
|
35
35
|
params(files: T::Array[String]).
|
36
|
-
returns(T::Hash[String, T.nilable(::
|
36
|
+
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
37
37
|
end
|
38
38
|
def map_files_to_owners(files)
|
39
39
|
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
|
@@ -46,7 +46,7 @@ module CodeOwnership
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
sig { params(filename: String).returns(T.nilable(
|
49
|
+
sig { params(filename: String).returns(T.nilable(CodeTeams::Team)) }
|
50
50
|
def file_annotation_based_owner(filename)
|
51
51
|
# If for a directory is named with an ownable extension, we need to skip
|
52
52
|
# so File.foreach doesn't blow up below. This was needed because Cypress
|
@@ -97,7 +97,7 @@ module CodeOwnership
|
|
97
97
|
end
|
98
98
|
|
99
99
|
sig do
|
100
|
-
override.returns(T::Hash[String, T.nilable(::
|
100
|
+
override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
101
101
|
end
|
102
102
|
def codeowners_lines_to_owners
|
103
103
|
@@map_files_to_owners = nil # rubocop:disable Style/ClassVars
|
@@ -16,7 +16,7 @@ module CodeOwnership
|
|
16
16
|
#
|
17
17
|
sig do
|
18
18
|
abstract.params(file: String).
|
19
|
-
returns(T.nilable(::
|
19
|
+
returns(T.nilable(::CodeTeams::Team))
|
20
20
|
end
|
21
21
|
def map_file_to_owner(file)
|
22
22
|
end
|
@@ -26,13 +26,13 @@ module CodeOwnership
|
|
26
26
|
#
|
27
27
|
sig do
|
28
28
|
abstract.params(files: T::Array[String]).
|
29
|
-
returns(T::Hash[String, T.nilable(::
|
29
|
+
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
30
30
|
end
|
31
31
|
def map_files_to_owners(files)
|
32
32
|
end
|
33
33
|
|
34
34
|
sig do
|
35
|
-
abstract.returns(T::Hash[String, T.nilable(::
|
35
|
+
abstract.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
36
36
|
end
|
37
37
|
def codeowners_lines_to_owners
|
38
38
|
end
|
@@ -13,7 +13,7 @@ module CodeOwnership
|
|
13
13
|
|
14
14
|
sig do
|
15
15
|
override.params(file: String).
|
16
|
-
returns(T.nilable(::
|
16
|
+
returns(T.nilable(::CodeTeams::Team))
|
17
17
|
end
|
18
18
|
def map_file_to_owner(file)
|
19
19
|
package = map_file_to_relevant_package(file)
|
@@ -26,7 +26,7 @@ module CodeOwnership
|
|
26
26
|
sig do
|
27
27
|
override.
|
28
28
|
params(files: T::Array[String]).
|
29
|
-
returns(T::Hash[String, T.nilable(::
|
29
|
+
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
30
30
|
end
|
31
31
|
def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
|
32
32
|
ParseJsPackages.all.each_with_object({}) do |package, res|
|
@@ -49,7 +49,7 @@ module CodeOwnership
|
|
49
49
|
# subset of files, but rather we want code ownership for all files.
|
50
50
|
#
|
51
51
|
sig do
|
52
|
-
override.returns(T::Hash[String, T.nilable(::
|
52
|
+
override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
53
53
|
end
|
54
54
|
def codeowners_lines_to_owners
|
55
55
|
ParseJsPackages.all.each_with_object({}) do |package, res|
|
@@ -65,7 +65,7 @@ module CodeOwnership
|
|
65
65
|
'Owner metadata key in package.json'
|
66
66
|
end
|
67
67
|
|
68
|
-
sig { params(package: ParseJsPackages::Package).returns(T.nilable(
|
68
|
+
sig { params(package: ParseJsPackages::Package).returns(T.nilable(CodeTeams::Team)) }
|
69
69
|
def owner_for_package(package)
|
70
70
|
raw_owner_value = package.metadata['owner']
|
71
71
|
return nil if !raw_owner_value
|
@@ -13,10 +13,10 @@ module CodeOwnership
|
|
13
13
|
|
14
14
|
sig do
|
15
15
|
override.params(file: String).
|
16
|
-
returns(T.nilable(::
|
16
|
+
returns(T.nilable(::CodeTeams::Team))
|
17
17
|
end
|
18
18
|
def map_file_to_owner(file)
|
19
|
-
package =
|
19
|
+
package = ParsePackwerk.package_from_path(file)
|
20
20
|
|
21
21
|
return nil if package.nil?
|
22
22
|
|
@@ -26,7 +26,7 @@ module CodeOwnership
|
|
26
26
|
sig do
|
27
27
|
override.
|
28
28
|
params(files: T::Array[String]).
|
29
|
-
returns(T::Hash[String, T.nilable(::
|
29
|
+
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
30
30
|
end
|
31
31
|
def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
|
32
32
|
ParsePackwerk.all.each_with_object({}) do |package, res|
|
@@ -49,7 +49,7 @@ module CodeOwnership
|
|
49
49
|
# subset of files, but rather we want code ownership for all files.
|
50
50
|
#
|
51
51
|
sig do
|
52
|
-
override.returns(T::Hash[String, T.nilable(::
|
52
|
+
override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
53
53
|
end
|
54
54
|
def codeowners_lines_to_owners
|
55
55
|
ParsePackwerk.all.each_with_object({}) do |package, res|
|
@@ -65,7 +65,7 @@ module CodeOwnership
|
|
65
65
|
'Owner metadata key in package.yml'
|
66
66
|
end
|
67
67
|
|
68
|
-
sig { params(package: ParsePackwerk::Package).returns(T.nilable(
|
68
|
+
sig { params(package: ParsePackwerk::Package).returns(T.nilable(CodeTeams::Team)) }
|
69
69
|
def owner_for_package(package)
|
70
70
|
raw_owner_value = package.metadata['owner']
|
71
71
|
return nil if !raw_owner_value
|
@@ -80,41 +80,6 @@ module CodeOwnership
|
|
80
80
|
def bust_caches!
|
81
81
|
@@package_yml_cache = {} # rubocop:disable Style/ClassVars
|
82
82
|
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
# takes a file and finds the relevant `package.yml` file by walking up the directory
|
87
|
-
# structure. Example, given `packs/a/b/c.rb`, this looks for `packs/a/b/package.yml`, `packs/a/package.yml`,
|
88
|
-
# `packs/package.yml`, and `package.yml` in that order, stopping at the first file to actually exist.
|
89
|
-
# We do additional caching so that we don't have to check for file existence every time
|
90
|
-
sig { params(file: String).returns(T.nilable(ParsePackwerk::Package)) }
|
91
|
-
def map_file_to_relevant_package(file)
|
92
|
-
file_path = Pathname.new(file)
|
93
|
-
path_components = file_path.each_filename.to_a.map { |path| Pathname.new(path) }
|
94
|
-
|
95
|
-
(path_components.length - 1).downto(0).each do |i|
|
96
|
-
potential_relative_path_name = T.must(path_components[0...i]).reduce(Pathname.new('')) { |built_path, path| built_path.join(path) }
|
97
|
-
potential_package_yml_path = potential_relative_path_name.
|
98
|
-
join(ParsePackwerk::PACKAGE_YML_NAME)
|
99
|
-
|
100
|
-
potential_package_yml_string = potential_package_yml_path.to_s
|
101
|
-
|
102
|
-
package = nil
|
103
|
-
if @@package_yml_cache.key?(potential_package_yml_string)
|
104
|
-
package = @@package_yml_cache[potential_package_yml_string]
|
105
|
-
elsif potential_package_yml_path.exist?
|
106
|
-
package = ParsePackwerk::Package.from(potential_package_yml_path)
|
107
|
-
|
108
|
-
@@package_yml_cache[potential_package_yml_string] = package
|
109
|
-
else
|
110
|
-
@@package_yml_cache[potential_package_yml_string] = nil
|
111
|
-
end
|
112
|
-
|
113
|
-
return package unless package.nil?
|
114
|
-
end
|
115
|
-
|
116
|
-
nil
|
117
|
-
end
|
118
83
|
end
|
119
84
|
end
|
120
85
|
end
|
@@ -9,20 +9,20 @@ module CodeOwnership
|
|
9
9
|
extend T::Sig
|
10
10
|
include Interface
|
11
11
|
|
12
|
-
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, T.nilable(::
|
12
|
+
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
|
13
13
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
14
|
-
@@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String, T.nilable(::
|
14
|
+
@@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
|
15
15
|
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
16
16
|
|
17
17
|
sig do
|
18
18
|
override.
|
19
19
|
params(files: T::Array[String]).
|
20
|
-
returns(T::Hash[String, T.nilable(::
|
20
|
+
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
21
21
|
end
|
22
22
|
def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
|
23
23
|
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
|
24
24
|
|
25
|
-
@@map_files_to_owners =
|
25
|
+
@@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
26
26
|
TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
|
27
27
|
Dir.glob(glob).each do |filename|
|
28
28
|
map[filename] = team
|
@@ -33,19 +33,19 @@ module CodeOwnership
|
|
33
33
|
|
34
34
|
sig do
|
35
35
|
override.params(file: String).
|
36
|
-
returns(T.nilable(::
|
36
|
+
returns(T.nilable(::CodeTeams::Team))
|
37
37
|
end
|
38
38
|
def map_file_to_owner(file)
|
39
39
|
map_files_to_owners([file])[file]
|
40
40
|
end
|
41
41
|
|
42
42
|
sig do
|
43
|
-
override.returns(T::Hash[String, T.nilable(::
|
43
|
+
override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
44
44
|
end
|
45
45
|
def codeowners_lines_to_owners
|
46
46
|
return @@codeowners_lines_to_owners if @@codeowners_lines_to_owners&.keys && @@codeowners_lines_to_owners.keys.count > 0
|
47
47
|
|
48
|
-
@@codeowners_lines_to_owners =
|
48
|
+
@@codeowners_lines_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
49
49
|
TeamPlugins::Ownership.for(team).owned_globs.each do |owned_glob|
|
50
50
|
map[owned_glob] = team
|
51
51
|
end
|
@@ -32,6 +32,16 @@ module CodeOwnership
|
|
32
32
|
name: package_name,
|
33
33
|
metadata: package_loaded_json[METADATA] || {}
|
34
34
|
)
|
35
|
+
rescue JSON::ParserError => e
|
36
|
+
error_message = <<~MESSAGE
|
37
|
+
#{e.inspect}
|
38
|
+
|
39
|
+
#{pathname} has invalid JSON, so code ownership cannot be determined.
|
40
|
+
|
41
|
+
Please either make the JSON in that file valid or specify `js_package_paths` in config/code_ownership.yml.
|
42
|
+
MESSAGE
|
43
|
+
|
44
|
+
raise InvalidCodeOwnershipConfigurationError.new(error_message)
|
35
45
|
end
|
36
46
|
|
37
47
|
sig { returns(Pathname) }
|
@@ -25,24 +25,66 @@ module CodeOwnership
|
|
25
25
|
# https://help.github.com/en/articles/about-code-owners
|
26
26
|
HEADER
|
27
27
|
|
28
|
-
|
29
|
-
header,
|
28
|
+
expected_content_lines = [
|
29
|
+
*header.split("\n"),
|
30
|
+
nil, # For line between header and codeowners_file_lines
|
30
31
|
*codeowners_file_lines,
|
31
32
|
nil, # For end-of-file newline
|
32
|
-
]
|
33
|
+
]
|
34
|
+
|
35
|
+
expected_contents = expected_content_lines.join("\n")
|
36
|
+
actual_contents = codeowners_filepath.exist? ? codeowners_filepath.read : ""
|
37
|
+
actual_content_lines = actual_contents.split("\n")
|
33
38
|
|
34
|
-
codeowners_up_to_date =
|
39
|
+
codeowners_up_to_date = actual_contents == expected_contents
|
35
40
|
|
36
41
|
errors = T.let([], T::Array[String])
|
37
42
|
|
38
43
|
if !codeowners_up_to_date
|
39
44
|
if autocorrect
|
40
|
-
codeowners_filepath.write(
|
45
|
+
codeowners_filepath.write(expected_contents)
|
41
46
|
if stage_changes
|
42
47
|
`git add #{codeowners_filepath}`
|
43
48
|
end
|
44
49
|
else
|
45
|
-
|
50
|
+
# If there is no current file or its empty, display a shorter message.
|
51
|
+
missing_lines = expected_content_lines - actual_content_lines
|
52
|
+
extra_lines = actual_content_lines - expected_content_lines
|
53
|
+
missing_lines_text = if missing_lines.any?
|
54
|
+
<<~COMMENT
|
55
|
+
CODEOWNERS should contain the following lines, but does not:
|
56
|
+
#{(expected_content_lines - actual_content_lines).map { |line| "- \"#{line}\""}.join("\n")}
|
57
|
+
COMMENT
|
58
|
+
end
|
59
|
+
|
60
|
+
extra_lines_text = if extra_lines.any?
|
61
|
+
<<~COMMENT
|
62
|
+
CODEOWNERS should not contain the following lines, but it does:
|
63
|
+
#{(actual_content_lines - expected_content_lines).map { |line| "- \"#{line}\""}.join("\n")}
|
64
|
+
COMMENT
|
65
|
+
end
|
66
|
+
|
67
|
+
diff_text = if missing_lines_text && extra_lines_text
|
68
|
+
"#{missing_lines_text}\n#{extra_lines_text}".chomp
|
69
|
+
elsif missing_lines_text
|
70
|
+
missing_lines_text
|
71
|
+
elsif extra_lines_text
|
72
|
+
extra_lines_text
|
73
|
+
else
|
74
|
+
""
|
75
|
+
end
|
76
|
+
|
77
|
+
if actual_contents == ""
|
78
|
+
errors << <<~CODEOWNERS_ERROR
|
79
|
+
CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file
|
80
|
+
CODEOWNERS_ERROR
|
81
|
+
else
|
82
|
+
errors << <<~CODEOWNERS_ERROR
|
83
|
+
CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file
|
84
|
+
|
85
|
+
#{diff_text.chomp}
|
86
|
+
CODEOWNERS_ERROR
|
87
|
+
end
|
46
88
|
end
|
47
89
|
end
|
48
90
|
|
@@ -56,7 +98,7 @@ module CodeOwnership
|
|
56
98
|
# https://help.github.com/articles/about-codeowners/
|
57
99
|
sig { returns(T::Array[String]) }
|
58
100
|
def codeowners_file_lines
|
59
|
-
github_team_map =
|
101
|
+
github_team_map = CodeTeams.all.each_with_object({}) do |team, map|
|
60
102
|
team_github = TeamPlugins::Github.for(team).github
|
61
103
|
next if team_github.do_not_add_to_codeowners_file
|
62
104
|
|
@@ -50,7 +50,7 @@ module CodeOwnership
|
|
50
50
|
end
|
51
51
|
|
52
52
|
if errors.any?
|
53
|
-
errors << 'See https://github.com/
|
53
|
+
errors << 'See https://github.com/rubyatscale/code_ownership#README.md for more details'
|
54
54
|
raise InvalidCodeOwnershipConfigurationError.new(errors.join("\n")) # rubocop:disable Style/RaiseArgs
|
55
55
|
end
|
56
56
|
end
|
@@ -92,11 +92,11 @@ module CodeOwnership
|
|
92
92
|
@tracked_files ||= Dir.glob(configuration.owned_globs)
|
93
93
|
end
|
94
94
|
|
95
|
-
sig { params(team_name: String, location_of_reference: String).returns(
|
95
|
+
sig { params(team_name: String, location_of_reference: String).returns(CodeTeams::Team) }
|
96
96
|
def self.find_team!(team_name, location_of_reference)
|
97
|
-
found_team =
|
97
|
+
found_team = CodeTeams.find(team_name)
|
98
98
|
if found_team.nil?
|
99
|
-
raise StandardError, "Could not find team with name: `#{team_name}` in #{location_of_reference}. Make sure the team is one of `#{
|
99
|
+
raise StandardError, "Could not find team with name: `#{team_name}` in #{location_of_reference}. Make sure the team is one of `#{CodeTeams.all.map(&:name).sort}`"
|
100
100
|
else
|
101
101
|
found_team
|
102
102
|
end
|
data/lib/code_ownership.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# typed: strict
|
4
4
|
|
5
5
|
require 'set'
|
6
|
-
require '
|
6
|
+
require 'code_teams'
|
7
7
|
require 'sorbet-runtime'
|
8
8
|
require 'json'
|
9
9
|
require 'parse_packwerk'
|
@@ -17,15 +17,15 @@ module CodeOwnership
|
|
17
17
|
|
18
18
|
requires_ancestor { Kernel }
|
19
19
|
|
20
|
-
sig { params(file: String).returns(T.nilable(
|
20
|
+
sig { params(file: String).returns(T.nilable(CodeTeams::Team)) }
|
21
21
|
def for_file(file)
|
22
|
-
@for_file ||= T.let(@for_file, T.nilable(T::Hash[String, T.nilable(
|
22
|
+
@for_file ||= T.let(@for_file, T.nilable(T::Hash[String, T.nilable(CodeTeams::Team)]))
|
23
23
|
@for_file ||= {}
|
24
24
|
|
25
25
|
return nil if file.start_with?('./')
|
26
26
|
return @for_file[file] if @for_file.key?(file)
|
27
27
|
|
28
|
-
owner = T.let(nil, T.nilable(
|
28
|
+
owner = T.let(nil, T.nilable(CodeTeams::Team))
|
29
29
|
|
30
30
|
Private.mappers.each do |mapper|
|
31
31
|
owner = mapper.map_file_to_owner(file)
|
@@ -61,7 +61,7 @@ module CodeOwnership
|
|
61
61
|
|
62
62
|
# Given a backtrace from either `Exception#backtrace` or `caller`, find the
|
63
63
|
# first line that corresponds to a file with assigned ownership
|
64
|
-
sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::
|
64
|
+
sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::CodeTeams::Team]).returns(T.nilable(::CodeTeams::Team)) }
|
65
65
|
def for_backtrace(backtrace, excluded_teams: [])
|
66
66
|
return unless backtrace
|
67
67
|
|
@@ -93,9 +93,9 @@ module CodeOwnership
|
|
93
93
|
nil
|
94
94
|
end
|
95
95
|
|
96
|
-
sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(::
|
96
|
+
sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(::CodeTeams::Team)) }
|
97
97
|
def for_class(klass)
|
98
|
-
@memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(::
|
98
|
+
@memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)]))
|
99
99
|
@memoized_values ||= {}
|
100
100
|
# We use key because the memoized value could be `nil`
|
101
101
|
if !@memoized_values.key?(klass.to_s)
|
@@ -110,7 +110,7 @@ module CodeOwnership
|
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
|
-
sig { params(package: ParsePackwerk::Package).returns(T.nilable(::
|
113
|
+
sig { params(package: ParsePackwerk::Package).returns(T.nilable(::CodeTeams::Team)) }
|
114
114
|
def for_package(package)
|
115
115
|
Private::OwnershipMappers::PackageOwnership.new.owner_for_package(package)
|
116
116
|
end
|
@@ -1,78 +1,78 @@
|
|
1
1
|
# typed: true
|
2
2
|
|
3
3
|
# DO NOT EDIT MANUALLY
|
4
|
-
# This is an autogenerated file for types exported from the `
|
5
|
-
# Please instead update this file by running `bin/tapioca gem
|
4
|
+
# This is an autogenerated file for types exported from the `code_teams` gem.
|
5
|
+
# Please instead update this file by running `bin/tapioca gem code_teams`.
|
6
6
|
|
7
|
-
module
|
7
|
+
module CodeTeams
|
8
8
|
class << self
|
9
|
-
sig { returns(T::Array[::
|
9
|
+
sig { returns(T::Array[::CodeTeams::Team]) }
|
10
10
|
def all; end
|
11
11
|
|
12
12
|
sig { void }
|
13
13
|
def bust_caches!; end
|
14
14
|
|
15
|
-
sig { params(name: ::String).returns(T.nilable(::
|
15
|
+
sig { params(name: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
16
16
|
def find(name); end
|
17
17
|
|
18
|
-
sig { params(dir: ::String).returns(T::Array[::
|
18
|
+
sig { params(dir: ::String).returns(T::Array[::CodeTeams::Team]) }
|
19
19
|
def for_directory(dir); end
|
20
20
|
|
21
21
|
sig { params(string: ::String).returns(::String) }
|
22
22
|
def tag_value_for(string); end
|
23
23
|
|
24
|
-
sig { params(teams: T::Array[::
|
24
|
+
sig { params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) }
|
25
25
|
def validation_errors(teams); end
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
class
|
29
|
+
class CodeTeams::IncorrectPublicApiUsageError < ::StandardError; end
|
30
30
|
|
31
|
-
class
|
31
|
+
class CodeTeams::Plugin
|
32
32
|
abstract!
|
33
33
|
|
34
|
-
sig { params(team: ::
|
34
|
+
sig { params(team: ::CodeTeams::Team).void }
|
35
35
|
def initialize(team); end
|
36
36
|
|
37
37
|
class << self
|
38
|
-
sig { returns(T::Array[T.class_of(
|
38
|
+
sig { returns(T::Array[T.class_of(CodeTeams::Plugin)]) }
|
39
39
|
def all_plugins; end
|
40
40
|
|
41
|
-
sig { params(team: ::
|
41
|
+
sig { params(team: ::CodeTeams::Team).returns(T.attached_class) }
|
42
42
|
def for(team); end
|
43
43
|
|
44
44
|
sig { params(base: T.untyped).void }
|
45
45
|
def inherited(base); end
|
46
46
|
|
47
|
-
sig { params(team: ::
|
47
|
+
sig { params(team: ::CodeTeams::Team, key: ::String).returns(::String) }
|
48
48
|
def missing_key_error_message(team, key); end
|
49
49
|
|
50
|
-
sig { params(teams: T::Array[::
|
50
|
+
sig { params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) }
|
51
51
|
def validation_errors(teams); end
|
52
52
|
|
53
53
|
private
|
54
54
|
|
55
|
-
sig { params(team: ::
|
55
|
+
sig { params(team: ::CodeTeams::Team).returns(T.attached_class) }
|
56
56
|
def register_team(team); end
|
57
57
|
|
58
|
-
sig { returns(T::Hash[T.nilable(::String), T::Hash[::Class, ::
|
58
|
+
sig { returns(T::Hash[T.nilable(::String), T::Hash[::Class, ::CodeTeams::Plugin]]) }
|
59
59
|
def registry; end
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
module
|
63
|
+
module CodeTeams::Plugins; end
|
64
64
|
|
65
|
-
class
|
66
|
-
sig { returns(::
|
65
|
+
class CodeTeams::Plugins::Identity < ::CodeTeams::Plugin
|
66
|
+
sig { returns(::CodeTeams::Plugins::Identity::IdentityStruct) }
|
67
67
|
def identity; end
|
68
68
|
|
69
69
|
class << self
|
70
|
-
sig { override.params(teams: T::Array[::
|
70
|
+
sig { override.params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) }
|
71
71
|
def validation_errors(teams); end
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
class
|
75
|
+
class CodeTeams::Plugins::Identity::IdentityStruct < ::Struct
|
76
76
|
def name; end
|
77
77
|
def name=(_); end
|
78
78
|
|
@@ -84,7 +84,7 @@ class Teams::Plugins::Identity::IdentityStruct < ::Struct
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
-
class
|
87
|
+
class CodeTeams::Team
|
88
88
|
sig { params(config_yml: T.nilable(::String), raw_hash: T::Hash[T.untyped, T.untyped]).void }
|
89
89
|
def initialize(config_yml:, raw_hash:); end
|
90
90
|
|
@@ -109,12 +109,12 @@ class Teams::Team
|
|
109
109
|
def to_tag; end
|
110
110
|
|
111
111
|
class << self
|
112
|
-
sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(::
|
112
|
+
sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(::CodeTeams::Team) }
|
113
113
|
def from_hash(raw_hash); end
|
114
114
|
|
115
|
-
sig { params(config_yml: ::String).returns(::
|
115
|
+
sig { params(config_yml: ::String).returns(::CodeTeams::Team) }
|
116
116
|
def from_yml(config_yml); end
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
|
-
|
120
|
+
CodeTeams::UNKNOWN_TEAM_STRING = T.let(T.unsafe(nil), String)
|
@@ -6,12 +6,18 @@
|
|
6
6
|
|
7
7
|
module ParsePackwerk
|
8
8
|
class << self
|
9
|
-
sig {
|
10
|
-
def all
|
9
|
+
sig { returns(T::Array[::ParsePackwerk::Package]) }
|
10
|
+
def all; end
|
11
|
+
|
12
|
+
sig { void }
|
13
|
+
def bust_cache!; end
|
11
14
|
|
12
15
|
sig { params(name: ::String).returns(T.nilable(::ParsePackwerk::Package)) }
|
13
16
|
def find(name); end
|
14
17
|
|
18
|
+
sig { params(file_path: T.any(::Pathname, ::String)).returns(T.nilable(::ParsePackwerk::Package)) }
|
19
|
+
def package_from_path(file_path); end
|
20
|
+
|
15
21
|
sig { params(package: ::ParsePackwerk::Package).void }
|
16
22
|
def write_package_yml!(package); end
|
17
23
|
|
@@ -27,12 +33,24 @@ end
|
|
27
33
|
|
28
34
|
class ParsePackwerk::Configuration < ::T::Struct
|
29
35
|
const :exclude, T::Array[::String]
|
36
|
+
const :package_paths, T::Array[::String]
|
30
37
|
|
31
38
|
class << self
|
39
|
+
sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[::String]) }
|
40
|
+
def excludes(config_hash); end
|
41
|
+
|
42
|
+
sig { returns(::ParsePackwerk::Configuration) }
|
43
|
+
def fetch; end
|
44
|
+
|
32
45
|
def inherited(s); end
|
46
|
+
|
47
|
+
sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[::String]) }
|
48
|
+
def package_paths(config_hash); end
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
52
|
+
ParsePackwerk::DEFAULT_EXCLUDE_GLOBS = T.let(T.unsafe(nil), Array)
|
53
|
+
ParsePackwerk::DEFAULT_PACKAGE_PATHS = T.let(T.unsafe(nil), Array)
|
36
54
|
ParsePackwerk::DEPENDENCIES = T.let(T.unsafe(nil), String)
|
37
55
|
ParsePackwerk::DEPRECATED_REFERENCES_YML_NAME = T.let(T.unsafe(nil), String)
|
38
56
|
|
@@ -91,6 +109,23 @@ class ParsePackwerk::Package < ::T::Struct
|
|
91
109
|
end
|
92
110
|
end
|
93
111
|
|
112
|
+
class ParsePackwerk::PackageSet
|
113
|
+
class << self
|
114
|
+
sig do
|
115
|
+
params(
|
116
|
+
package_pathspec: T::Array[::String],
|
117
|
+
exclude_pathspec: T::Array[::String]
|
118
|
+
).returns(T::Array[::ParsePackwerk::Package])
|
119
|
+
end
|
120
|
+
def from(package_pathspec:, exclude_pathspec:); end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
sig { params(globs: T::Array[::String], path: ::Pathname).returns(T::Boolean) }
|
125
|
+
def exclude_path?(globs, path); end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
94
129
|
ParsePackwerk::ROOT_PACKAGE_NAME = T.let(T.unsafe(nil), String)
|
95
130
|
|
96
131
|
class ParsePackwerk::Violation < ::T::Struct
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_ownership
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.28.2
|
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-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: code_teams
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: parse_packwerk
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,17 +149,17 @@ files:
|
|
149
149
|
- lib/code_ownership/private/validations/github_codeowners_up_to_date.rb
|
150
150
|
- lib/code_ownership/private/validations/interface.rb
|
151
151
|
- sorbet/config
|
152
|
-
- sorbet/rbi/gems/
|
153
|
-
- sorbet/rbi/gems/parse_packwerk@0.
|
152
|
+
- sorbet/rbi/gems/code_teams@1.0.0.rbi
|
153
|
+
- sorbet/rbi/gems/parse_packwerk@0.12.0.rbi
|
154
154
|
- sorbet/rbi/manual.rbi
|
155
155
|
- sorbet/rbi/todo.rbi
|
156
|
-
homepage: https://github.com/
|
156
|
+
homepage: https://github.com/rubyatscale/code_ownership
|
157
157
|
licenses:
|
158
158
|
- MIT
|
159
159
|
metadata:
|
160
|
-
homepage_uri: https://github.com/
|
161
|
-
source_code_uri: https://github.com/
|
162
|
-
changelog_uri: https://github.com/
|
160
|
+
homepage_uri: https://github.com/rubyatscale/code_ownership
|
161
|
+
source_code_uri: https://github.com/rubyatscale/code_ownership
|
162
|
+
changelog_uri: https://github.com/rubyatscale/code_ownership/releases
|
163
163
|
allowed_push_host: https://rubygems.org
|
164
164
|
post_install_message:
|
165
165
|
rdoc_options: []
|