virtual-code-owners 7.0.1 → 7.0.2
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/dist/cli.js +57 -53
- package/dist/codeowners/generate.js +48 -37
- package/dist/labeler-yml/generate.js +43 -36
- package/dist/main.js +22 -18
- package/dist/team-map/read.js +24 -17
- package/dist/team-map/virtual-teams.schema.js +13 -12
- package/dist/utensils.js +2 -2
- package/dist/version.js +1 -1
- package/dist/virtual-code-owners/anomalies.js +27 -26
- package/dist/virtual-code-owners/parse.js +53 -49
- package/dist/virtual-code-owners/read.js +32 -20
- package/package.json +5 -3
package/dist/cli.js
CHANGED
|
@@ -24,61 +24,65 @@ Options:
|
|
|
24
24
|
--dryRun Just validate inputs, don't generate
|
|
25
25
|
outputs (default: false)
|
|
26
26
|
-h, --help display help for command`;
|
|
27
|
-
export function cli(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
main(lOptions, pErrorStream);
|
|
27
|
+
export function cli(
|
|
28
|
+
pArguments = process.argv.slice(2),
|
|
29
|
+
pOutStream = process.stdout,
|
|
30
|
+
pErrorStream = process.stderr,
|
|
31
|
+
pErrorExitCode = 1,
|
|
32
|
+
) {
|
|
33
|
+
try {
|
|
34
|
+
const lOptions = getOptions(pArguments);
|
|
35
|
+
if (lOptions.help) {
|
|
36
|
+
pOutStream.write(`${HELP_MESSAGE}${EOL}`);
|
|
37
|
+
return;
|
|
39
38
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
if (lOptions.version) {
|
|
40
|
+
pOutStream.write(`${VERSION}${EOL}`);
|
|
41
|
+
return;
|
|
43
42
|
}
|
|
43
|
+
main(lOptions, pErrorStream);
|
|
44
|
+
} catch (pError) {
|
|
45
|
+
pErrorStream.write(`${EOL}ERROR: ${pError.message}${EOL}${EOL}`);
|
|
46
|
+
process.exitCode = pErrorExitCode;
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
function getOptions(pArguments) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
|
84
88
|
}
|
|
@@ -1,52 +1,63 @@
|
|
|
1
1
|
import { EOL } from "node:os";
|
|
2
2
|
import { isEmailIshUsername } from "../utensils.js";
|
|
3
|
-
const DEFAULT_WARNING =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
const DEFAULT_WARNING =
|
|
4
|
+
`#${EOL}` +
|
|
5
|
+
`# DO NOT EDIT - this file is generated and your edits will be overwritten${EOL}` +
|
|
6
|
+
`#${EOL}` +
|
|
7
|
+
`# To make changes:${EOL}` +
|
|
8
|
+
`#${EOL}` +
|
|
9
|
+
`# - edit .github/VIRTUAL-CODEOWNERS.txt${EOL}` +
|
|
10
|
+
`# - and/ or add team members to .github/virtual-teams.yml${EOL}` +
|
|
11
|
+
`# - run 'npx virtual-code-owners' (or 'npx virtual-code-owners --emitLabeler' if you also${EOL}` +
|
|
12
|
+
`# want to generate a .github/labeler.yml)${EOL}` +
|
|
13
|
+
`#${EOL}${EOL}`;
|
|
14
|
+
export default function generateCodeOwners(
|
|
15
|
+
pVirtualCodeOwners,
|
|
16
|
+
pTeamMap,
|
|
17
|
+
pGeneratedWarning = DEFAULT_WARNING,
|
|
18
|
+
) {
|
|
19
|
+
return (
|
|
20
|
+
pGeneratedWarning +
|
|
21
|
+
pVirtualCodeOwners
|
|
22
|
+
.filter((pLine) => pLine.type !== "ignorable-comment")
|
|
23
|
+
.map((pLine) => generateLine(pLine, pTeamMap))
|
|
24
|
+
.join(EOL)
|
|
25
|
+
);
|
|
19
26
|
}
|
|
20
27
|
function generateLine(pCSTLine, pTeamMap) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
if (pCSTLine.type === "rule") {
|
|
29
|
+
const lUserNames = uniq(
|
|
30
|
+
pCSTLine.users.flatMap((pUser) => expandTeamToUserNames(pUser, pTeamMap)),
|
|
31
|
+
)
|
|
32
|
+
.sort(compareUserNames)
|
|
33
|
+
.join(" ");
|
|
34
|
+
return (
|
|
35
|
+
pCSTLine.filesPattern +
|
|
36
|
+
pCSTLine.spaces +
|
|
37
|
+
lUserNames +
|
|
38
|
+
(pCSTLine.inlineComment ? ` #${pCSTLine.inlineComment}` : "")
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return pCSTLine.raw;
|
|
31
42
|
}
|
|
32
43
|
function expandTeamToUserNames(pUser, pTeamMap) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
if (pUser.type === "virtual-team-name") {
|
|
45
|
+
return stringifyTeamMembers(pTeamMap, pUser.bareName);
|
|
46
|
+
}
|
|
47
|
+
return [pUser.raw];
|
|
37
48
|
}
|
|
38
49
|
function stringifyTeamMembers(pTeamMap, pTeamName) {
|
|
39
|
-
|
|
50
|
+
return (pTeamMap[pTeamName] ?? []).map(userNameToCodeOwner);
|
|
40
51
|
}
|
|
41
52
|
function userNameToCodeOwner(pUserName) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
if (isEmailIshUsername(pUserName)) {
|
|
54
|
+
return pUserName;
|
|
55
|
+
}
|
|
56
|
+
return `@${pUserName}`;
|
|
46
57
|
}
|
|
47
58
|
function compareUserNames(pLeftName, pRightName) {
|
|
48
|
-
|
|
59
|
+
return pLeftName.toLowerCase() > pRightName.toLowerCase() ? 1 : -1;
|
|
49
60
|
}
|
|
50
61
|
function uniq(pUserNames) {
|
|
51
|
-
|
|
62
|
+
return Array.from(new Set(pUserNames));
|
|
52
63
|
}
|
|
@@ -1,46 +1,53 @@
|
|
|
1
1
|
import { EOL } from "node:os";
|
|
2
|
-
const DEFAULT_WARNING =
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
const DEFAULT_WARNING =
|
|
3
|
+
`#${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 teams (& members) to .github/virtual-teams.yml${EOL}` +
|
|
10
|
+
`# - run 'npx virtual-code-owners --emitLabeler'${EOL}` +
|
|
11
|
+
`#${EOL}${EOL}`;
|
|
12
|
+
export default function generateLabelerYml(
|
|
13
|
+
pCodeOwners,
|
|
14
|
+
pTeamMap,
|
|
15
|
+
pGeneratedWarning = DEFAULT_WARNING,
|
|
16
|
+
) {
|
|
17
|
+
let lReturnValue = pGeneratedWarning;
|
|
18
|
+
for (const lTeamName in pTeamMap) {
|
|
19
|
+
const lPatternsForTeam = getPatternsForTeam(pCodeOwners, lTeamName)
|
|
20
|
+
.map((pPattern) => ` - ${transformForYamlAndMinimatch(pPattern)}${EOL}`)
|
|
21
|
+
.join("");
|
|
22
|
+
if (lPatternsForTeam) {
|
|
23
|
+
lReturnValue += `${lTeamName}:${EOL}${lPatternsForTeam}${EOL}`;
|
|
20
24
|
}
|
|
21
|
-
|
|
25
|
+
}
|
|
26
|
+
return lReturnValue;
|
|
22
27
|
}
|
|
23
28
|
function getPatternsForTeam(pCodeOwners, pTeamName) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
lineContainsTeamName(pLine, pTeamName));
|
|
29
|
+
return pCodeOwners
|
|
30
|
+
.filter((pLine) => {
|
|
31
|
+
return pLine.type === "rule" && lineContainsTeamName(pLine, pTeamName);
|
|
28
32
|
})
|
|
29
|
-
|
|
33
|
+
.map((pLine) => pLine.filesPattern);
|
|
30
34
|
}
|
|
31
35
|
function transformForYamlAndMinimatch(pOriginalString) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
let lReturnValue = pOriginalString;
|
|
37
|
+
if (pOriginalString === "*") {
|
|
38
|
+
lReturnValue = "**";
|
|
39
|
+
}
|
|
40
|
+
if (lReturnValue.startsWith("*")) {
|
|
41
|
+
lReturnValue = `"${lReturnValue}"`;
|
|
42
|
+
}
|
|
43
|
+
if (pOriginalString.endsWith("/")) {
|
|
44
|
+
lReturnValue = `${lReturnValue}**`;
|
|
45
|
+
}
|
|
46
|
+
return lReturnValue;
|
|
43
47
|
}
|
|
44
48
|
function lineContainsTeamName(pLine, pTeamName) {
|
|
45
|
-
|
|
49
|
+
return pLine.users.some(
|
|
50
|
+
(pUser) =>
|
|
51
|
+
pUser.type === "virtual-team-name" && pUser.bareName === pTeamName,
|
|
52
|
+
);
|
|
46
53
|
}
|
package/dist/main.js
CHANGED
|
@@ -5,24 +5,28 @@ import generateLabelerYml from "./labeler-yml/generate.js";
|
|
|
5
5
|
import readTeamMap from "./team-map/read.js";
|
|
6
6
|
import readVirtualCodeOwners from "./virtual-code-owners/read.js";
|
|
7
7
|
export function main(pOptions, pErrorStream) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const lTeamMap = readTeamMap(pOptions.virtualTeams);
|
|
9
|
+
const lVirtualCodeOwners = readVirtualCodeOwners(
|
|
10
|
+
pOptions.virtualCodeOwners,
|
|
11
|
+
lTeamMap,
|
|
12
|
+
);
|
|
13
|
+
const lCodeOwnersContent = generateCodeOwners(lVirtualCodeOwners, lTeamMap);
|
|
14
|
+
if (!pOptions.dryRun) {
|
|
15
|
+
writeFileSync(pOptions.codeOwners, lCodeOwnersContent, {
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (pOptions.emitLabeler) {
|
|
20
|
+
const lLabelerContent = generateLabelerYml(lVirtualCodeOwners, lTeamMap);
|
|
11
21
|
if (!pOptions.dryRun) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
if (pOptions.emitLabeler) {
|
|
17
|
-
const lLabelerContent = generateLabelerYml(lVirtualCodeOwners, lTeamMap);
|
|
18
|
-
if (!pOptions.dryRun) {
|
|
19
|
-
writeFileSync(pOptions.labelerLocation, lLabelerContent, {
|
|
20
|
-
encoding: "utf-8",
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
pErrorStream.write(`${EOL}Wrote '${pOptions.codeOwners}' AND '${pOptions.labelerLocation}'${EOL}${EOL}`);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
pErrorStream.write(`${EOL}Wrote '${pOptions.codeOwners}'${EOL}${EOL}`);
|
|
22
|
+
writeFileSync(pOptions.labelerLocation, lLabelerContent, {
|
|
23
|
+
encoding: "utf-8",
|
|
24
|
+
});
|
|
27
25
|
}
|
|
26
|
+
pErrorStream.write(
|
|
27
|
+
`${EOL}Wrote '${pOptions.codeOwners}' AND '${pOptions.labelerLocation}'${EOL}${EOL}`,
|
|
28
|
+
);
|
|
29
|
+
} else {
|
|
30
|
+
pErrorStream.write(`${EOL}Wrote '${pOptions.codeOwners}'${EOL}${EOL}`);
|
|
31
|
+
}
|
|
28
32
|
}
|
package/dist/team-map/read.js
CHANGED
|
@@ -4,27 +4,34 @@ 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const lVirtualTeamsAsAString = readFileSync(pVirtualTeamsFileName, {
|
|
8
|
+
encoding: "utf-8",
|
|
9
|
+
});
|
|
10
|
+
const lTeamMap = parseYaml(lVirtualTeamsAsAString);
|
|
11
|
+
assertTeamMapValid(lTeamMap, pVirtualTeamsFileName);
|
|
12
|
+
return lTeamMap;
|
|
13
13
|
}
|
|
14
14
|
function assertTeamMapValid(pTeamMap, pVirtualTeamsFileName) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
const ajv = new Ajv({
|
|
16
|
+
allErrors: true,
|
|
17
|
+
verbose: true,
|
|
18
|
+
});
|
|
19
|
+
if (!ajv.validate(virtualTeamsSchema, pTeamMap)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`This is not a valid virtual-teams.yml:${EOL}${formatAjvErrors(
|
|
22
|
+
ajv.errors,
|
|
23
|
+
pVirtualTeamsFileName,
|
|
24
|
+
)}.\n`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
function formatAjvErrors(pAjvErrors, pVirtualTeamsFileName) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
return pAjvErrors
|
|
30
|
+
.map((pAjvError) => formatAjvError(pAjvError, pVirtualTeamsFileName))
|
|
31
|
+
.join(EOL);
|
|
27
32
|
}
|
|
28
33
|
function formatAjvError(pAjvError, pVirtualTeamsFileName) {
|
|
29
|
-
|
|
34
|
+
return `${pVirtualTeamsFileName}: ${
|
|
35
|
+
pAjvError.instancePath
|
|
36
|
+
} - ${JSON.stringify(pAjvError.data)} ${pAjvError.message}`;
|
|
30
37
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
12
|
+
"Username or e-mail address of a team member. (Don't prefix usernames with '@')",
|
|
13
|
+
pattern: "^[^@][^\\s]+$",
|
|
14
14
|
},
|
|
15
|
+
},
|
|
15
16
|
};
|
package/dist/utensils.js
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "7.0.
|
|
1
|
+
export const VERSION = "7.0.2";
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
export function getAnomalies(pVirtualCodeOwners) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
const weirdLines = pVirtualCodeOwners
|
|
3
|
+
.filter((pLine) => pLine.type === "unknown")
|
|
4
|
+
.map((pLine) => ({
|
|
5
|
+
...pLine,
|
|
6
|
+
type: "invalid-line",
|
|
7
7
|
}));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
21
|
}
|
|
22
22
|
function orderAnomaly(pLeft, pRight) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
if (
|
|
24
|
+
pLeft.line === pRight.line &&
|
|
25
|
+
pLeft.type === "invalid-user" &&
|
|
26
|
+
pRight.type === "invalid-user"
|
|
27
|
+
) {
|
|
28
|
+
return pLeft.userNumberWithinLine > pRight.userNumberWithinLine ? 1 : -1;
|
|
29
|
+
} else {
|
|
30
|
+
return pLeft.line > pRight.line ? 1 : -1;
|
|
31
|
+
}
|
|
31
32
|
}
|
|
@@ -1,63 +1,67 @@
|
|
|
1
1
|
import { EOL } from "node:os";
|
|
2
2
|
import { isEmailIshUsername } from "../utensils.js";
|
|
3
3
|
export function parse(pVirtualCodeOwnersAsString, pTeamMap = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
return pVirtualCodeOwnersAsString
|
|
5
|
+
.split(EOL)
|
|
6
|
+
.map((pUntreatedLine, pLineNo) =>
|
|
7
|
+
parseLine(pUntreatedLine, pTeamMap, pLineNo + 1),
|
|
8
|
+
);
|
|
7
9
|
}
|
|
8
10
|
function parseLine(pUntreatedLine, pTeamMap, pLineNo) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const lTrimmedLine = pUntreatedLine.trim();
|
|
12
|
+
const lCommentSplitLine = lTrimmedLine.split(/\s*#/);
|
|
13
|
+
const lRule = lCommentSplitLine[0]?.match(
|
|
14
|
+
/^(?<filesPattern>[^\s]+)(?<spaces>\s+)(?<userNames>.*)$/,
|
|
15
|
+
);
|
|
16
|
+
if (lTrimmedLine.startsWith("#!")) {
|
|
17
|
+
return { type: "ignorable-comment", line: pLineNo, raw: pUntreatedLine };
|
|
18
|
+
}
|
|
19
|
+
if (lTrimmedLine.startsWith("#")) {
|
|
20
|
+
return { type: "comment", line: pLineNo, raw: pUntreatedLine };
|
|
21
|
+
}
|
|
22
|
+
if (!lRule?.groups) {
|
|
23
|
+
if (lTrimmedLine === "") {
|
|
24
|
+
return { type: "empty", line: pLineNo, raw: pUntreatedLine };
|
|
14
25
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
line: pLineNo,
|
|
27
|
-
filesPattern: lRule.groups.filesPattern,
|
|
28
|
-
spaces: lRule.groups.spaces,
|
|
29
|
-
users: parseUsers(lRule.groups.userNames, pTeamMap),
|
|
30
|
-
inlineComment: lCommentSplitLine[1] ?? "",
|
|
31
|
-
raw: pUntreatedLine,
|
|
32
|
-
};
|
|
26
|
+
return { type: "unknown", line: pLineNo, raw: pUntreatedLine };
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
type: "rule",
|
|
30
|
+
line: pLineNo,
|
|
31
|
+
filesPattern: lRule.groups.filesPattern,
|
|
32
|
+
spaces: lRule.groups.spaces,
|
|
33
|
+
users: parseUsers(lRule.groups.userNames, pTeamMap),
|
|
34
|
+
inlineComment: lCommentSplitLine[1] ?? "",
|
|
35
|
+
raw: pUntreatedLine,
|
|
36
|
+
};
|
|
33
37
|
}
|
|
34
38
|
function parseUsers(pUserNamesString, pTeamMap) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
const lUserNames = pUserNamesString.split(/\s+/);
|
|
40
|
+
return lUserNames.map((pUserName, pIndex) => {
|
|
41
|
+
const lBareName = getBareUserName(pUserName);
|
|
42
|
+
return {
|
|
43
|
+
type: getUserNameType(pUserName, lBareName, pTeamMap),
|
|
44
|
+
userNumberWithinLine: pIndex + 1,
|
|
45
|
+
bareName: lBareName,
|
|
46
|
+
raw: pUserName,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
45
49
|
}
|
|
46
50
|
function getUserNameType(pUserName, pBareName, pTeamMap) {
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
if (isEmailIshUsername(pUserName)) {
|
|
52
|
+
return "e-mail-address";
|
|
53
|
+
}
|
|
54
|
+
if (pUserName.startsWith("@")) {
|
|
55
|
+
if (pTeamMap.hasOwnProperty(pBareName)) {
|
|
56
|
+
return "virtual-team-name";
|
|
49
57
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
return "other-user-or-team";
|
|
55
|
-
}
|
|
56
|
-
return "invalid";
|
|
58
|
+
return "other-user-or-team";
|
|
59
|
+
}
|
|
60
|
+
return "invalid";
|
|
57
61
|
}
|
|
58
62
|
function getBareUserName(pUserName) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
if (pUserName.startsWith("@")) {
|
|
64
|
+
return pUserName.slice(1);
|
|
65
|
+
}
|
|
66
|
+
return pUserName;
|
|
63
67
|
}
|
|
@@ -2,27 +2,39 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { EOL } from "node:os";
|
|
3
3
|
import { getAnomalies } from "./anomalies.js";
|
|
4
4
|
import { parse as parseVirtualCodeOwners } from "./parse.js";
|
|
5
|
-
export default function readVirtualCodeOwners(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
export default function readVirtualCodeOwners(
|
|
6
|
+
pVirtualCodeOwnersFileName,
|
|
7
|
+
pTeamMap,
|
|
8
|
+
) {
|
|
9
|
+
const lVirtualCodeOwnersAsAString = readFileSync(pVirtualCodeOwnersFileName, {
|
|
10
|
+
encoding: "utf-8",
|
|
11
|
+
});
|
|
12
|
+
const lVirtualCodeOwners = parseVirtualCodeOwners(
|
|
13
|
+
lVirtualCodeOwnersAsAString,
|
|
14
|
+
pTeamMap,
|
|
15
|
+
);
|
|
16
|
+
const lAnomalies = getAnomalies(lVirtualCodeOwners);
|
|
17
|
+
if (lAnomalies.length > 0) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`This is not a valid virtual code-owners file:${EOL}${reportAnomalies(
|
|
20
|
+
pVirtualCodeOwnersFileName,
|
|
21
|
+
lAnomalies,
|
|
22
|
+
)}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return lVirtualCodeOwners;
|
|
15
26
|
}
|
|
16
27
|
function reportAnomalies(pFileName, pAnomalies) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
return pAnomalies
|
|
29
|
+
.map((pAnomaly) => {
|
|
30
|
+
if (pAnomaly.type === "invalid-line") {
|
|
31
|
+
return `${pFileName}:${pAnomaly.line}:1 invalid line - neither a rule, comment nor empty: "${pAnomaly.raw}"`;
|
|
32
|
+
} else {
|
|
33
|
+
return (
|
|
34
|
+
`${pFileName}:${pAnomaly.line}:1 invalid user or team name "${pAnomaly.raw}" (#${pAnomaly.userNumberWithinLine} on this line). ` +
|
|
35
|
+
`It should either start with "@" or be an e-mail address.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
26
38
|
})
|
|
27
|
-
|
|
39
|
+
.join(EOL);
|
|
28
40
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virtual-code-owners",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"description": "Makes your CODEOWNERS file liveable again",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin":
|
|
6
|
+
"bin": {
|
|
7
|
+
"virtual-code-owners": "dist/run-cli.js"
|
|
8
|
+
},
|
|
7
9
|
"files": [
|
|
8
10
|
"dist/",
|
|
9
11
|
"package.json",
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
27
29
|
"ajv": "8.12.0",
|
|
28
|
-
"yaml": "2.3.
|
|
30
|
+
"yaml": "2.3.4"
|
|
29
31
|
},
|
|
30
32
|
"engines": {
|
|
31
33
|
"node": "^18.11.0||>=20.0.0"
|