-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathprocessor.js
187 lines (172 loc) · 5.79 KB
/
processor.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
/* eslint no-continue: 0 */
import Commenter from './commenter.js';
import PluginManager from './plugins/index.js';
import { parseBase64Json } from './utils.js';
import processConfig from './config-processor.js';
/**
* @typedef {import('@octokit/webhooks').EventPayloads.WebhookPayloadPullRequest} WebhookPayloadPullRequest
* @typedef {WebhookPayloadPullRequest & { changes: Object }} WebhookPayloadPullRequestWithChanges
* @typedef {import('probot').Context<WebhookPayloadPullRequestWithChanges>} ProbotContext
* @typedef {import('./plugins/base.js')} BasePlugin
* @typedef {{[pluginName: string]: BasePlugin}} PluginManagerType
*/
/**
* Process a PR
*
* @param {ProbotContext} context PR webhook context
* @returns {Promise<void>} Completion promise
* @public
*/
export default async function processPR(context) {
return processPRInternal(context);
}
/**
* Process a PR (internal function for testing)
* @param {ProbotContext} context PR webhook context
* @param {typeof Commenter} CommenterType Commenter type (for dependency injection)
* @param {typeof PluginManager} PluginManagerType PluginManager type (for dependency injection)
* @returns {Promise<void>} Completion promise
* @private
*/
// eslint-disable-next-line complexity, max-statements
export async function processPRInternal(context, CommenterType = Commenter, PluginManagerType = PluginManager) {
const logData = {
repository: context.payload.repository.full_name,
number: context.payload.number,
requestId: context.id
};
context.log.info(logData, 'Processing PR');
if (process.env.GH_ENTERPRISE_ID) {
const ghecEnterpriseId = parseInt(process.env.GH_ENTERPRISE_ID, 10);
if (!isNaN(ghecEnterpriseId) &&
context.payload.enterprise &&
context.payload.enterprise.id === ghecEnterpriseId) {
context.log.info('PR is from the configured Enterprise');
} else {
context.log.info('PR is not from the configured Enterprise, nothing to do');
return;
}
}
if (process.env.NO_PUBLIC_REPOS === 'true' && !context.payload.repository.private) {
context.log.info('Pullie has been disabled on public repos, nothing to do');
return;
}
let repoConfig;
try {
repoConfig = await getRepoConfig(context);
} catch (err) {
context.log.error({
requestId: context.id,
err
}, 'Error getting repository config');
return;
}
if (!repoConfig) {
// No config specified for this repo, nothing to do
context.log.info(logData, 'No config specified for repo, nothing to do');
return;
}
let orgConfig;
try {
orgConfig = await getOrgConfig(context);
} catch (err) {
context.log.warn({
requestId: context.id,
err
}, 'Error getting org config');
orgConfig = null;
}
/** @type {PluginManagerType} */
// @ts-ignore
const pluginManager = new PluginManagerType();
const config = processConfig(pluginManager, orgConfig, repoConfig, invalidPlugin => {
context.log.error({
repository: context.payload.repository.full_name, plugin: invalidPlugin, requestId: context.id
}, 'Invalid plugin specified in repo config');
});
if (!Array.isArray(config.plugins) || config.plugins.length === 0) {
// No plugins to run, nothing to do
context.log.info(logData, 'No plugins to run, nothing to do');
return;
}
const commenter = new CommenterType();
for (const pluginConfig of config.plugins) {
const pluginName = typeof pluginConfig === 'string' ? pluginConfig : pluginConfig.plugin;
const plugin = pluginManager[pluginName];
if (!plugin) {
context.log.error({
repository: context.payload.repository.full_name, plugin: pluginName, requestId: context.id
}, 'Invalid plugin specified in config');
continue;
}
if (context.payload.action === 'edited' && !plugin.processesEdits) {
continue;
}
if (context.payload.action === 'ready_for_review' && !plugin.processesReadyForReview) {
continue;
}
const cfg = typeof pluginConfig === 'string' ? {} : pluginConfig.config;
try {
await plugin.processRequest(context, commenter, cfg);
} catch (pluginProcessRequestErr) {
context.log.error({
error: pluginProcessRequestErr,
repository: context.payload.repository.full_name,
number: context.payload.number,
plugin: pluginName,
requestId: context.id
}, 'Error running plugin');
continue;
}
}
context.log.info(logData, 'Finished processing PR');
const comment = commenter.flushToString();
if (comment) {
await context.octokit.issues.createComment({
...context.repo(),
issue_number: context.payload.number,
body: comment
});
}
}
/**
* Get config for the repo specified in the context
*
* @param {ProbotContext} context PR webhook context
* @returns {Promise<Object>} Config for the repo
*/
async function getRepoConfig(context) {
let pullieRcRes = {};
try {
pullieRcRes = await context.octokit.repos.getContent({
...context.repo(),
path: '.pullierc'
});
} catch (err) {
// If there's no .pullierc, just skip this request. Otherwise, re-throw the error.
if (err.status === 404) return;
throw err;
}
return parseBase64Json(pullieRcRes.data);
}
/**
* Get org-level config for the org specified in the context
*
* @param {ProbotContext} context PR webhook context
* @returns {Promise<Object>} Config for the repo
*/
async function getOrgConfig(context) {
let pullieRcRes = {};
try {
pullieRcRes = await context.octokit.repos.getContent({
owner: context.payload.repository.owner.login,
repo: '.github',
path: '.pullierc'
});
} catch (err) {
// If there's no .pullierc, just skip this request. Otherwise, re-throw the error.
if (err.status === 404) return;
throw err;
}
return parseBase64Json(pullieRcRes.data);
}