virtual-code-owners 6.2.0 → 7.0.1
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.
- package/README.md +125 -110
- package/dist/cli.js +84 -3
- package/dist/{generate-codeowners.js → codeowners/generate.js} +2 -2
- package/dist/main.js +5 -87
- package/dist/run-cli.js +3 -0
- package/dist/{read-team-map.js → team-map/read.js} +2 -2
- package/dist/team-map/virtual-teams.schema.js +15 -0
- package/dist/version.js +1 -1
- package/dist/virtual-code-owners/anomalies.js +31 -0
- package/dist/{parse-virtual-code-owners.js → virtual-code-owners/parse.js} +2 -33
- package/dist/{read-virtual-code-owners.js → virtual-code-owners/read.js} +2 -1
- package/dist/virtual-teams.schema.json +1 -1
- package/package.json +6 -63
- package/types/generate-codeowners.d.ts +0 -6
- package/types/generate-labeler-yml.d.ts +0 -6
- package/types/parse-virtual-code-owners.d.ts +0 -18
- package/types/types.d.ts +0 -45
- /package/dist/{generate-labeler-yml.js → labeler-yml/generate.js} +0 -0
package/README.md
CHANGED
|
@@ -1,82 +1,34 @@
|
|
|
1
1
|
## What?
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This generates your `CODEOWNERS` file (_patterns_ x _users_) from
|
|
4
4
|
|
|
5
|
-
- VIRTUAL-CODEOWNERS.txt
|
|
6
|
-
- a `virtual-teams.yml`
|
|
5
|
+
- a `VIRTUAL-CODEOWNERS.txt` (_patterns_ x _teams_)
|
|
6
|
+
- a `virtual-teams.yml` (_teams_ x _users_)
|
|
7
7
|
|
|
8
|
-
...
|
|
8
|
+
... which makes it easier to keep `CODEOWNERS` in sync on multi-team mono repos.
|
|
9
|
+
When those teams are not defined on GitHub level.
|
|
9
10
|
|
|
10
11
|
## Usage
|
|
11
12
|
|
|
12
13
|
- Rename your `.github/CODEOWNERS` to `.github/VIRTUAL-CODEOWNERS.txt` and put team names in them.
|
|
13
|
-
-
|
|
14
|
+
- Define teams that don't (yet) exist on GitHub level in `.github/virtual-teams.yml`
|
|
14
15
|
- Run this:
|
|
15
16
|
|
|
16
17
|
```
|
|
17
18
|
npx virtual-code-owners
|
|
19
|
+
# Wrote '.github/CODEOWNERS'
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
npx virtual-code-owners \
|
|
24
|
-
--virtualCodeOwners .github/VIRTUAL-CODEOWNERS.txt \
|
|
25
|
-
--virtualTeams .github/virtual-teams.yml \
|
|
26
|
-
--codeOwners .github/CODEOWNERS
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Why?
|
|
30
|
-
|
|
31
|
-
Organizations sometimes have large mono repositories with loads of code owners.
|
|
32
|
-
They or their bureaucracy haven't landed on actually using GitHub teams to clearly
|
|
33
|
-
demarcate that. Teams in those organizations who want to have clear code ownership
|
|
34
|
-
have the following choices:
|
|
35
|
-
|
|
36
|
-
- Wrestle the bureaucracy.
|
|
37
|
-
This is the recommended approach. It might take a while, though - and even
|
|
38
|
-
though there are good people on many levels in bureaucracies, it might
|
|
39
|
-
eventually not pan out because #reasons.
|
|
40
|
-
- Maintain a CODEOWNERS file with code assigned to large lists of individuals.
|
|
41
|
-
An option, but laborious to maintain, even for smaller projects; for example:
|
|
42
|
-
|
|
43
|
-
```CODEOWNERS
|
|
44
|
-
# catch-all to ensure there at least _is_ a code owner, even when
|
|
45
|
-
# it's _everyone_
|
|
46
|
-
|
|
47
|
-
* @cloud-heroes-all
|
|
48
|
-
|
|
49
|
-
# admin & ci stuff => transversal
|
|
50
|
-
|
|
51
|
-
.github/ @abraham-lincoln @benjamin-franklin @koos-koets @luke-the-lucky-ch @mary-the-merry-ch @naomi-the-namegiver-ch
|
|
52
|
-
|
|
53
|
-
# generic stuff
|
|
54
|
-
|
|
55
|
-
apps/framework/ @abraham-lincoln @benjamin-franklin @koos-koets @luke-the-lucky-ch @mary-the-merry-ch @naomi-the-namegiver-ch
|
|
56
|
-
apps/ux-portal/ @abraham-lincoln @benjamin-franklin @charlotte-de-bourbon-ch @davy-davidson-ch @joe-dalton-ch @john-johnson-ch @koos-koets @luke-the-lucky-ch @mary-the-merry-ch @naomi-the-namegiver-ch
|
|
57
|
-
libs/components/ @charlotte-de-bourbon-ch @davy-davidson-ch @joe-dalton-ch @john-johnson-ch @koos-koets
|
|
58
|
-
|
|
59
|
-
# specific functionality
|
|
60
|
-
|
|
61
|
-
libs/ubc-sales/ @abraham-ableton-ch @boris-bubbleblower-ch @charlotte-charleston-ch @dagny-taggert-ch @gregory-gregson-ch @jane-doe-ch @karl-marx-ch
|
|
62
|
-
libs/ubc-after-sales/ @daisy-duck @donald-duck @john-doe-ch @pete-peterson-ch @william-the-fourth-ch
|
|
63
|
-
libs/ubc-pre-sales/ @averel-dalton-ch @jean-claude-ch @john-galt-ch @valerie-valerton-ch
|
|
64
|
-
libs/ubc-refund/ @abraham-ableton-ch @boris-bubbleblower-ch @charlotte-charleston-ch @dagny-taggert-ch @daisy-duck @donald-duck @gregory-gregson-ch @jane-doe-ch @john-doe-ch @karl-marx-ch @pete-peterson-ch @william-the-fourth-ch
|
|
65
|
-
libs/ubc-baarden/ jan@example.com korneel@example.com pier@example.com tjorus@example.com
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
This is where `virtual-code-owners` comes in.
|
|
22
|
+
- :sparkles:
|
|
69
23
|
|
|
70
24
|
## Formats
|
|
71
25
|
|
|
72
26
|
### VIRTUAL-CODEOWNERS.txt
|
|
73
27
|
|
|
74
|
-
`VIRTUAL-CODEOWNERS.txt`
|
|
75
|
-
|
|
76
|
-
the _teams_ the former uses might not exist yet, except in a `virtual-teams.yml`.
|
|
77
|
-
This enables you to write a _much_ easier to maintain list of code owners.
|
|
28
|
+
`VIRTUAL-CODEOWNERS.txt` sticks to the [CODEOWNERS](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) format,
|
|
29
|
+
but adds the ability to include teams defined in `virtual-teams.yml`.
|
|
78
30
|
|
|
79
|
-
For example
|
|
31
|
+
For example a CODEOWNERS file can look like this:
|
|
80
32
|
|
|
81
33
|
```CODEOWNERS
|
|
82
34
|
#! comments that start with #! won't appear in the CODEOWNERS output
|
|
@@ -108,11 +60,14 @@ libs/ubc-refund/ @ch/sales @ch/after-sales
|
|
|
108
60
|
libs/ubc-baarden/ @ch/mannen-met-baarden
|
|
109
61
|
```
|
|
110
62
|
|
|
63
|
+
... where only the @cloud-heroes-all is a 'real' team on GitHub level. The other
|
|
64
|
+
ones are defined in `virtual-teams.yml`.
|
|
65
|
+
|
|
111
66
|
### virtual-teams.yml
|
|
112
67
|
|
|
113
|
-
A valid YAML file that contains a list of teams
|
|
114
|
-
|
|
115
|
-
to
|
|
68
|
+
A valid YAML file that contains a list of teams and their members.
|
|
69
|
+
Update it whenever you have new team members and run `npx virtual-code-owners`
|
|
70
|
+
to keep CODEOWNERS current.
|
|
116
71
|
|
|
117
72
|
```yaml
|
|
118
73
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/sverweij/virtual-code-owners/main/src/virtual-teams.schema.json
|
|
@@ -148,61 +103,58 @@ ch/transversal:
|
|
|
148
103
|
- benjamin-franklin
|
|
149
104
|
- koos-koets
|
|
150
105
|
- abraham-lincoln
|
|
106
|
+
ch/mannen-met-baarden:
|
|
107
|
+
- jan@example.com
|
|
108
|
+
- pier@example.com
|
|
109
|
+
- tjorus@example.com
|
|
110
|
+
- korneel@example.com
|
|
151
111
|
```
|
|
152
112
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
### Can I mix real and virtual teams in `VIRTUAL-CODEOWNERS.txt`?
|
|
113
|
+
### CODEOWNERS
|
|
156
114
|
|
|
157
|
-
|
|
115
|
+
Running `npx virtual-code-owners` will combine these into a CODEOWNERS file like this:
|
|
158
116
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
117
|
+
```CODEOWNERS
|
|
118
|
+
#
|
|
119
|
+
# DO NOT EDIT - this file is generated and your edits will be overwritten
|
|
120
|
+
#
|
|
121
|
+
# To make changes:
|
|
122
|
+
#
|
|
123
|
+
# - edit .github/VIRTUAL-CODEOWNERS.txt
|
|
124
|
+
# - and/ or add team members to .github/virtual-teams.yml
|
|
125
|
+
# - run 'npx virtual-code-owners' (or 'npx virtual-code-owners --emitLabeler' if you also
|
|
126
|
+
# want to generate a .github/labeler.yml)
|
|
127
|
+
#
|
|
166
128
|
|
|
167
|
-
|
|
168
|
-
|
|
129
|
+
# catch-all to ensure there at least _is_ a code owner, even when
|
|
130
|
+
# it's _everyone_
|
|
169
131
|
|
|
170
|
-
|
|
132
|
+
* @cloud-heroes-all
|
|
171
133
|
|
|
172
|
-
|
|
134
|
+
# admin & ci stuff => transversal
|
|
173
135
|
|
|
174
|
-
-
|
|
175
|
-
aren't an e-mail address)
|
|
176
|
-
- it will find invalid 'rules'; which is the case when there is a file pattern on
|
|
177
|
-
the line, but no user or team names.
|
|
136
|
+
.github/ @abraham-lincoln @benjamin-franklin @koos-koets @luke-the-lucky-ch @mary-the-merry-ch @naomi-the-namegiver-ch
|
|
178
137
|
|
|
179
|
-
|
|
180
|
-
with the location of the error and exit with a non-zero code, to prevent the
|
|
181
|
-
creation of a potentially invalid CODEOWNERS file.
|
|
138
|
+
# generic stuff
|
|
182
139
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
140
|
+
apps/framework/ @abraham-lincoln @benjamin-franklin @koos-koets @luke-the-lucky-ch @mary-the-merry-ch @naomi-the-namegiver-ch
|
|
141
|
+
apps/ux-portal/ @abraham-lincoln @benjamin-franklin @charlotte-de-bourbon-ch @davy-davidson-ch @joe-dalton-ch @john-johnson-ch @koos-koets @luke-the-lucky-ch @mary-the-merry-ch @naomi-the-namegiver-ch
|
|
142
|
+
libs/components/ @charlotte-de-bourbon-ch @davy-davidson-ch @joe-dalton-ch @john-johnson-ch @koos-koets
|
|
186
143
|
|
|
187
|
-
|
|
144
|
+
# specific functionality
|
|
188
145
|
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
virtual-teams.yml actually exist.
|
|
146
|
+
libs/ubc-sales/ @abraham-ableton-ch @boris-bubbleblower-ch @charlotte-charleston-ch @dagny-taggert-ch @gregory-gregson-ch @jane-doe-ch @karl-marx-ch
|
|
147
|
+
libs/ubc-after-sales/ @daisy-duck @donald-duck @john-doe-ch @pete-peterson-ch @william-the-fourth-ch
|
|
148
|
+
libs/ubc-pre-sales/ @averel-dalton-ch @jean-claude-ch @john-galt-ch @valerie-valerton-ch
|
|
149
|
+
libs/ubc-refund/ @abraham-ableton-ch @boris-bubbleblower-ch @charlotte-charleston-ch @dagny-taggert-ch @daisy-duck @donald-duck @gregory-gregson-ch @jane-doe-ch @john-doe-ch @karl-marx-ch @pete-peterson-ch @william-the-fourth-ch
|
|
150
|
+
libs/ubc-baarden/ jan@example.com korneel@example.com pier@example.com tjorus@example.com
|
|
151
|
+
```
|
|
196
152
|
|
|
197
|
-
|
|
153
|
+
## FAQ
|
|
198
154
|
|
|
199
|
-
|
|
200
|
-
to be markdown, and will auto format them as such - making for either very ugly
|
|
201
|
-
or in worst cases invalid CODEOWNERS files. Usually such autoformatting is not
|
|
202
|
-
present on text files.
|
|
155
|
+
### Any gotcha's?
|
|
203
156
|
|
|
204
|
-
|
|
205
|
-
doesn't seem to be happening over there.
|
|
157
|
+
It won't check whether the users or teams you entered exist.
|
|
206
158
|
|
|
207
159
|
### Do I have to run this each time I edit `VIRTUAL-CODEOWNERS.txt`?
|
|
208
160
|
|
|
@@ -220,14 +172,27 @@ like this:
|
|
|
220
172
|
}
|
|
221
173
|
```
|
|
222
174
|
|
|
223
|
-
###
|
|
175
|
+
### Can I mix real and virtual teams in `VIRTUAL-CODEOWNERS.txt`?
|
|
176
|
+
|
|
177
|
+
Yes.
|
|
178
|
+
|
|
179
|
+
It might be you already have a team or two defined, but just want to use
|
|
180
|
+
_additional_ teams. In that case just don't specify the already-defined teams
|
|
181
|
+
in `virtual-teams.yml` and _virtual-code-owners_ will leave them alone.
|
|
182
|
+
|
|
183
|
+
### Can I still use usernames in `VIRTUAL-CODEOWNERS.txt`?
|
|
184
|
+
|
|
185
|
+
Yes.
|
|
186
|
+
|
|
187
|
+
Just make sure there's no name clashes between the username and a (virtual)
|
|
188
|
+
team name and _virtual-code-owners_ will leave the real name alone.
|
|
189
|
+
|
|
190
|
+
### Can I automatically label PR's for virtual teams?
|
|
224
191
|
|
|
225
|
-
|
|
192
|
+
Yep.
|
|
226
193
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
sync with the virtual-teams and virtual code-owners files manually is a bit of
|
|
230
|
-
a chore, though, so `virtual-code-owners` has an option to automate that.
|
|
194
|
+
Use [actions/labeler](https://github.com/actions/labeler) and tickle
|
|
195
|
+
`virtual-code-owners` to generate the labeler config file:
|
|
231
196
|
|
|
232
197
|
```sh
|
|
233
198
|
npx virtual-code-owners --emitLabeler
|
|
@@ -237,12 +202,62 @@ npx virtual-code-owners --emitLabeler
|
|
|
237
202
|
If you have an alternate file location for the `labeler.yml` you can specify that
|
|
238
203
|
with virtual-code-owner's `--labelerLocation` parameter.
|
|
239
204
|
|
|
240
|
-
###
|
|
205
|
+
### What validations does virtual-code-owners perform?
|
|
206
|
+
|
|
207
|
+
virtual-code-owners checks for basic CODEOWNERS format errors and invalid
|
|
208
|
+
user/team names but doesn't verify their existence in the project.
|
|
209
|
+
|
|
210
|
+
- valid user/team names start with an `@` or are an e-mail address
|
|
211
|
+
- valid rules have a file pattern and at least one user/team name
|
|
212
|
+
|
|
213
|
+
### I want to specify different locations for the files (e.g. because I'm using GitLab)
|
|
214
|
+
|
|
215
|
+
Here you go:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
npx virtual-code-owners \
|
|
219
|
+
--virtualCodeOwners .gitlab/VIRTUAL-CODEOWNERS.txt \
|
|
220
|
+
--virtualTeams .gitlab/virtual-teams.yml \
|
|
221
|
+
--codeOwners .gitlab/CODEOWNERS
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Can I just validate VIRTUAL-CODEOWNERS.txt & virtual-teams.yml without generating output?
|
|
241
225
|
|
|
242
226
|
So _without_ generating any output?
|
|
243
227
|
|
|
244
|
-
|
|
228
|
+
Sure thing. Use `--dryRun`:
|
|
245
229
|
|
|
246
230
|
```
|
|
247
231
|
npx virtual-code-owners --dryRun
|
|
248
232
|
```
|
|
233
|
+
|
|
234
|
+
### Why the `.txt` extension?
|
|
235
|
+
|
|
236
|
+
It keeps editors and IDE's from messing up your formatting.
|
|
237
|
+
|
|
238
|
+
Various editors assume an ALL_CAPS file name with `#` characters on various lines
|
|
239
|
+
to be markdown, and will auto format them as such. This makes for either very ugly
|
|
240
|
+
or in worst cases invalid CODEOWNERS files. Usually such autoformatting is not
|
|
241
|
+
present on text files.
|
|
242
|
+
|
|
243
|
+
Apparently these editors know about CODEOWNERS, though, so they don't mess with
|
|
244
|
+
the formatting of _those_.
|
|
245
|
+
|
|
246
|
+
### Why does this exist at all? Why not just use GitHub teams?
|
|
247
|
+
|
|
248
|
+
You should _totally_ use GitHub teams! If you can.
|
|
249
|
+
|
|
250
|
+
Organizations sometimes have large mono repositories with loads of code owners.
|
|
251
|
+
They or their bureaucracy haven't landed on actually using GitHub teams to clearly
|
|
252
|
+
demarcate that. Or you're working on a cross-functional team that doesn't follow
|
|
253
|
+
the organization chart (and hence the GitHub teams). Teams in those organizations
|
|
254
|
+
who want to have clear code ownership have the following choices:
|
|
255
|
+
|
|
256
|
+
- Wrestle the bureaucracy.
|
|
257
|
+
This is the recommended approach. It might take a while, though - and even
|
|
258
|
+
though there are good people on many levels in bureaucracies, it might
|
|
259
|
+
eventually not pan out because #reasons.
|
|
260
|
+
- Maintain a CODEOWNERS file with code assigned to large lists of individuals.
|
|
261
|
+
An option, but laborious to maintain, even for smaller projects
|
|
262
|
+
|
|
263
|
+
This is where `virtual-code-owners` comes in.
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { EOL } from "node:os";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { main } from "./main.js";
|
|
4
|
+
import { VERSION } from "./version.js";
|
|
5
|
+
const HELP_MESSAGE = `Usage: virtual-code-owners [options]
|
|
6
|
+
|
|
7
|
+
Merges a VIRTUAL-CODEOWNERS.txt and a virtual-teams.yml into CODEOWNERS
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
-V, --version output the version number
|
|
11
|
+
-v, --virtualCodeOwners [file-name] A CODEOWNERS file with team names in them
|
|
12
|
+
that are defined in a virtual teams file
|
|
13
|
+
(default: ".github/VIRTUAL-CODEOWNERS.txt")
|
|
14
|
+
-t, --virtualTeams [file-name] A YAML file listing teams and their
|
|
15
|
+
members
|
|
16
|
+
(default: ".github/virtual-teams.yml")
|
|
17
|
+
-c, --codeOwners [file-name] The location of the CODEOWNERS file
|
|
18
|
+
(default: ".github/CODEOWNERS")
|
|
19
|
+
-l, --emitLabeler Whether or not to emit a labeler.yml to be
|
|
20
|
+
used with actions/labeler
|
|
21
|
+
(default: false)
|
|
22
|
+
--labelerLocation [file-name] The location of the labeler.yml file
|
|
23
|
+
(default: ".github/labeler.yml")
|
|
24
|
+
--dryRun Just validate inputs, don't generate
|
|
25
|
+
outputs (default: false)
|
|
26
|
+
-h, --help display help for command`;
|
|
27
|
+
export function cli(pArguments = process.argv.slice(2), pOutStream = process.stdout, pErrorStream = process.stderr, pErrorExitCode = 1) {
|
|
28
|
+
try {
|
|
29
|
+
const lOptions = getOptions(pArguments);
|
|
30
|
+
if (lOptions.help) {
|
|
31
|
+
pOutStream.write(`${HELP_MESSAGE}${EOL}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (lOptions.version) {
|
|
35
|
+
pOutStream.write(`${VERSION}${EOL}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
main(lOptions, pErrorStream);
|
|
39
|
+
}
|
|
40
|
+
catch (pError) {
|
|
41
|
+
pErrorStream.write(`${EOL}ERROR: ${pError.message}${EOL}${EOL}`);
|
|
42
|
+
process.exitCode = pErrorExitCode;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getOptions(pArguments) {
|
|
46
|
+
return parseArgs({
|
|
47
|
+
args: pArguments,
|
|
48
|
+
options: {
|
|
49
|
+
virtualCodeOwners: {
|
|
50
|
+
type: "string",
|
|
51
|
+
short: "v",
|
|
52
|
+
default: ".github/VIRTUAL-CODEOWNERS.txt",
|
|
53
|
+
},
|
|
54
|
+
virtualTeams: {
|
|
55
|
+
type: "string",
|
|
56
|
+
short: "t",
|
|
57
|
+
default: ".github/virtual-teams.yml",
|
|
58
|
+
},
|
|
59
|
+
codeOwners: {
|
|
60
|
+
type: "string",
|
|
61
|
+
short: "c",
|
|
62
|
+
default: ".github/CODEOWNERS",
|
|
63
|
+
},
|
|
64
|
+
emitLabeler: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
short: "l",
|
|
67
|
+
default: false,
|
|
68
|
+
},
|
|
69
|
+
labelerLocation: {
|
|
70
|
+
type: "string",
|
|
71
|
+
default: ".github/labeler.yml",
|
|
72
|
+
},
|
|
73
|
+
dryRun: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
help: { type: "boolean", short: "h", default: false },
|
|
78
|
+
version: { type: "boolean", short: "V", default: false },
|
|
79
|
+
},
|
|
80
|
+
strict: true,
|
|
81
|
+
allowPositionals: true,
|
|
82
|
+
tokens: false,
|
|
83
|
+
}).values;
|
|
84
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EOL } from "node:os";
|
|
2
|
-
import { isEmailIshUsername } from "
|
|
2
|
+
import { isEmailIshUsername } from "../utensils.js";
|
|
3
3
|
const DEFAULT_WARNING = `#${EOL}` +
|
|
4
4
|
`# DO NOT EDIT - this file is generated and your edits will be overwritten${EOL}` +
|
|
5
5
|
`#${EOL}` +
|
|
@@ -36,7 +36,7 @@ function expandTeamToUserNames(pUser, pTeamMap) {
|
|
|
36
36
|
return [pUser.raw];
|
|
37
37
|
}
|
|
38
38
|
function stringifyTeamMembers(pTeamMap, pTeamName) {
|
|
39
|
-
return pTeamMap[pTeamName].map(userNameToCodeOwner);
|
|
39
|
+
return (pTeamMap[pTeamName] ?? []).map(userNameToCodeOwner);
|
|
40
40
|
}
|
|
41
41
|
function userNameToCodeOwner(pUserName) {
|
|
42
42
|
if (isEmailIshUsername(pUserName)) {
|
package/dist/main.js
CHANGED
|
@@ -1,92 +1,10 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { EOL } from "node:os";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import { VERSION } from "./version.js";
|
|
9
|
-
const HELP_MESSAGE = `Usage: virtual-code-owners [options]
|
|
10
|
-
|
|
11
|
-
Merges a VIRTUAL-CODEOWNERS.txt and a virtual-teams.yml into CODEOWNERS
|
|
12
|
-
|
|
13
|
-
Options:
|
|
14
|
-
-V, --version output the version number
|
|
15
|
-
-v, --virtualCodeOwners [file-name] A CODEOWNERS file with team names in them
|
|
16
|
-
that are defined in a virtual teams file
|
|
17
|
-
(default: ".github/VIRTUAL-CODEOWNERS.txt")
|
|
18
|
-
-t, --virtualTeams [file-name] A YAML file listing teams and their
|
|
19
|
-
members
|
|
20
|
-
(default: ".github/virtual-teams.yml")
|
|
21
|
-
-c, --codeOwners [file-name] The location of the CODEOWNERS file
|
|
22
|
-
(default: ".github/CODEOWNERS")
|
|
23
|
-
-l, --emitLabeler Whether or not to emit a labeler.yml to be
|
|
24
|
-
used with actions/labeler
|
|
25
|
-
(default: false)
|
|
26
|
-
--labelerLocation [file-name] The location of the labeler.yml file
|
|
27
|
-
(default: ".github/labeler.yml")
|
|
28
|
-
--dryRun Just validate inputs, don't generate
|
|
29
|
-
outputs (default: false)
|
|
30
|
-
-h, --help display help for command`;
|
|
31
|
-
export function cli(pArguments = process.argv.slice(2), pOutStream = process.stdout, pErrorStream = process.stderr, pErrorExitCode = 1) {
|
|
32
|
-
try {
|
|
33
|
-
const lOptions = getOptions(pArguments);
|
|
34
|
-
if (lOptions.help) {
|
|
35
|
-
pOutStream.write(`${HELP_MESSAGE}${EOL}`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (lOptions.version) {
|
|
39
|
-
pOutStream.write(`${VERSION}${EOL}`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
main(lOptions, pErrorStream);
|
|
43
|
-
}
|
|
44
|
-
catch (pError) {
|
|
45
|
-
pErrorStream.write(`${EOL}ERROR: ${pError.message}${EOL}${EOL}`);
|
|
46
|
-
process.exitCode = pErrorExitCode;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function getOptions(pArguments) {
|
|
50
|
-
return parseArgs({
|
|
51
|
-
args: pArguments,
|
|
52
|
-
options: {
|
|
53
|
-
virtualCodeOwners: {
|
|
54
|
-
type: "string",
|
|
55
|
-
short: "v",
|
|
56
|
-
default: ".github/VIRTUAL-CODEOWNERS.txt",
|
|
57
|
-
},
|
|
58
|
-
virtualTeams: {
|
|
59
|
-
type: "string",
|
|
60
|
-
short: "t",
|
|
61
|
-
default: ".github/virtual-teams.yml",
|
|
62
|
-
},
|
|
63
|
-
codeOwners: {
|
|
64
|
-
type: "string",
|
|
65
|
-
short: "c",
|
|
66
|
-
default: ".github/CODEOWNERS",
|
|
67
|
-
},
|
|
68
|
-
emitLabeler: {
|
|
69
|
-
type: "boolean",
|
|
70
|
-
short: "l",
|
|
71
|
-
default: false,
|
|
72
|
-
},
|
|
73
|
-
labelerLocation: {
|
|
74
|
-
type: "string",
|
|
75
|
-
default: ".github/labeler.yml",
|
|
76
|
-
},
|
|
77
|
-
dryRun: {
|
|
78
|
-
type: "boolean",
|
|
79
|
-
default: false,
|
|
80
|
-
},
|
|
81
|
-
help: { type: "boolean", short: "h", default: false },
|
|
82
|
-
version: { type: "boolean", short: "V", default: false },
|
|
83
|
-
},
|
|
84
|
-
strict: true,
|
|
85
|
-
allowPositionals: true,
|
|
86
|
-
tokens: false,
|
|
87
|
-
}).values;
|
|
88
|
-
}
|
|
89
|
-
function main(pOptions, pErrorStream) {
|
|
3
|
+
import generateCodeOwners from "./codeowners/generate.js";
|
|
4
|
+
import generateLabelerYml from "./labeler-yml/generate.js";
|
|
5
|
+
import readTeamMap from "./team-map/read.js";
|
|
6
|
+
import readVirtualCodeOwners from "./virtual-code-owners/read.js";
|
|
7
|
+
export function main(pOptions, pErrorStream) {
|
|
90
8
|
const lTeamMap = readTeamMap(pOptions.virtualTeams);
|
|
91
9
|
const lVirtualCodeOwners = readVirtualCodeOwners(pOptions.virtualCodeOwners, lTeamMap);
|
|
92
10
|
const lCodeOwnersContent = generateCodeOwners(lVirtualCodeOwners, lTeamMap);
|
package/dist/run-cli.js
ADDED
|
@@ -2,7 +2,7 @@ import Ajv from "ajv";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { EOL } from "node:os";
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
|
-
|
|
5
|
+
import virtualTeamsSchema from "./virtual-teams.schema.js";
|
|
6
6
|
export default function readTeamMap(pVirtualTeamsFileName) {
|
|
7
7
|
const lVirtualTeamsAsAString = readFileSync(pVirtualTeamsFileName, {
|
|
8
8
|
encoding: "utf-8",
|
|
@@ -16,7 +16,7 @@ function assertTeamMapValid(pTeamMap, pVirtualTeamsFileName) {
|
|
|
16
16
|
allErrors: true,
|
|
17
17
|
verbose: true,
|
|
18
18
|
});
|
|
19
|
-
if (!ajv.validate(
|
|
19
|
+
if (!ajv.validate(virtualTeamsSchema, pTeamMap)) {
|
|
20
20
|
throw new Error(`This is not a valid virtual-teams.yml:${EOL}${formatAjvErrors(ajv.errors, pVirtualTeamsFileName)}.\n`);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
title: "virtual teams schema for virtual-code-owners",
|
|
4
|
+
description: "a list of teams and their team members",
|
|
5
|
+
$id: "org.js.virtual-code-owners/7.0.0",
|
|
6
|
+
type: "object",
|
|
7
|
+
additionalProperties: {
|
|
8
|
+
type: "array",
|
|
9
|
+
items: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Username or e-mail address of a team member. (Don't prefix usernames with '@')",
|
|
12
|
+
pattern: "^[^@][^\\s]+$",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "
|
|
1
|
+
export const VERSION = "7.0.1";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function getAnomalies(pVirtualCodeOwners) {
|
|
2
|
+
const weirdLines = pVirtualCodeOwners
|
|
3
|
+
.filter((pLine) => pLine.type === "unknown")
|
|
4
|
+
.map((pLine) => ({
|
|
5
|
+
...pLine,
|
|
6
|
+
type: "invalid-line",
|
|
7
|
+
}));
|
|
8
|
+
const weirdUsers = pVirtualCodeOwners.flatMap((pLine) => {
|
|
9
|
+
if (pLine.type === "rule") {
|
|
10
|
+
return pLine.users
|
|
11
|
+
.filter((pUser) => pUser.type === "invalid")
|
|
12
|
+
.map((pUser) => ({
|
|
13
|
+
...pUser,
|
|
14
|
+
line: pLine.line,
|
|
15
|
+
type: "invalid-user",
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
});
|
|
20
|
+
return weirdLines.concat(weirdUsers).sort(orderAnomaly);
|
|
21
|
+
}
|
|
22
|
+
function orderAnomaly(pLeft, pRight) {
|
|
23
|
+
if (pLeft.line === pRight.line &&
|
|
24
|
+
pLeft.type === "invalid-user" &&
|
|
25
|
+
pRight.type === "invalid-user") {
|
|
26
|
+
return pLeft.userNumberWithinLine > pRight.userNumberWithinLine ? 1 : -1;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return pLeft.line > pRight.line ? 1 : -1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,45 +1,14 @@
|
|
|
1
1
|
import { EOL } from "node:os";
|
|
2
|
-
import { isEmailIshUsername } from "
|
|
2
|
+
import { isEmailIshUsername } from "../utensils.js";
|
|
3
3
|
export function parse(pVirtualCodeOwnersAsString, pTeamMap = {}) {
|
|
4
4
|
return pVirtualCodeOwnersAsString
|
|
5
5
|
.split(EOL)
|
|
6
6
|
.map((pUntreatedLine, pLineNo) => parseLine(pUntreatedLine, pTeamMap, pLineNo + 1));
|
|
7
7
|
}
|
|
8
|
-
export function getAnomalies(pVirtualCodeOwners) {
|
|
9
|
-
const weirdLines = pVirtualCodeOwners
|
|
10
|
-
.filter((pLine) => pLine.type === "unknown")
|
|
11
|
-
.map((pLine) => ({
|
|
12
|
-
...pLine,
|
|
13
|
-
type: "invalid-line",
|
|
14
|
-
}));
|
|
15
|
-
const weirdUsers = pVirtualCodeOwners.flatMap((pLine) => {
|
|
16
|
-
if (pLine.type === "rule") {
|
|
17
|
-
return pLine.users
|
|
18
|
-
.filter((pUser) => pUser.type === "invalid")
|
|
19
|
-
.map((pUser) => ({
|
|
20
|
-
...pUser,
|
|
21
|
-
line: pLine.line,
|
|
22
|
-
type: "invalid-user",
|
|
23
|
-
}));
|
|
24
|
-
}
|
|
25
|
-
return [];
|
|
26
|
-
});
|
|
27
|
-
return weirdLines.concat(weirdUsers).sort(orderAnomaly);
|
|
28
|
-
}
|
|
29
|
-
function orderAnomaly(pLeft, pRight) {
|
|
30
|
-
if (pLeft.line === pRight.line &&
|
|
31
|
-
pLeft.type === "invalid-user" &&
|
|
32
|
-
pRight.type === "invalid-user") {
|
|
33
|
-
return pLeft.userNumberWithinLine > pRight.userNumberWithinLine ? 1 : -1;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
return pLeft.line > pRight.line ? 1 : -1;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
8
|
function parseLine(pUntreatedLine, pTeamMap, pLineNo) {
|
|
40
9
|
const lTrimmedLine = pUntreatedLine.trim();
|
|
41
10
|
const lCommentSplitLine = lTrimmedLine.split(/\s*#/);
|
|
42
|
-
const lRule = lCommentSplitLine[0]
|
|
11
|
+
const lRule = lCommentSplitLine[0]?.match(/^(?<filesPattern>[^\s]+)(?<spaces>\s+)(?<userNames>.*)$/);
|
|
43
12
|
if (lTrimmedLine.startsWith("#!")) {
|
|
44
13
|
return { type: "ignorable-comment", line: pLineNo, raw: pUntreatedLine };
|
|
45
14
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { EOL } from "node:os";
|
|
3
|
-
import { getAnomalies
|
|
3
|
+
import { getAnomalies } from "./anomalies.js";
|
|
4
|
+
import { parse as parseVirtualCodeOwners } from "./parse.js";
|
|
4
5
|
export default function readVirtualCodeOwners(pVirtualCodeOwnersFileName, pTeamMap) {
|
|
5
6
|
const lVirtualCodeOwnersAsAString = readFileSync(pVirtualCodeOwnersFileName, {
|
|
6
7
|
encoding: "utf-8",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "virtual teams schema for virtual-code-owners",
|
|
4
4
|
"description": "a list of teams and their team members",
|
|
5
|
-
"$id": "org.js.virtual-code-owners/
|
|
5
|
+
"$id": "org.js.virtual-code-owners/7.0.0",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": {
|
|
8
8
|
"type": "array",
|
package/package.json
CHANGED
|
@@ -1,60 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virtual-code-owners",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"description": "Makes your CODEOWNERS file liveable again",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"parse": [
|
|
8
|
-
{
|
|
9
|
-
"import": "./dist/parse-virtual-code-owners.js",
|
|
10
|
-
"types": "./types/parse-virtual-code-owners.d.ts"
|
|
11
|
-
},
|
|
12
|
-
"./dist/parse-virtual-code-owners.js"
|
|
13
|
-
],
|
|
14
|
-
"generateCodeOwners": [
|
|
15
|
-
{
|
|
16
|
-
"import": "./dist/generate-codeowners.js",
|
|
17
|
-
"types": "./types/generate-codeowners.d.ts"
|
|
18
|
-
},
|
|
19
|
-
"./dist/generate-codeowners.js"
|
|
20
|
-
],
|
|
21
|
-
"generateLabelerYml": [
|
|
22
|
-
{
|
|
23
|
-
"import": "./dist/generate-labeler-yml.js",
|
|
24
|
-
"types": "./types/generate-labeler-yml.d.ts"
|
|
25
|
-
},
|
|
26
|
-
"./dist/generate-labeler-yml.js"
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
"main": "dist/main.js",
|
|
30
|
-
"types": "types/types.d.ts",
|
|
31
|
-
"bin": "dist/cli.js",
|
|
6
|
+
"bin": "dist/run-cli.js",
|
|
32
7
|
"files": [
|
|
33
8
|
"dist/",
|
|
34
|
-
"types/",
|
|
35
9
|
"package.json",
|
|
36
10
|
"README.md",
|
|
37
11
|
"LICENSE"
|
|
38
12
|
],
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "rm -rf dist && node --no-warnings --loader ts-node/esm tools/get-version.ts > src/version.ts && tsc && cp -f src/*.json dist/.",
|
|
41
|
-
"check": "npm run format && npm run build && npm run depcruise -- --no-progress && npm test",
|
|
42
|
-
"depcruise": "depcruise src tools",
|
|
43
|
-
"depcruise:graph": "depcruise src --include-only '^(src)' --output-type dot | dot -T svg | depcruise-wrap-stream-in-html > dependency-graph.html",
|
|
44
|
-
"depcruise:graph:dev": "depcruise src --prefix vscode://file/$(pwd)/ --output-type dot | dot -T svg | depcruise-wrap-stream-in-html | browser",
|
|
45
|
-
"depcruise:graph:diff:dev": "depcruise src --prefix vscode://file/$(pwd)/ --output-type dot --reaches \"$(watskeburt $SHA -T regex)\"| dot -T svg | depcruise-wrap-stream-in-html | browser",
|
|
46
|
-
"depcruise:graph:diff:mermaid": "depcruise src tools --output-type mermaid --output-to - --reaches \"$(watskeburt $SHA -T regex)\"",
|
|
47
|
-
"depcruise:html": "depcruise src tools --output-type err-html --output-to dependency-violation-report.html",
|
|
48
|
-
"format": "prettier --log-level warn --write \"**/*.{md,ts,json,yml}\"",
|
|
49
|
-
"prepare": "husky install",
|
|
50
|
-
"scm:stage": "git add .",
|
|
51
|
-
"test": "c8 node --no-warnings --loader 'ts-node/esm' --test-reporter ./tools/dot-with-summary.reporter.js --test src/*.test.ts",
|
|
52
|
-
"update-dependencies": "npm run upem:update && npm run upem:install && npm run check",
|
|
53
|
-
"upem-outdated": "npm outdated --json --long | upem --dry-run",
|
|
54
|
-
"upem:install": "npm install",
|
|
55
|
-
"upem:update": "npm outdated --json --long | upem | pbcopy && pbpaste",
|
|
56
|
-
"version": "npm run build && npm run scm:stage"
|
|
57
|
-
},
|
|
58
13
|
"keywords": [
|
|
59
14
|
"CODEOWNERS"
|
|
60
15
|
],
|
|
@@ -63,28 +18,16 @@
|
|
|
63
18
|
"homepage": "https://github.com/sverweij/virtual-code-owners",
|
|
64
19
|
"repository": {
|
|
65
20
|
"type": "git",
|
|
66
|
-
"url": "git+https://github.com/sverweij/virtual-code-owners"
|
|
21
|
+
"url": "git+https://github.com/sverweij/virtual-code-owners.git"
|
|
67
22
|
},
|
|
68
23
|
"bugs": {
|
|
69
24
|
"url": "https://github.com/sverweij/virtual-code-owners/issues"
|
|
70
25
|
},
|
|
71
|
-
"devDependencies": {
|
|
72
|
-
"@types/node": "20.5.8",
|
|
73
|
-
"c8": "8.0.1",
|
|
74
|
-
"dependency-cruiser": "13.1.5",
|
|
75
|
-
"husky": "8.0.3",
|
|
76
|
-
"lint-staged": "14.0.1",
|
|
77
|
-
"prettier": "3.0.3",
|
|
78
|
-
"ts-node": "10.9.1",
|
|
79
|
-
"typescript": "5.2.2",
|
|
80
|
-
"upem": "8.0.0",
|
|
81
|
-
"watskeburt": "1.0.1"
|
|
82
|
-
},
|
|
83
26
|
"dependencies": {
|
|
84
27
|
"ajv": "8.12.0",
|
|
85
|
-
"yaml": "2.3.
|
|
28
|
+
"yaml": "2.3.3"
|
|
86
29
|
},
|
|
87
30
|
"engines": {
|
|
88
|
-
"node": "^
|
|
31
|
+
"node": "^18.11.0||>=20.0.0"
|
|
89
32
|
}
|
|
90
|
-
}
|
|
33
|
+
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { IAnomaly, ITeamMap, IVirtualCodeOwnersCST } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* parses (virtual) codeowners as a string into a virtual codeowners CST
|
|
4
|
-
* which can be used to do further rocessing on (e.g. generate codeowners,
|
|
5
|
-
* validate etc.)
|
|
6
|
-
*
|
|
7
|
-
* @param pVirtualCodeOwnersAsString CODEOWNERS or VIRTUAL-CODE-OWNERS.txt file to parse
|
|
8
|
-
* @param pTeamMap a virtual team map ()
|
|
9
|
-
* @returns a virtual code owners CST
|
|
10
|
-
*/
|
|
11
|
-
export declare function parse(
|
|
12
|
-
pVirtualCodeOwnersAsString: string,
|
|
13
|
-
pTeamMap?: ITeamMap,
|
|
14
|
-
): IVirtualCodeOwnersCST;
|
|
15
|
-
|
|
16
|
-
export declare function getAnomalies(
|
|
17
|
-
pVirtualCodeOwners: IVirtualCodeOwnersCST,
|
|
18
|
-
): IAnomaly[];
|
package/types/types.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export interface ITeamMap {
|
|
2
|
-
[teamName: string]: string[];
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export type IVirtualCodeOwnersCST = IVirtualCodeOwnerLine[];
|
|
6
|
-
export type IVirtualCodeOwnerLine = IBoringCSTLine | IInterestingCSTLine;
|
|
7
|
-
export interface IBoringCSTLine {
|
|
8
|
-
type: "comment" | "ignorable-comment" | "empty" | "unknown";
|
|
9
|
-
line: number;
|
|
10
|
-
raw: string;
|
|
11
|
-
}
|
|
12
|
-
export interface IInterestingCSTLine {
|
|
13
|
-
type: "rule";
|
|
14
|
-
line: number;
|
|
15
|
-
filesPattern: string;
|
|
16
|
-
spaces: string;
|
|
17
|
-
users: IUser[];
|
|
18
|
-
inlineComment: string;
|
|
19
|
-
raw: string;
|
|
20
|
-
}
|
|
21
|
-
export type UserType =
|
|
22
|
-
| "virtual-team-name"
|
|
23
|
-
| "e-mail-address"
|
|
24
|
-
| "other-user-or-team"
|
|
25
|
-
| "invalid";
|
|
26
|
-
export type IUser = {
|
|
27
|
-
type: UserType;
|
|
28
|
-
userNumberWithinLine: number;
|
|
29
|
-
bareName: string;
|
|
30
|
-
raw: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type IAnomaly = ILineAnomaly | IUserAnomaly;
|
|
34
|
-
export interface ILineAnomaly {
|
|
35
|
-
type: "invalid-line";
|
|
36
|
-
line: number;
|
|
37
|
-
raw: string;
|
|
38
|
-
}
|
|
39
|
-
export interface IUserAnomaly {
|
|
40
|
-
type: "invalid-user";
|
|
41
|
-
line: number;
|
|
42
|
-
userNumberWithinLine: number;
|
|
43
|
-
bareName: string;
|
|
44
|
-
raw: string;
|
|
45
|
-
}
|
|
File without changes
|