code_ownership 2.0.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83f3c317cdf7db45eb48b216fc86fc03ba101c8e87ebe2e6b2ec0a27b23f7b5b
4
- data.tar.gz: ea97e16de10c95494e4e7ea334cbe78c81e2c7589607afe7a6afe967b818dda5
3
+ metadata.gz: 523b1156430e253b991257fdada359ab8f0dddec015984c7994c925205412b33
4
+ data.tar.gz: 7033d3b132d70101d78d4ff10602953e0c17bbfa14e830cabe68ce81ea49405d
5
5
  SHA512:
6
- metadata.gz: 9642faa88fea498efd7e7d504f45bc9f092aad4e736d77dc551c6d36d11569ecf4f3f62074fbd69533a721799ad5e822ad87b855fe9e8e12cfb0e0b557d8783e
7
- data.tar.gz: 6a783844309b3fec6bdd704c05ce142bb67dea227f2865eb487a1fe1e988025a813f65144dcf58c33c7d82be7425e9984d00713236f9c5e3999b2ed0d611ed7f
6
+ metadata.gz: 8dae151b7c6b4e9ef31a632c9e77fae434ec6620df11f15b65f5e5e88b200f95740972cf6b3ccf87d941564dbe16a04cd3a65f7c31d2cfb5778eda211db4d00c
7
+ data.tar.gz: 8cf1f5ae34760260a9ff460684cafad0e8810911bda5cf211fd423582c02fe421bdf0ec33425eebe39e18963361a8150f61ad4e32a78d4af7a323ee052a8f7f2
data/README.md CHANGED
@@ -6,13 +6,14 @@ Check out [`lib/code_ownership.rb`](https://github.com/rubyatscale/code_ownershi
6
6
 
7
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
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
+ There is also a [companion VSCode Extension](https://github.com/rubyatscale/code-ownership-vscode) for this gem. Just search `Gusto.code-ownership-vscode` in the VSCode Extension Marketplace.
10
10
 
11
11
  ## Getting started
12
12
 
13
13
  To get started there's a few things you should do.
14
14
 
15
- 1) Create a `config/code_ownership.yml` file and declare where your files live. Here's a sample to start with:
15
+ 1. Create a `config/code_ownership.yml` file and declare where your files live. Here's a sample to start with:
16
+
16
17
  ```yml
17
18
  owned_globs:
18
19
  - '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
@@ -23,33 +24,42 @@ unowned_globs:
23
24
  - app/services/some_file2.rb
24
25
  - frontend/javascripts/**/__generated__/**/*
25
26
  ```
26
- 2) Declare some teams. Here's an example, that would live at `config/teams/operations.yml`:
27
+
28
+ 2. Declare some teams. Here's an example, that would live at `config/teams/operations.yml`:
29
+
27
30
  ```yml
28
31
  name: Operations
29
32
  github:
30
33
  team: '@my-org/operations-team'
31
34
  ```
32
- 3) Declare ownership. You can do this at a directory level or at a file level. All of the files within the `owned_globs` you declared in step 1 will need to have an owner assigned (or be opted out via `unowned_globs`). See the next section for more detail.
33
- 4) Run validations when you commit, and/or in CI. If you run validations in CI, ensure that if your `.github/CODEOWNERS` file gets changed, that gets pushed to the PR.
35
+
36
+ 3. Declare ownership. You can do this at a directory level or at a file level. All of the files within the `owned_globs` you declared in step 1 will need to have an owner assigned (or be opted out via `unowned_globs`). See the next section for more detail.
37
+ 4. Run validations when you commit, and/or in CI. If you run validations in CI, ensure that if your `.github/CODEOWNERS` file gets changed, that gets pushed to the PR.
34
38
 
35
39
  ## Usage: Declaring Ownership
36
40
 
37
41
  There are five ways to declare code ownership using this gem:
38
42
 
39
43
  ### Directory-Based Ownership
44
+
40
45
  Directory based ownership allows for all files in that directory and all its sub-directories to be owned by one team. To define this, add a `.codeowner` file inside that directory with the name of the team as the contents of that file.
46
+
41
47
  ```
42
48
  Team
43
49
  ```
44
50
 
45
51
  ### File-Annotation Based Ownership
52
+
46
53
  File annotations are a last resort if there is no clear home for your code. File annotations go at the top of your file, and look like this:
54
+
47
55
  ```ruby
48
56
  # @team MyTeam
49
57
  ```
50
58
 
51
59
  ### Package-Based Ownership
60
+
52
61
  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:
62
+
53
63
  ```yml
54
64
  enforce_dependency: true
55
65
  enforce_privacy: true
@@ -58,16 +68,19 @@ metadata:
58
68
  ```
59
69
 
60
70
  You can also define `owner` as a top-level key, e.g.
71
+
61
72
  ```yml
62
73
  enforce_dependency: true
63
74
  enforce_privacy: true
64
75
  owner: Team
65
76
  ```
66
77
 
67
- To do this, add `code_ownership` to the `require` key of your `packwerk.yml`. See https://github.com/Shopify/packwerk/blob/main/USAGE.md#loading-extensions for more information.
78
+ To do this, add `code_ownership` to the `require` key of your `packwerk.yml`. See <https://github.com/Shopify/packwerk/blob/main/USAGE.md#loading-extensions> for more information.
68
79
 
69
80
  ### Glob-Based Ownership
81
+
70
82
  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`:
83
+
71
84
  ```yml
72
85
  name: My Team
73
86
  owned_globs:
@@ -78,6 +91,7 @@ unowned_globs:
78
91
  ```
79
92
 
80
93
  ### Javascript Package Ownership
94
+
81
95
  Javascript package based ownership allows you to specify an ownership key in a `package.json`. To use this, configure your `package.json` like this:
82
96
 
83
97
  ```json
@@ -91,6 +105,7 @@ Javascript package based ownership allows you to specify an ownership key in a `
91
105
  ```
92
106
 
93
107
  You can also tell `code_ownership` where to find JS packages in the configuration, like this:
108
+
94
109
  ```yml
95
110
  js_package_paths:
96
111
  - frontend/javascripts/packages/*
@@ -101,12 +116,15 @@ This defaults `**/`, which makes it look for `package.json` files across your ap
101
116
 
102
117
  > [!NOTE]
103
118
  > 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.
119
+
104
120
  ```yml
105
121
  js_package_paths: []
106
122
  ```
107
123
 
108
124
  ## Usage: Reading CodeOwnership
125
+
109
126
  ### `for_file`
127
+
110
128
  `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.
111
129
 
112
130
  ```ruby
@@ -118,6 +136,7 @@ Contributor note: If you are making updates to this method or the methods gettin
118
136
  See `code_ownership_spec.rb` for examples.
119
137
 
120
138
  ### `for_backtrace`
139
+
121
140
  `CodeOwnership.for_backtrace` can be given a backtrace and will either return `nil`, or a `CodeTeams::Team`.
122
141
 
123
142
  ```ruby
@@ -141,19 +160,22 @@ Under the hood, this finds the file where the class is defined and returns the o
141
160
  See `code_ownership_spec.rb` for an example.
142
161
 
143
162
  ### `for_team`
163
+
144
164
  `CodeOwnership.for_team` can be used to generate an ownership report for a team.
165
+
145
166
  ```ruby
146
167
  CodeOwnership.for_team('My Team')
147
168
  ```
148
169
 
149
170
  You can shovel this into a markdown file for easy viewing using the CLI:
171
+
150
172
  ```
151
- bin/codeownership for_team 'My Team' > tmp/ownership_report.md
173
+ codeownership for_team 'My Team' > tmp/ownership_report.md
152
174
  ```
153
175
 
154
176
  ## Usage: Generating a `CODEOWNERS` file
155
177
 
156
- 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.
178
+ A `CODEOWNERS` file defines who owns specific files or paths in a repository. When you run `codeownership validate`, a `.github/CODEOWNERS` file will automatically be generated and updated.
157
179
 
158
180
  If `codeowners_path` is set in `code_ownership.yml` codeowners will use that path to generate the `CODEOWNERS` file. For example, `codeowners_path: docs` will generate `docs/CODEOWNERS`.
159
181
 
@@ -161,14 +183,15 @@ If `codeowners_path` is set in `code_ownership.yml` codeowners will use that pat
161
183
 
162
184
  CodeOwnership comes with a validation function to ensure the following things are true:
163
185
 
164
- 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.
165
- 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`).
166
- 3) All files have ownership. You can specify in `unowned_globs` to represent a TODO list of files to add ownership to.
167
- 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 `config/code_ownership.yml`.
186
+ 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.
187
+ 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`).
188
+ 3. All files have ownership. You can specify in `unowned_globs` to represent a TODO list of files to add ownership to.
189
+ 4. 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 `config/code_ownership.yml`.
168
190
 
169
191
  CodeOwnership also allows you to specify which globs and file extensions should be considered ownable.
170
192
 
171
193
  Here is an example `config/code_ownership.yml`.
194
+
172
195
  ```yml
173
196
  owned_globs:
174
197
  - '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
@@ -178,13 +201,24 @@ unowned_globs:
178
201
  - app/services/some_file2.rb
179
202
  - frontend/javascripts/**/__generated__/**/*
180
203
  ```
204
+
181
205
  You can call the validation function with the Ruby API
206
+
182
207
  ```ruby
183
208
  CodeOwnership.validate!
184
209
  ```
210
+
185
211
  or the CLI
186
- ```
187
- bin/codeownership validate
212
+
213
+ ```bash
214
+ # Validate all files
215
+ codeownership validate
216
+
217
+ # Validate specific files
218
+ codeownership validate path/to/file1.rb path/to/file2.rb
219
+
220
+ # Validate only staged files
221
+ codeownership validate --diff
188
222
  ```
189
223
 
190
224
  ## Development
@@ -192,10 +226,12 @@ bin/codeownership validate
192
226
  Please add to `CHANGELOG.md` and this `README.md` when you make make changes.
193
227
 
194
228
  ## Running specs
229
+
195
230
  ```sh
196
231
  bundle install
197
232
  bundle exec rake
198
233
  ```
199
234
 
200
235
  ## Creating a new release
236
+
201
237
  Simply [create a new release](https://github.com/rubyatscale/code_ownership/releases/new) with github. The release tag must match the gem version
@@ -37,7 +37,7 @@ module CodeOwnership
37
37
  options = {}
38
38
 
39
39
  parser = OptionParser.new do |opts|
40
- opts.banner = "Usage: #{EXECUTABLE} validate [options]"
40
+ opts.banner = "Usage: #{EXECUTABLE} validate [options] [files...]"
41
41
 
42
42
  opts.on('--skip-autocorrect', 'Skip automatically correcting any errors, such as the .github/CODEOWNERS file') do
43
43
  options[:skip_autocorrect] = true
@@ -59,11 +59,22 @@ module CodeOwnership
59
59
  args = parser.order!(argv)
60
60
  parser.parse!(args)
61
61
 
62
- files = if options[:diff]
62
+ # Collect any remaining arguments as file paths
63
+ specified_files = argv.reject { |arg| arg.start_with?('--') }
64
+
65
+ files = if !specified_files.empty?
66
+ # Files explicitly provided on command line
67
+ if options[:diff]
68
+ warn 'Warning: Ignoring --diff flag because explicit files were provided'
69
+ end
70
+ specified_files.select { |file| File.exist?(file) }
71
+ elsif options[:diff]
72
+ # Staged files from git
63
73
  ENV.fetch('CODEOWNERS_GIT_STAGED_FILES') { `git diff --staged --name-only` }.split("\n").select do |file|
64
74
  File.exist?(file)
65
75
  end
66
76
  else
77
+ # No files specified, validate all
67
78
  nil
68
79
  end
69
80
 
@@ -6,6 +6,7 @@ module CodeOwnership
6
6
  module Private
7
7
  class ForFileOutputBuilder
8
8
  extend T::Sig
9
+
9
10
  private_class_method :new
10
11
 
11
12
  sig { params(file_path: String, json: T::Boolean, verbose: T::Boolean).void }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module CodeOwnership
5
- VERSION = '2.0.0'
5
+ VERSION = '2.1.0'
6
6
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  # typed: strict
4
4
 
5
- require 'set'
6
5
  require 'code_teams'
7
6
  require 'sorbet-runtime'
8
7
  require 'json'
@@ -239,6 +238,57 @@ module CodeOwnership
239
238
  end
240
239
  end
241
240
 
241
+ # Removes the file annotation (e.g., "# @team TeamName") from a file.
242
+ #
243
+ # This method removes the ownership annotation from the first line of a file,
244
+ # which is typically used to declare team ownership at the file level.
245
+ # The annotation can be in the form of:
246
+ # - Ruby comments: # @team TeamName
247
+ # - JavaScript/TypeScript comments: // @team TeamName
248
+ # - YAML comments: -# @team TeamName
249
+ #
250
+ # If the file does not have an annotation or the annotation doesn't match a valid team,
251
+ # this method does nothing.
252
+ #
253
+ # @param filename [String] The path to the file from which to remove the annotation.
254
+ # Can be relative or absolute.
255
+ #
256
+ # @return [void]
257
+ #
258
+ # @example Remove annotation from a Ruby file
259
+ # # Before: File contains "# @team Platform\nclass User; end"
260
+ # CodeOwnership.remove_file_annotation!('app/models/user.rb')
261
+ # # After: File contains "class User; end"
262
+ #
263
+ # @example Remove annotation from a JavaScript file
264
+ # # Before: File contains "// @team Frontend\nexport default function() {}"
265
+ # CodeOwnership.remove_file_annotation!('app/javascript/component.js')
266
+ # # After: File contains "export default function() {}"
267
+ #
268
+ # @note This method modifies the file in place.
269
+ # @note Leading newlines after the annotation are also removed to maintain clean formatting.
270
+ #
271
+ sig { params(filename: String).void }
272
+ def remove_file_annotation!(filename)
273
+ filepath = Pathname.new(filename)
274
+
275
+ begin
276
+ content = filepath.read
277
+ rescue Errno::EISDIR, Errno::ENOENT
278
+ # Ignore files that fail to read (directories, missing files, etc.)
279
+ return
280
+ end
281
+
282
+ # Remove the team annotation and any trailing newlines after it
283
+ team_pattern = %r{\A(?:#|//|-#) @team .*\n+}
284
+ new_content = content.sub(team_pattern, '')
285
+
286
+ filepath.write(new_content) if new_content != content
287
+ rescue ArgumentError => e
288
+ # Handle invalid byte sequences gracefully
289
+ raise unless e.message.include?('invalid byte sequence')
290
+ end
291
+
242
292
  # Given a backtrace from either `Exception#backtrace` or `caller`, find the
243
293
  # first line that corresponds to a file with assigned ownership
244
294
  sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::CodeTeams::Team]).returns(T.nilable(::CodeTeams::Team)) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_ownership
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers