forked from raycast/extensions
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathissue-bot.js
196 lines (168 loc) · 5.89 KB
/
issue-bot.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
const fs = require("fs");
const path = require("path");
const newMatch = /### Extension\s*https:\/\/(?:www\.)?raycast\.com\/[^\/]+\/([^\/\s]+)/;
const newMatchGitHub =
/### Extension\s*https:\/\/(?:www\.)?github\.com\/raycast\/extensions\/[^\s]*extensions\/([^\/\s]+)/;
const oldMatch =
/# Extension – \[[^\]]*\]\(https:\/\/(?:www\.)?github\.com\/raycast\/extensions\/[^\s]*extensions\/([^\/\s]+)\/\)/;
const closeIssueMatch = /@raycastbot close this issue/;
module.exports = async ({ github, context, core }) => {
const sender = context.payload.sender.login;
if (sender === "raycastbot" || sender === "stale") {
console.log("We don't notify people when the bots are doing their stuff");
return;
}
if (context.payload.issue.labels.every((x) => x.name !== "extension")) {
console.log("We only deal with extension issues");
return;
}
const codeowners = await getCodeOwners({ github, context });
const [, ext] =
newMatch.exec(context.payload.issue.body) ||
newMatchGitHub.exec(context.payload.issue.body) ||
oldMatch.exec(context.payload.issue.body) ||
[];
if (!ext) {
console.log(`could not find the extension in the body`);
await comment({
github,
context,
comment: `We could not find the extension related to this issue. Please update the issue with the link to the extension.`,
});
await github.rest.issues.addLabels({
issue_number: context.payload.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["status: stalled"],
});
return;
}
const owners =
codeowners[`/extensions/${ext}`] ||
// some extensions don't have a folder that match their name
codeowners[`/extensions/${(await getExtensionName2Folder({ github, context }))[ext]}`];
if (!owners) {
console.log(`could not find the extension ${ext}`);
await comment({
github,
context,
comment: `We could not find the extension related to this issue. Please update the issue with the correct link to the extension.`,
});
await github.rest.issues.addLabels({
issue_number: context.payload.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["status: stalled"],
});
return;
}
if (context.payload.comment) {
// we don't want to label the issue here, only answer to a comment
// if the one who posts a comment is an owner of the extension related to the issue
if (
context.payload.comment.user &&
(owners.indexOf(context.payload.comment.user.login) !== -1 ||
// also allow the OP to close the issue that way
context.payload.comment.user.login === context.payload.issue.user.login)
) {
if (closeIssueMatch.test(context.payload.comment.body)) {
console.log(`closing #${context.payload.issue.number}`);
await github.rest.issues.update({
issue_number: context.payload.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
state: "closed",
});
} else {
console.log(`didn't find the right comment`);
}
} else {
console.log(`${context.payload.comment.user.login} is not an owner`);
}
return;
}
await github.rest.issues.addLabels({
issue_number: context.payload.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [limitLabelLength(`extension: ${ext}`)],
});
try {
await github.rest.issues.removeLabel({
issue_number: context.payload.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: "status: stalled",
});
} catch (err) {
// ignore, it might not be there
}
const toNotify = owners.filter((x) => x !== sender);
if (!toNotify.length) {
console.log("no one to notify, skipping comment");
return;
}
console.log("Sending welcome message");
await comment({
github,
context,
comment: `Thank you for opening this issue!\n\n🔔 ${toNotify
.map((x) => `@${x}`)
.join(
" "
)} you might want to have a look.\n\n💡 Tip: Once the issue is resolved, comment \`@raycastbot close this issue\` to close it.`,
});
};
async function getCodeOwners({ github, context }) {
const codeowners = await getGitHubFile(".github/CODEOWNERS", { github, context });
const regex = /(\/extensions\/[\w-]+) +(.+)/g;
const matches = codeowners.matchAll(regex);
return Array.from(matches).reduce((prev, match) => {
prev[match[1]] = match[2].split(" ").map((x) => x.replace(/^@/, ""));
return prev;
}, {});
}
async function getExtensionName2Folder({ github, context }) {
const file = await getGitHubFile(".github/extensionName2Folder.json", { github, context });
return JSON.parse(file);
}
async function getGitHubFile(path, { github, context }) {
const { data } = await github.rest.repos.getContent({
mediaType: {
format: "raw",
},
owner: context.repo.owner,
repo: context.repo.repo,
path,
});
return data;
}
// Create a new comment or update the existing one
async function comment({ github, context, comment }) {
// Get the existing comments on the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
// Find any comment already made by the bot
const botComment = comments.find((comment) => comment.user.login === "raycastbot");
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment,
});
}
}
function limitLabelLength(label) {
return label.length > 50 ? label.substring(0, 49) + "…" : label;
}