-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathcollaborators.js
138 lines (119 loc) · 4.03 KB
/
collaborators.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import fs from 'node:fs';
const TSC_TITLE = '#### TSC voting members';
const TSC_REGULAR_TITLE = '#### TSC regular members';
const TSCE_TITLE = '#### TSC emeriti members';
const CL_TITLE = '### Collaborators';
const CLE_TITLE = '### Collaborator emeriti';
const CONTACT_RE = /\* +\[(.+?)\]\(.+?\) +-\s+\*\*([^*]+?)\*\* +(?:<|\\<|<<)([^>]+?)(?:>|>)/mg;
const TSC = 'TSC';
const COLLABORATOR = 'COLLABORATOR';
export class Collaborator {
constructor(login, name, email, type) {
this.login = login; // This is not lowercased
this.name = name;
this.email = email;
this.type = type;
}
isActor(actor) {
if (!actor || !actor.login) { // ghost
return false;
}
return actor.login.toLowerCase() === this.login.toLowerCase();
}
isTSC() {
return this.type === TSC;
}
getName() {
return `${this.name} (@${this.login})`;
}
getContact() {
return `${this.name} <${this.email}>`;
}
}
Collaborator.TYPES = {
TSC, COLLABORATOR
};
export async function getCollaborators(cli, request, argv) {
const { readme, owner, repo } = argv;
let readmeText;
if (readme) {
cli.updateSpinner(`Reading collaborator contacts from ${readme}`);
readmeText = fs.readFileSync(readme, 'utf8');
} else {
cli.updateSpinner(
'Getting collaborator contacts from README of nodejs/node');
const url = 'https://raw.githubusercontent.com/nodejs/node/HEAD/README.md';
readmeText = await request.text(url);
}
let collaborators;
try {
collaborators = parseCollaborators(readmeText, cli);
} catch (err) {
const readmePath = readme || `${owner}/${repo}/README.md`;
cli.stopSpinner(`Failed to get collaborator info from ${readmePath}`,
cli.SPINNER_STATUS.FAILED);
throw err;
}
return collaborators;
}
function parseCollaborators(readme, cli) {
// This is more or less taken from
// https://github.com/rvagg/archived-iojs-tools/blob/main/pr-metadata/pr-metadata.js
const collaborators = new Map();
let m;
const tscIndex = readme.toUpperCase().indexOf(TSC_TITLE.toUpperCase());
const tscrIndex = readme.toUpperCase().indexOf(TSC_REGULAR_TITLE.toUpperCase());
const tsceIndex = readme.toUpperCase().indexOf(TSCE_TITLE.toUpperCase());
const clIndex = readme.toUpperCase().indexOf(CL_TITLE.toUpperCase());
const cleIndex = readme.toUpperCase().indexOf(CLE_TITLE.toUpperCase());
if (tscIndex === -1) {
throw new Error(`Couldn't find ${TSC_TITLE} in the README`);
}
if (tscrIndex === -1) {
throw new Error(`Couldn't find ${TSC_REGULAR_TITLE} in the README`);
}
if (tsceIndex === -1) {
throw new Error(`Couldn't find ${TSCE_TITLE} in the README`);
}
if (clIndex === -1) {
throw new Error(`Couldn't find ${CL_TITLE} in the README`);
}
if (cleIndex === -1) {
throw new Error(`Couldn't find ${CLE_TITLE} in the README`);
}
if (!(tscIndex < tscrIndex &&
tscrIndex < tsceIndex &&
tsceIndex < clIndex &&
clIndex < cleIndex)) {
cli.warn('Contacts in the README is out of order, ' +
'analysis could go wrong.', { newline: true });
}
// We also assume that TSC & TSC Emeriti are also listed as collaborators
CONTACT_RE.lastIndex = tscIndex;
while ((m = CONTACT_RE.exec(readme)) && CONTACT_RE.lastIndex < tscrIndex) {
const login = m[1].toLowerCase();
const user = new Collaborator(m[1], m[2], m[3], TSC);
collaborators.set(login, user);
}
CONTACT_RE.lastIndex = clIndex;
while ((m = CONTACT_RE.exec(readme)) &&
CONTACT_RE.lastIndex < cleIndex) {
const login = m[1].toLowerCase();
if (!collaborators.get(login)) {
const user = new Collaborator(m[1], m[2], m[3], COLLABORATOR);
collaborators.set(login, user);
}
}
if (!collaborators.size) {
throw new Error('Could not find any collaborators');
}
return collaborators;
}
/**
* @param {Map<string, Collaborator>} collaborators
* @param {{login?: string}} user
*/
export function isCollaborator(collaborators, user) {
return (user && user.login && // could be a ghost
collaborators.get(user.login.toLowerCase()));
}