code_ownership 1.28.0 → 1.29.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 +42 -3
- data/lib/code_ownership/cli.rb +25 -0
- data/lib/code_ownership/private/ownership_mappers/package_ownership.rb +1 -36
- data/lib/code_ownership/private/parse_js_packages.rb +10 -0
- data/lib/code_ownership.rb +30 -0
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/{parse_packwerk@0.7.0.rbi → parse_packwerk@0.12.0.rbi} +37 -2
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8008aad63593c3be910dc1e7f45f2518150d0e76a92f8a021279902995499402
|
|
4
|
+
data.tar.gz: 551585001712db071af3e33607bfd121ff48524fa66776f8ee48a299da28ef1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2b94a65f309e158505b6dd4aa740cf3cfedc7076d9ccb0e7fbc1fe04ce147c8355cbe9cfd687a6b0e05e5ad755bb703f8fc1b530ad40f9b24fb9a4d8326a18f
|
|
7
|
+
data.tar.gz: 895352504690505cce06da848c5a400b3e48f1a9e10d49baa5a1e0093bd29c70c6042eaaa9c21dea14cbe8b0e7e44d15a8bf124150528f33280abee5eb101d97
|
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.
|
|
6
|
+
|
|
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.
|
|
7
8
|
|
|
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
|
|
@@ -31,6 +34,29 @@ 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
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.
|
|
@@ -66,12 +92,25 @@ Under the hood, this finds the file where the class is defined and returns the o
|
|
|
66
92
|
|
|
67
93
|
See `code_ownership_spec.rb` for an example.
|
|
68
94
|
|
|
95
|
+
### `for_team`
|
|
96
|
+
`CodeOwnership.for_team` can be used to generate an ownership report for a team.
|
|
97
|
+
```ruby
|
|
98
|
+
CodeOwnership.for_team('My Team')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
You can shovel this into a markdown file for easy viewing using the CLI:
|
|
102
|
+
```
|
|
103
|
+
bin/codeownership for_team 'My Team' > tmp/ownership_report.md
|
|
104
|
+
```
|
|
105
|
+
|
|
69
106
|
## Usage: Generating a `CODEOWNERS` file
|
|
70
107
|
|
|
71
108
|
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
109
|
|
|
73
110
|
## Proper Configuration & Validation
|
|
111
|
+
|
|
74
112
|
CodeOwnership comes with a validation function to ensure the following things are true:
|
|
113
|
+
|
|
75
114
|
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
115
|
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
116
|
3) All files have ownership. You can specify in `unowned_globs` to represent a TODO list of files to add ownership to.
|
data/lib/code_ownership/cli.rb
CHANGED
|
@@ -11,6 +11,8 @@ module CodeOwnership
|
|
|
11
11
|
validate!(argv)
|
|
12
12
|
elsif command == 'for_file'
|
|
13
13
|
for_file(argv)
|
|
14
|
+
elsif command == 'for_team'
|
|
15
|
+
for_team(argv)
|
|
14
16
|
elsif [nil, "help"].include?(command)
|
|
15
17
|
puts <<~USAGE
|
|
16
18
|
Usage: bin/codeownership <subcommand>
|
|
@@ -18,6 +20,7 @@ module CodeOwnership
|
|
|
18
20
|
Subcommands:
|
|
19
21
|
validate - run all validations
|
|
20
22
|
for_file - find code ownership for a single file
|
|
23
|
+
for_team - find code ownership information for a team
|
|
21
24
|
help - display help information about code_ownership
|
|
22
25
|
USAGE
|
|
23
26
|
else
|
|
@@ -116,6 +119,28 @@ module CodeOwnership
|
|
|
116
119
|
end
|
|
117
120
|
end
|
|
118
121
|
|
|
122
|
+
def self.for_team(argv)
|
|
123
|
+
options = {}
|
|
124
|
+
|
|
125
|
+
parser = OptionParser.new do |opts|
|
|
126
|
+
opts.banner = 'Usage: bin/codeownership for_team \'Team Name\''
|
|
127
|
+
|
|
128
|
+
opts.on('--help', 'Shows this prompt') do
|
|
129
|
+
puts opts
|
|
130
|
+
exit
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
teams = argv.select { |arg| !arg.start_with?('--') }
|
|
134
|
+
args = parser.order!(argv) {}
|
|
135
|
+
parser.parse!(args)
|
|
136
|
+
|
|
137
|
+
if teams.count != 1
|
|
138
|
+
raise "Please pass in one team. Use `bin/codeownership for_team --help` for more info"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
puts CodeOwnership.for_team(teams.first)
|
|
142
|
+
end
|
|
143
|
+
|
|
119
144
|
private_class_method :validate!
|
|
120
145
|
end
|
|
121
146
|
end
|
|
@@ -16,7 +16,7 @@ module CodeOwnership
|
|
|
16
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
|
|
|
@@ -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
|
|
@@ -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) }
|
data/lib/code_ownership.rb
CHANGED
|
@@ -35,6 +35,36 @@ module CodeOwnership
|
|
|
35
35
|
@for_file[file] = owner
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
sig { params(team: T.any(CodeTeams::Team, String)).returns(String) }
|
|
39
|
+
def for_team(team)
|
|
40
|
+
team = T.must(CodeTeams.find(team)) if team.is_a?(String)
|
|
41
|
+
ownership_information = T.let([], T::Array[String])
|
|
42
|
+
|
|
43
|
+
ownership_information << "# Code Ownership Report for `#{team.name}` Team"
|
|
44
|
+
Private.mappers.each do |mapper|
|
|
45
|
+
ownership_information << "## #{mapper.description}"
|
|
46
|
+
codeowners_lines = mapper.codeowners_lines_to_owners
|
|
47
|
+
ownership_for_mapper = []
|
|
48
|
+
codeowners_lines.each do |line, team_for_line|
|
|
49
|
+
next if team_for_line.nil?
|
|
50
|
+
if team_for_line.name == team.name
|
|
51
|
+
ownership_for_mapper << "- #{line}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if ownership_for_mapper.empty?
|
|
56
|
+
ownership_information << 'This team owns nothing in this category.'
|
|
57
|
+
else
|
|
58
|
+
ownership_information += ownership_for_mapper
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
ownership_information << ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ownership_information.join("\n")
|
|
66
|
+
end
|
|
67
|
+
|
|
38
68
|
class InvalidCodeOwnershipConfigurationError < StandardError
|
|
39
69
|
end
|
|
40
70
|
|
data/sorbet/config
CHANGED
|
@@ -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,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: code_ownership
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.29.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-11-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: code_teams
|
|
@@ -150,7 +150,7 @@ files:
|
|
|
150
150
|
- lib/code_ownership/private/validations/interface.rb
|
|
151
151
|
- sorbet/config
|
|
152
152
|
- sorbet/rbi/gems/code_teams@1.0.0.rbi
|
|
153
|
-
- sorbet/rbi/gems/parse_packwerk@0.
|
|
153
|
+
- sorbet/rbi/gems/parse_packwerk@0.12.0.rbi
|
|
154
154
|
- sorbet/rbi/manual.rbi
|
|
155
155
|
- sorbet/rbi/todo.rbi
|
|
156
156
|
homepage: https://github.com/rubyatscale/code_ownership
|
|
@@ -176,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
176
176
|
- !ruby/object:Gem::Version
|
|
177
177
|
version: '0'
|
|
178
178
|
requirements: []
|
|
179
|
-
rubygems_version: 3.
|
|
179
|
+
rubygems_version: 3.1.6
|
|
180
180
|
signing_key:
|
|
181
181
|
specification_version: 4
|
|
182
182
|
summary: A gem to help engineering teams declare ownership of code
|