code_ownership 1.34.2 → 1.36.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a67c504ef03f9010003b0c3fad3eb09aab223ed033d58a090c3a3efc3509a89
4
- data.tar.gz: 30aef026b8487bc1c20aa94890a4eca64fdaa26dddd44f17b77f705317c9b038
3
+ metadata.gz: e34134e28ee70e58344ea6d3f90be157bbdd6945888eee07d73abf3db3ed873b
4
+ data.tar.gz: 1875f6d3131505551c163aa03413edefa86c6f279d2c3189d9663a74a9603758
5
5
  SHA512:
6
- metadata.gz: '09d72749e78b49b14284c3360cba9f9f3dda4e20818305b3fe0a819bbaa2720be5107bb85d8612bedfbd5590a039010d42700510ac027b34bc1a6bdaadc7531f'
7
- data.tar.gz: 4be38dfd55b95eadf9ee661cb160715cc0ee7f3936f9b9b19d8b7ce65d0ed19b62805f882b8b684863acbc2caa602cb676e58837a060da04fd142f1b89b1e744
6
+ metadata.gz: de217fe525521395f2b6ad647257aecd69c9e8f8895fa0d466051cf4d365c3fce2dcf08ea9c9bfbeeef0c75116f3188d6f350c72becb965f7f3d49a992a0b6fa
7
+ data.tar.gz: abf92fcca79c8311cfa9d82fabcb0b129fdd076c9cb7a3db31c0db983dabf1e6cb5810b501e38cdab9fbf028dc3a43ed6105928469015b6bf30a33e774b5a6b6
data/README.md CHANGED
@@ -73,6 +73,12 @@ js_package_paths:
73
73
 
74
74
  This defaults `**/`, which makes it look for `package.json` files across your application.
75
75
 
76
+ > [!NOTE]
77
+ > Javscript package ownership does not respect `unowned_globs`. If you wish to disable usage of this feature you can set `js_package_paths` to an empty list.
78
+ ```yml
79
+ js_package_paths: []
80
+ ```
81
+
76
82
  ### Custom Ownership
77
83
  To enable custom ownership, you can inject your own custom classes into `code_ownership`.
78
84
  To do this, first create a class that adheres to the `CodeOwnership::Mapper` and/or `CodeOwnership::Validator` interface.
@@ -155,7 +161,7 @@ unowned_globs:
155
161
  - app/services/some_file2.rb
156
162
  - frontend/javascripts/**/__generated__/**/*
157
163
  ```
158
- You can call the validation function with the Ruby API
164
+ You can call the validation function with the Ruby API
159
165
  ```ruby
160
166
  CodeOwnership.validate!
161
167
  ```
@@ -11,6 +11,7 @@ module CodeOwnership
11
11
  const :unbuilt_gems_path, T.nilable(String)
12
12
  const :skip_codeowners_validation, T::Boolean
13
13
  const :raw_hash, T::Hash[T.untyped, T.untyped]
14
+ const :require_github_teams, T::Boolean
14
15
 
15
16
  sig { returns(Configuration) }
16
17
  def self.fetch
@@ -27,7 +28,8 @@ module CodeOwnership
27
28
  unowned_globs: config_hash.fetch('unowned_globs', []),
28
29
  js_package_paths: js_package_paths(config_hash),
29
30
  skip_codeowners_validation: config_hash.fetch('skip_codeowners_validation', false),
30
- raw_hash: config_hash
31
+ raw_hash: config_hash,
32
+ require_github_teams: config_hash.fetch('require_github_teams', false)
31
33
  )
32
34
  end
33
35
 
@@ -10,6 +10,8 @@ module CodeOwnership
10
10
  include Mapper
11
11
 
12
12
  CODEOWNERS_DIRECTORY_FILE_NAME = '.codeowner'
13
+ RELATIVE_ROOT = Pathname('.').freeze
14
+ ABSOLUTE_ROOT = Pathname('/').freeze
13
15
 
14
16
  @@directory_cache = T.let({}, T::Hash[String, T.nilable(CodeTeams::Team)]) # rubocop:disable Style/ClassVars
15
17
 
@@ -74,36 +76,46 @@ module CodeOwnership
74
76
  )
75
77
  end
76
78
 
77
- # takes a file and finds the relevant `.codeowner` file by walking up the directory
79
+ # Takes a file and finds the relevant `.codeowner` file by walking up the directory
78
80
  # structure. Example, given `a/b/c.rb`, this looks for `a/b/.codeowner`, `a/.codeowner`,
79
81
  # and `.codeowner` in that order, stopping at the first file to actually exist.
80
- # We do additional caching so that we don't have to check for file existence every time
82
+ # If the parovided file is a directory, it will look for `.codeowner` in that directory and then upwards.
83
+ # We do additional caching so that we don't have to check for file existence every time.
81
84
  sig { params(file: String).returns(T.nilable(CodeTeams::Team)) }
82
85
  def map_file_to_relevant_owner(file)
83
86
  file_path = Pathname.new(file)
84
- path_components = file_path.each_filename.to_a.map { |path| Pathname.new(path) }
87
+ team = T.let(nil, T.nilable(CodeTeams::Team))
85
88
 
86
- (path_components.length - 1).downto(0).each do |i|
87
- potential_relative_path_name = T.must(path_components[0...i]).reduce(Pathname.new('')) { |built_path, path| built_path.join(path) }
88
- potential_codeowners_file = potential_relative_path_name.join(CODEOWNERS_DIRECTORY_FILE_NAME)
89
+ if File.directory?(file)
90
+ team = get_team_from_codeowners_file_within_directory(file_path)
91
+ end
92
+
93
+ while team.nil? && file_path != RELATIVE_ROOT && file_path != ABSOLUTE_ROOT
94
+ file_path = file_path.parent
95
+ team = get_team_from_codeowners_file_within_directory(file_path)
96
+ end
97
+
98
+ team
99
+ end
89
100
 
90
- potential_codeowners_file_name = potential_codeowners_file.to_s
101
+ sig { params(directory: Pathname).returns(T.nilable(CodeTeams::Team)) }
102
+ def get_team_from_codeowners_file_within_directory(directory)
103
+ potential_codeowners_file = directory.join(CODEOWNERS_DIRECTORY_FILE_NAME)
91
104
 
92
- team = nil
93
- if @@directory_cache.key?(potential_codeowners_file_name)
94
- team = @@directory_cache[potential_codeowners_file_name]
95
- elsif potential_codeowners_file.exist?
96
- team = owner_for_codeowners_file(potential_codeowners_file)
105
+ potential_codeowners_file_name = potential_codeowners_file.to_s
97
106
 
98
- @@directory_cache[potential_codeowners_file_name] = team
99
- else
100
- @@directory_cache[potential_codeowners_file_name] = nil
101
- end
107
+ team = nil
108
+ if @@directory_cache.key?(potential_codeowners_file_name)
109
+ team = @@directory_cache[potential_codeowners_file_name]
110
+ elsif potential_codeowners_file.exist?
111
+ team = owner_for_codeowners_file(potential_codeowners_file)
102
112
 
103
- return team unless team.nil?
113
+ @@directory_cache[potential_codeowners_file_name] = team
114
+ else
115
+ @@directory_cache[potential_codeowners_file_name] = nil
104
116
  end
105
117
 
106
- nil
118
+ return team
107
119
  end
108
120
  end
109
121
  end
@@ -21,7 +21,7 @@ module CodeOwnership
21
21
 
22
22
  sig { override.params(teams: T::Array[CodeTeams::Team]).returns(T::Array[String]) }
23
23
  def self.validation_errors(teams)
24
- all_github_teams = teams.flat_map { |team| self.for(team).github.team }
24
+ all_github_teams = teams.flat_map { |team| self.for(team).github.team }.compact
25
25
 
26
26
  teams_used_more_than_once = all_github_teams.tally.select do |_team, count|
27
27
  count > 1
@@ -29,6 +29,18 @@ module CodeOwnership
29
29
 
30
30
  errors = T.let([], T::Array[String])
31
31
 
32
+ if require_github_teams?
33
+ missing_github_teams = teams.select { |team| self.for(team).github.team.nil? }
34
+
35
+ if missing_github_teams.any?
36
+ errors << <<~ERROR
37
+ The following teams are missing `github.team` entries:
38
+
39
+ #{missing_github_teams.map(&:config_yml).join("\n")}
40
+ ERROR
41
+ end
42
+ end
43
+
32
44
  if teams_used_more_than_once.any?
33
45
  errors << <<~ERROR
34
46
  The following teams are specified multiple times:
@@ -40,6 +52,11 @@ module CodeOwnership
40
52
 
41
53
  errors
42
54
  end
55
+
56
+ sig { returns(T::Boolean) }
57
+ def self.require_github_teams?
58
+ CodeOwnership.configuration.require_github_teams
59
+ end
43
60
  end
44
61
  end
45
62
  end
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.34.2
4
+ version: 1.36.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: 2023-08-09 00:00:00.000000000 Z
11
+ date: 2023-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams