-
-
Notifications
You must be signed in to change notification settings - Fork 20
/
index.js
244 lines (222 loc) · 7.54 KB
/
index.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/**
* @fileoverview Handle PR status after the release and when the release is complete
* @author Gyandeep Singh
*/
"use strict";
const PATCH_COMMIT_MESSAGE_REGEX = /^(?:Build|Chore|Docs|Fix|Upgrade):/u;
const POST_RELEASE_LABEL = "patch release pending";
const RELEASE_LABEL = "release";
/**
* Apply different checks on the commit message to see if its ok for a patch release
* @param {string} message commit message
* @returns {boolean} `true` if the commit message is valid for patch release
* @private
*/
function isMessageValidForPatchRelease(message) {
return PATCH_COMMIT_MESSAGE_REGEX.test(message);
}
/**
* Get the sha value from the latest commit
* @param {Object[]} allCommits A list of commit objects from GitHub's API
* @returns {string} latest commit sha
* @private
*/
function pluckLatestCommitSha(allCommits) {
return allCommits[allCommits.length - 1].sha;
}
/**
* Gets all the open PR
* @param {Object} context context object
* @returns {Promise} collection of pr objects
* @private
*/
function getAllOpenPRs(context) {
return context.github.paginate(
context.github.pullRequests.list(
context.repo({
state: "open"
})
),
res => res.data
);
}
/**
* Create status on the PR
* @param {Object} options Configure the status
* @param {Object} options.context probot context object
* @param {string} options.state state can be either success or failure
* @param {string} options.sha sha for the commit
* @param {string} options.description description for the status
* @param {string} options.targetUrl The URL that the status should link to
* @returns {Promise} Resolves when the status is created on the PR
* @private
*/
function createStatusOnPR({ context, state, sha, description, targetUrl }) {
return context.github.repos.createStatus(
context.repo({
sha,
state,
target_url: targetUrl || "",
description,
context: "release-monitor"
})
);
}
/**
* Get all the commits for a PR
* @param {Object} options Configure the request
* @param {Object} options.context Probot context object
* @param {Object} options.pr pull request object from GitHub's API
* @returns {Promise<Object[]>} A Promise that fulfills with a list of commit objects from GitHub's API
* @private
*/
async function getAllCommitsForPR({ context, pr }) {
const { data: commitList } = await context.github.pullRequests.listCommits(
context.repo({ number: pr.number })
);
return commitList;
}
/**
* Creates an appropriate status on a PR, based on the current patch release state and the PR type.
* * If there is no pending patch release, creates a success status with the message "No patch release is pending".
* * If there is a pending patch release and this PR is semver-patch, creates a success status with the message
* "This change is semver-patch" and a link to the release issue.
* * If there is a pending patch release and this PR is not semver-patch, creates a pending status with the message
* "A patch release is pending" and a link to the release issue.
* @param {Object} options Configure the status
* @param {Object} options.context Probot context object
* @param {Object} options.pr pull request object from GitHub's API
* @param {string|null} options.pendingReleaseIssueUrl If a patch release is pending, this is the HTML URL of the
* release issue. Otherwise, this is null.
* @returns {Promise<void>} A Promise that fulfills when the status check has been created
*/
async function createAppropriateStatusForPR({ context, pr, pendingReleaseIssueUrl }) {
const allCommits = await getAllCommitsForPR({ context, pr });
const sha = pluckLatestCommitSha(allCommits);
if (pendingReleaseIssueUrl === null) {
await createStatusOnPR({
context,
sha,
state: "success",
description: "No patch release is pending"
});
} else if (isMessageValidForPatchRelease(pr.title)) {
await createStatusOnPR({
context,
sha,
state: "success",
description: "This change is semver-patch",
targetUrl: pendingReleaseIssueUrl
});
} else {
await createStatusOnPR({
context,
sha,
state: "pending",
description: "A patch release is pending",
targetUrl: pendingReleaseIssueUrl
});
}
}
/**
* Get all the commits for a PR
* @param {Object} options Configure the status
* @param {Object} options.context probot context object
* @param {string|null} options.pendingReleaseIssueUrl A link to the pending release issue, if it exists
* @returns {Promise} Resolves when the status is created on the PR
* @private
*/
async function createStatusOnAllPRs({ context, pendingReleaseIssueUrl }) {
const allOpenPrs = await getAllOpenPRs(context);
return Promise.all(allOpenPrs.map(pr =>
createAppropriateStatusForPR({
context,
pr,
pendingReleaseIssueUrl
})));
}
/**
* Release label is present
* @param {Array<Object>} labels collection of label objects
* @returns {boolean} True if release label is present
* @private
*/
function hasReleaseLabel(labels) {
return labels.some(({ name }) => name === RELEASE_LABEL);
}
/**
* Check if it is Post Release label
* @param {Object} label label options
* @param {string} label.name label name
* @returns {boolean} True if its post release label
* @private
*/
function isPostReleaseLabel({ name }) {
return name === POST_RELEASE_LABEL;
}
/**
* Handler for issue label event
* @param {Object} context probot context object
* @returns {Promise} promise
* @private
*/
async function issueLabeledHandler(context) {
// check if the label is post-release and the same issue has release label
if (isPostReleaseLabel(context.payload.label) && hasReleaseLabel(context.payload.issue.labels)) {
await createStatusOnAllPRs({
context,
pendingReleaseIssueUrl: context.payload.issue.html_url
});
}
}
/**
* Handler for issue close event
* @param {Object} context probot context object
* @returns {Promise} promise
* @private
*/
async function issueCloseHandler(context) {
// check if the closed issue is a release issue
if (hasReleaseLabel(context.payload.issue.labels)) {
await createStatusOnAllPRs({
context,
pendingReleaseIssueUrl: null
});
}
}
/**
* Handler for pull request open and reopen event
* @param {Object} context probot context object
* @returns {Promise} promise
* @private
*/
async function prOpenHandler(context) {
/**
* check if the release issue has the label for no semver minor merge please
* false: add success status to pr
* true: add failure message if its not a fix or doc pr else success
*/
const { data: releaseIssues } = await context.github.issues.listForRepo(
context.repo({
labels: `${RELEASE_LABEL},${POST_RELEASE_LABEL}`
})
);
await createAppropriateStatusForPR({
context,
pr: context.payload.pull_request,
pendingReleaseIssueUrl: releaseIssues.length ? releaseIssues[0].html_url : null
});
}
module.exports = robot => {
robot.on("issues.labeled", issueLabeledHandler);
robot.on("issues.closed", issueCloseHandler);
robot.on(
[
"pull_request.opened",
"pull_request.reopened",
"pull_request.synchronize",
"pull_request.edited"
],
prOpenHandler
);
};