virtual-code-owners 4.1.1 → 5.0.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.
- package/README.md +34 -0
- package/dist/generate-codeowners.js +45 -0
- package/dist/generate-labeler-yml.js +38 -0
- package/dist/main.js +51 -5
- package/dist/parse.js +92 -0
- package/dist/read-team-map.js +8 -0
- package/dist/read-virtual-code-owners.js +8 -0
- package/dist/utensils.js +4 -0
- package/dist/version.js +1 -1
- package/package.json +7 -6
- package/dist/convert-to-codeowners.js +0 -56
- package/dist/read-and-convert.js +0 -13
package/README.md
CHANGED
|
@@ -167,6 +167,23 @@ Yes.
|
|
|
167
167
|
Just make sure there's no name clashes between the username and a (virtual)
|
|
168
168
|
team name and _virtual-code-owners_ will leave the real name alone.
|
|
169
169
|
|
|
170
|
+
### What validations does virtual-code-owners perform?
|
|
171
|
+
|
|
172
|
+
On the VIRTUAL-CODEOWNERS.txt file it performs is a little bit of validation:
|
|
173
|
+
|
|
174
|
+
- it will find invalid user/ team names (those that don't start with an `@` or
|
|
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.
|
|
178
|
+
|
|
179
|
+
When it encounters any of these virtual-code-owners will emit a clear error message
|
|
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.
|
|
182
|
+
|
|
183
|
+
It _does not_ check whether the user or team names actually exist in the current
|
|
184
|
+
project, though. Although nice, there's already tooling on the generated CODEOWNERS
|
|
185
|
+
file that will check that for you.
|
|
186
|
+
|
|
170
187
|
### Any limitations I should know of?
|
|
171
188
|
|
|
172
189
|
- ~~Currently only works for _user names_ to identify team members - not for e-mail
|
|
@@ -202,3 +219,20 @@ like this:
|
|
|
202
219
|
]
|
|
203
220
|
}
|
|
204
221
|
```
|
|
222
|
+
|
|
223
|
+
### It'd be _pretty_ handy if I could see for which virtual teams a PR is. For instance with a bunch of :label: labels.
|
|
224
|
+
|
|
225
|
+
How do I go about that?
|
|
226
|
+
|
|
227
|
+
You can use the [actions/labeler](https://github.com/actions/labeler) action for
|
|
228
|
+
for this. Maintaining the configuration file (`.github/labeler.yml`) and keeping it
|
|
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.
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
npx virtual-code-owners --emitLabeler
|
|
234
|
+
# Wrote .github/CODEOWNERS AND .github/labeler.yml
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If you have an alternate file location for the `labeler.yml` you can specify that
|
|
238
|
+
with virtual-code-owner's `--labelerLocation` parameter.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EOL } from "node:os";
|
|
2
|
+
import { isEmailIshUsername } from "./utensils.js";
|
|
3
|
+
const DEFAULT_WARNING = `#${EOL}` +
|
|
4
|
+
`# DO NOT EDIT - this file is generated and your edits will be overwritten${EOL}` +
|
|
5
|
+
`#${EOL}` +
|
|
6
|
+
`# To make changes:${EOL}` +
|
|
7
|
+
`#${EOL}` +
|
|
8
|
+
`# - edit .github/VIRTUAL-CODEOWNERS.txt${EOL}` +
|
|
9
|
+
`# - and/ or add team members to .github/virtual-teams.yml${EOL}` +
|
|
10
|
+
`# - run 'npx virtual-code-owners'${EOL}` +
|
|
11
|
+
`#${EOL}${EOL}`;
|
|
12
|
+
export default function generateCodeOwners(pVirtualCodeOwners, pTeamMap, pGeneratedWarning = DEFAULT_WARNING) {
|
|
13
|
+
return (pGeneratedWarning +
|
|
14
|
+
pVirtualCodeOwners
|
|
15
|
+
.filter((pLine) => pLine.type !== "ignorable-comment")
|
|
16
|
+
.map((pLine) => generateLine(pLine, pTeamMap))
|
|
17
|
+
.join(EOL));
|
|
18
|
+
}
|
|
19
|
+
function generateLine(pCSTLine, pTeamMap) {
|
|
20
|
+
if (pCSTLine.type === "rule") {
|
|
21
|
+
const lUserNames = uniq(pCSTLine.users.flatMap((pUser) => expandTeamToUserNames(pUser, pTeamMap)))
|
|
22
|
+
.sort()
|
|
23
|
+
.join(" ");
|
|
24
|
+
return pCSTLine.filesPattern + pCSTLine.spaces + lUserNames;
|
|
25
|
+
}
|
|
26
|
+
return pCSTLine.raw;
|
|
27
|
+
}
|
|
28
|
+
function expandTeamToUserNames(pUser, pTeamMap) {
|
|
29
|
+
if (pUser.type == "virtual-team-name") {
|
|
30
|
+
return stringifyTeamMembers(pTeamMap, pUser.bareName);
|
|
31
|
+
}
|
|
32
|
+
return [pUser.raw];
|
|
33
|
+
}
|
|
34
|
+
function stringifyTeamMembers(pTeamMap, pTeamName) {
|
|
35
|
+
return pTeamMap[pTeamName].map(userNameToCodeOwner);
|
|
36
|
+
}
|
|
37
|
+
function userNameToCodeOwner(pUserName) {
|
|
38
|
+
if (isEmailIshUsername(pUserName)) {
|
|
39
|
+
return pUserName;
|
|
40
|
+
}
|
|
41
|
+
return `@${pUserName}`;
|
|
42
|
+
}
|
|
43
|
+
function uniq(pUserNames) {
|
|
44
|
+
return Array.from(new Set(pUserNames));
|
|
45
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EOL } from "node:os";
|
|
2
|
+
export default function generateLabelerYml(pCodeOwners, pTeamMap) {
|
|
3
|
+
let lReturnValue = "";
|
|
4
|
+
for (const lTeamName in pTeamMap) {
|
|
5
|
+
const lPatternsForTeam = getPatternsForTeam(pCodeOwners, lTeamName)
|
|
6
|
+
.map((pPattern) => ` - ${transformForYamlAndMinimatch(pPattern)}${EOL}`)
|
|
7
|
+
.join("");
|
|
8
|
+
if (lPatternsForTeam) {
|
|
9
|
+
lReturnValue += `${lTeamName}:${EOL}${lPatternsForTeam}${EOL}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return lReturnValue;
|
|
13
|
+
}
|
|
14
|
+
function getPatternsForTeam(pCodeOwners, pTeamName) {
|
|
15
|
+
return (pCodeOwners
|
|
16
|
+
.filter((pLine) => {
|
|
17
|
+
const isARule = pLine.type === "rule";
|
|
18
|
+
return (isARule &&
|
|
19
|
+
lineContainsTeamName(pLine, pTeamName));
|
|
20
|
+
})
|
|
21
|
+
.map((pLine) => pLine.filesPattern));
|
|
22
|
+
}
|
|
23
|
+
function transformForYamlAndMinimatch(pOriginalString) {
|
|
24
|
+
let lReturnValue = pOriginalString;
|
|
25
|
+
if (pOriginalString === "*") {
|
|
26
|
+
lReturnValue = "**";
|
|
27
|
+
}
|
|
28
|
+
if (lReturnValue.startsWith("*")) {
|
|
29
|
+
lReturnValue = `'${lReturnValue}'`;
|
|
30
|
+
}
|
|
31
|
+
if (pOriginalString.endsWith("/")) {
|
|
32
|
+
lReturnValue = `${lReturnValue}**`;
|
|
33
|
+
}
|
|
34
|
+
return lReturnValue;
|
|
35
|
+
}
|
|
36
|
+
function lineContainsTeamName(pLine, pTeamName) {
|
|
37
|
+
return pLine.users.some((pUser) => pUser.type === "virtual-team-name" && pUser.bareName === pTeamName);
|
|
38
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { VERSION } from "./version.js";
|
|
2
|
-
import { readAndConvert } from "./read-and-convert.js";
|
|
3
1
|
import { writeFileSync } from "node:fs";
|
|
4
2
|
import { EOL } from "node:os";
|
|
5
3
|
import { parseArgs } from "node:util";
|
|
4
|
+
import generateCodeOwners from "./generate-codeowners.js";
|
|
5
|
+
import generateLabelerYml from "./generate-labeler-yml.js";
|
|
6
|
+
import readTeamMap from "./read-team-map.js";
|
|
7
|
+
import readVirtualCodeOwners from "./read-virtual-code-owners.js";
|
|
8
|
+
import { VERSION } from "./version.js";
|
|
9
|
+
import { getAnomalies } from "./parse.js";
|
|
6
10
|
const HELP_MESSAGE = `Usage: virtual-code-owners [options]
|
|
7
11
|
|
|
8
12
|
Merges a VIRTUAL-CODEOWNERS.txt and a virtual-teams.yml into CODEOWNERS
|
|
@@ -17,10 +21,15 @@ Options:
|
|
|
17
21
|
(default: ".github/virtual-teams.yml")
|
|
18
22
|
-c, --codeOwners [file-name] The location of the CODEOWNERS file
|
|
19
23
|
(default: ".github/CODEOWNERS")
|
|
24
|
+
-l, --emitLabeler Whether or not to emit a labeler.yml to be
|
|
25
|
+
used with actions/labeler
|
|
26
|
+
(default: false)
|
|
27
|
+
--labelerLocation [file-name] The location of the labeler.yml file
|
|
28
|
+
(default: ".github/labeler.yml")
|
|
20
29
|
-h, --help display help for command`;
|
|
21
30
|
export function main(pArguments = process.argv.slice(2), pOutStream = process.stdout, pErrorStream = process.stderr) {
|
|
22
31
|
try {
|
|
23
|
-
|
|
32
|
+
const lOptions = getOptions(pArguments);
|
|
24
33
|
if (lOptions.help) {
|
|
25
34
|
pOutStream.write(`${HELP_MESSAGE}${EOL}`);
|
|
26
35
|
return;
|
|
@@ -29,17 +38,45 @@ export function main(pArguments = process.argv.slice(2), pOutStream = process.st
|
|
|
29
38
|
pOutStream.write(`${VERSION}${EOL}`);
|
|
30
39
|
return;
|
|
31
40
|
}
|
|
32
|
-
const
|
|
41
|
+
const lTeamMap = readTeamMap(lOptions.virtualTeams);
|
|
42
|
+
const lVirtualCodeOwners = readVirtualCodeOwners(lOptions.virtualCodeOwners, lTeamMap);
|
|
43
|
+
const lAnomalies = getAnomalies(lVirtualCodeOwners);
|
|
44
|
+
if (lAnomalies.length > 0) {
|
|
45
|
+
throw new Error(`${EOL}${reportAnomalies(lOptions.virtualCodeOwners, lAnomalies)}`);
|
|
46
|
+
}
|
|
47
|
+
const lCodeOwnersContent = generateCodeOwners(lVirtualCodeOwners, lTeamMap);
|
|
33
48
|
writeFileSync(lOptions.codeOwners, lCodeOwnersContent, {
|
|
34
49
|
encoding: "utf-8",
|
|
35
50
|
});
|
|
36
|
-
|
|
51
|
+
if (lOptions.emitLabeler) {
|
|
52
|
+
const lLabelerContent = generateLabelerYml(lVirtualCodeOwners, lTeamMap);
|
|
53
|
+
writeFileSync(lOptions.labelerLocation, lLabelerContent, {
|
|
54
|
+
encoding: "utf-8",
|
|
55
|
+
});
|
|
56
|
+
pErrorStream.write(`${EOL}Wrote ${lOptions.codeOwners} AND ${lOptions.labelerLocation}${EOL}${EOL}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
pErrorStream.write(`${EOL}Wrote ${lOptions.codeOwners}${EOL}${EOL}`);
|
|
60
|
+
}
|
|
37
61
|
}
|
|
38
62
|
catch (pError) {
|
|
39
63
|
pErrorStream.write(`${EOL}ERROR: ${pError.message}${EOL}${EOL}`);
|
|
40
64
|
process.exitCode = 1;
|
|
41
65
|
}
|
|
42
66
|
}
|
|
67
|
+
function reportAnomalies(pFileName, pAnomalies) {
|
|
68
|
+
return pAnomalies
|
|
69
|
+
.map((pAnomaly) => {
|
|
70
|
+
if (pAnomaly.type === "invalid-line") {
|
|
71
|
+
return `${pFileName}:${pAnomaly.line}:1 invalid line - neither a rule, comment nor empty: '${pAnomaly.raw}'`;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return (`${pFileName}:${pAnomaly.line}:1 invalid user or team name '${pAnomaly.raw}' (# ${pAnomaly.userNumberWithinLine} on this line). ` +
|
|
75
|
+
`It should either start with '@' or be an e-mail address.`);
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.join(EOL);
|
|
79
|
+
}
|
|
43
80
|
function getOptions(pArguments) {
|
|
44
81
|
return parseArgs({
|
|
45
82
|
args: pArguments,
|
|
@@ -59,6 +96,15 @@ function getOptions(pArguments) {
|
|
|
59
96
|
short: "c",
|
|
60
97
|
default: ".github/CODEOWNERS",
|
|
61
98
|
},
|
|
99
|
+
emitLabeler: {
|
|
100
|
+
type: "boolean",
|
|
101
|
+
short: "l",
|
|
102
|
+
default: false,
|
|
103
|
+
},
|
|
104
|
+
labelerLocation: {
|
|
105
|
+
type: "string",
|
|
106
|
+
default: ".github/labeler.yml",
|
|
107
|
+
},
|
|
62
108
|
help: { type: "boolean", short: "h", default: false },
|
|
63
109
|
version: { type: "boolean", short: "V", default: false },
|
|
64
110
|
},
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { EOL } from "node:os";
|
|
2
|
+
import { isEmailIshUsername } from "./utensils.js";
|
|
3
|
+
export function parse(pVirtualCodeOwnersAsString, pTeamMap = {}) {
|
|
4
|
+
return pVirtualCodeOwnersAsString
|
|
5
|
+
.split(EOL)
|
|
6
|
+
.map((pUntreatedLine, pLineNo) => parseLine(pUntreatedLine, pTeamMap, pLineNo + 1));
|
|
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
|
+
function parseLine(pUntreatedLine, pTeamMap, pLineNo) {
|
|
40
|
+
const lTrimmedLine = pUntreatedLine.trim();
|
|
41
|
+
const lSplitLine = lTrimmedLine.match(/^(?<filesPattern>[^\s]+)(?<spaces>\s+)(?<userNames>.*)$/);
|
|
42
|
+
if (lTrimmedLine.startsWith("#!")) {
|
|
43
|
+
return { type: "ignorable-comment", line: pLineNo, raw: pUntreatedLine };
|
|
44
|
+
}
|
|
45
|
+
if (lTrimmedLine.startsWith("#")) {
|
|
46
|
+
return { type: "comment", line: pLineNo, raw: pUntreatedLine };
|
|
47
|
+
}
|
|
48
|
+
if (!lSplitLine?.groups) {
|
|
49
|
+
if (lTrimmedLine === "") {
|
|
50
|
+
return { type: "empty", line: pLineNo, raw: pUntreatedLine };
|
|
51
|
+
}
|
|
52
|
+
return { type: "unknown", line: pLineNo, raw: pUntreatedLine };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
type: "rule",
|
|
56
|
+
line: pLineNo,
|
|
57
|
+
filesPattern: lSplitLine.groups.filesPattern,
|
|
58
|
+
spaces: lSplitLine.groups.spaces,
|
|
59
|
+
users: parseUsers(lSplitLine.groups.userNames, pTeamMap),
|
|
60
|
+
raw: pUntreatedLine,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseUsers(pUserNamesString, pTeamMap) {
|
|
64
|
+
const lUserNames = pUserNamesString.split(/\s+/);
|
|
65
|
+
return lUserNames.map((pUserName, pIndex) => {
|
|
66
|
+
const lBareName = getBareUserName(pUserName);
|
|
67
|
+
return {
|
|
68
|
+
type: getUserNameType(pUserName, lBareName, pTeamMap),
|
|
69
|
+
userNumberWithinLine: pIndex + 1,
|
|
70
|
+
bareName: lBareName,
|
|
71
|
+
raw: pUserName,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function getUserNameType(pUserName, pBareName, pTeamMap) {
|
|
76
|
+
if (isEmailIshUsername(pUserName)) {
|
|
77
|
+
return "e-mail-address";
|
|
78
|
+
}
|
|
79
|
+
if (pUserName.startsWith("@")) {
|
|
80
|
+
if (pTeamMap.hasOwnProperty(pBareName)) {
|
|
81
|
+
return "virtual-team-name";
|
|
82
|
+
}
|
|
83
|
+
return "other-user-or-team";
|
|
84
|
+
}
|
|
85
|
+
return "invalid";
|
|
86
|
+
}
|
|
87
|
+
function getBareUserName(pUserName) {
|
|
88
|
+
if (pUserName.startsWith("@")) {
|
|
89
|
+
return pUserName.slice(1);
|
|
90
|
+
}
|
|
91
|
+
return pUserName;
|
|
92
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { parse as parseYaml } from "yaml";
|
|
3
|
+
export default function readTeamMap(pVirtualTeamsFileName) {
|
|
4
|
+
const lVirtualTeamsAsAString = readFileSync(pVirtualTeamsFileName, {
|
|
5
|
+
encoding: "utf-8",
|
|
6
|
+
});
|
|
7
|
+
return parseYaml(lVirtualTeamsAsAString);
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { parse as parseVirtualCodeOwners } from "./parse.js";
|
|
3
|
+
export default function readVirtualCodeOwners(pVirtualCodeOwnersFileName, pTeamMap) {
|
|
4
|
+
const lVirtualCodeOwnersAsAString = readFileSync(pVirtualCodeOwnersFileName, {
|
|
5
|
+
encoding: "utf-8",
|
|
6
|
+
});
|
|
7
|
+
return parseVirtualCodeOwners(lVirtualCodeOwnersAsAString, pTeamMap);
|
|
8
|
+
}
|
package/dist/utensils.js
ADDED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "
|
|
1
|
+
export const VERSION = "5.0.0";
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virtual-code-owners",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Merges a VIRTUAL-CODEOWNERS.txt and a virtual-teams.yml into CODEOWNERS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": [
|
|
8
8
|
{
|
|
9
|
-
"import": "./dist/
|
|
9
|
+
"import": "./dist/parse-and-generate.js",
|
|
10
|
+
"types": "./types/parse-and-generate.d.ts"
|
|
10
11
|
},
|
|
11
|
-
"./dist/
|
|
12
|
+
"./dist/parse-and-generate.js"
|
|
12
13
|
]
|
|
13
14
|
},
|
|
14
|
-
"main": "dist/
|
|
15
|
+
"main": "dist/parse-and-generate.js",
|
|
15
16
|
"bin": "dist/cli.js",
|
|
16
17
|
"files": [
|
|
17
18
|
"dist",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@types/mocha": "10.0.1",
|
|
56
|
-
"@types/node": "20.
|
|
57
|
+
"@types/node": "20.3.0",
|
|
57
58
|
"c8": "7.14.0",
|
|
58
59
|
"dependency-cruiser": "13.0.3",
|
|
59
60
|
"husky": "8.0.3",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"prettier": "2.8.8",
|
|
63
64
|
"ts-node": "10.9.1",
|
|
64
65
|
"typescript": "5.1.3",
|
|
65
|
-
"upem": "
|
|
66
|
+
"upem": "8.0.0",
|
|
66
67
|
"watskeburt": "0.11.3"
|
|
67
68
|
},
|
|
68
69
|
"dependencies": {
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { EOL } from "node:os";
|
|
2
|
-
const DEFAULT_WARNING = `#${EOL}` +
|
|
3
|
-
`# DO NOT EDIT - this file is generated and your edits will be overwritten${EOL}` +
|
|
4
|
-
`#${EOL}` +
|
|
5
|
-
`# To make changes:${EOL}` +
|
|
6
|
-
`#${EOL}` +
|
|
7
|
-
`# - edit .github/VIRTUAL-CODEOWNERS.txt${EOL}` +
|
|
8
|
-
`# - and/ or add team members to .github/virtual-teams.yml${EOL}` +
|
|
9
|
-
`# - run 'npx virtual-code-owners'${EOL}` +
|
|
10
|
-
`#${EOL}${EOL}`;
|
|
11
|
-
export function convert(pCodeOwnersFileAsString, pTeamMap, pGeneratedWarning = DEFAULT_WARNING) {
|
|
12
|
-
return (pGeneratedWarning +
|
|
13
|
-
pCodeOwnersFileAsString
|
|
14
|
-
.split(EOL)
|
|
15
|
-
.filter(shouldAppearInResult)
|
|
16
|
-
.map(convertLine(pTeamMap))
|
|
17
|
-
.join(EOL));
|
|
18
|
-
}
|
|
19
|
-
function shouldAppearInResult(pLine) {
|
|
20
|
-
return !pLine.trimStart().startsWith("#!");
|
|
21
|
-
}
|
|
22
|
-
function convertLine(pTeamMap) {
|
|
23
|
-
return (pUntreatedLine) => {
|
|
24
|
-
const lTrimmedLine = pUntreatedLine.trim();
|
|
25
|
-
const lSplitLine = lTrimmedLine.match(/^(?<filesPattern>[^\s]+)(?<spaces>\s+)(?<userNames>.*)$/);
|
|
26
|
-
if (lTrimmedLine.startsWith("#") || !lSplitLine?.groups) {
|
|
27
|
-
return pUntreatedLine;
|
|
28
|
-
}
|
|
29
|
-
const lUserNames = replaceTeamNames(lSplitLine.groups.userNames, pTeamMap);
|
|
30
|
-
return (lSplitLine.groups.filesPattern +
|
|
31
|
-
lSplitLine.groups.spaces +
|
|
32
|
-
uniqAndSortUserNames(lUserNames));
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function replaceTeamNames(pUserNames, pTeamMap) {
|
|
36
|
-
let lReturnValue = pUserNames;
|
|
37
|
-
for (let lTeamName of Object.keys(pTeamMap)) {
|
|
38
|
-
lReturnValue = lReturnValue.replace(new RegExp(`(\\s|^)@${lTeamName}(\\s|$)`, "g"), `$1${stringifyTeamMembers(pTeamMap, lTeamName)}$2`);
|
|
39
|
-
}
|
|
40
|
-
return lReturnValue;
|
|
41
|
-
}
|
|
42
|
-
function stringifyTeamMembers(pTeamMap, pTeamName) {
|
|
43
|
-
return pTeamMap[pTeamName].map(userNameToCodeOwner).join(" ");
|
|
44
|
-
}
|
|
45
|
-
function userNameToCodeOwner(pUserName) {
|
|
46
|
-
const lEmailIshUsernameRE = /^.+@.+$/;
|
|
47
|
-
if (pUserName.match(lEmailIshUsernameRE)) {
|
|
48
|
-
return pUserName;
|
|
49
|
-
}
|
|
50
|
-
return `@${pUserName}`;
|
|
51
|
-
}
|
|
52
|
-
function uniqAndSortUserNames(pUserNames) {
|
|
53
|
-
return Array.from(new Set(pUserNames.split(/\s+/)))
|
|
54
|
-
.sort()
|
|
55
|
-
.join(" ");
|
|
56
|
-
}
|
package/dist/read-and-convert.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { parse } from "yaml";
|
|
3
|
-
import { convert } from "./convert-to-codeowners.js";
|
|
4
|
-
export function readAndConvert(pVirtualCodeOwnersFileName, pVirtualTeamsFileName) {
|
|
5
|
-
const lVirtualCodeOwnersAsAString = readFileSync(pVirtualCodeOwnersFileName, {
|
|
6
|
-
encoding: "utf-8",
|
|
7
|
-
});
|
|
8
|
-
const lVirtualTeamsAsAString = readFileSync(pVirtualTeamsFileName, {
|
|
9
|
-
encoding: "utf-8",
|
|
10
|
-
});
|
|
11
|
-
const lTeamMap = parse(lVirtualTeamsAsAString);
|
|
12
|
-
return convert(lVirtualCodeOwnersAsAString, lTeamMap);
|
|
13
|
-
}
|