wuffle 0.73.3 → 0.75.0

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.
Files changed (29) hide show
  1. package/lib/apps/auth-routes/AuthRoutes.js +16 -14
  2. package/lib/apps/{automatic-dev-flow.js → automatic-dev-flow/AutomaticDevFlow.js} +12 -13
  3. package/lib/apps/automatic-dev-flow/index.js +6 -0
  4. package/lib/apps/background-sync/BackgroundSync.js +41 -83
  5. package/lib/apps/background-sync/BackgroundSyncBackend.js +140 -0
  6. package/lib/apps/background-sync/index.js +3 -1
  7. package/lib/apps/board-api-routes/board-api-routes.js +20 -14
  8. package/lib/apps/dump-store/s3/S3.js +12 -0
  9. package/lib/apps/{events-sync.js → events-sync/EventsSync.js} +7 -5
  10. package/lib/apps/events-sync/index.js +6 -0
  11. package/lib/apps/github-app/GithubApp.js +69 -6
  12. package/lib/apps/github-checks/GithubChecks.js +2 -0
  13. package/lib/apps/github-comments/GithubComments.js +9 -78
  14. package/lib/apps/github-comments/GithubCommentsBackend.js +132 -0
  15. package/lib/apps/github-comments/index.js +3 -1
  16. package/lib/apps/github-issues/GithubIssues.js +161 -9
  17. package/lib/apps/github-reviews/GithubReviews.js +2 -0
  18. package/lib/apps/github-statuses/GithubStatuses.js +2 -0
  19. package/lib/apps/issue-filter/IssueFilter.js +5 -3
  20. package/lib/apps/log-events.js +4 -4
  21. package/lib/apps/{reindex-store.js → reindex-store/ReindexStore.js} +6 -4
  22. package/lib/apps/reindex-store/index.js +6 -0
  23. package/lib/apps/search/Search.js +9 -5
  24. package/lib/apps/webhook-events/WebhookEvents.js +12 -5
  25. package/lib/events.js +2 -2
  26. package/lib/index.js +13 -5
  27. package/lib/probot/CustomProbot.js +2 -1
  28. package/lib/util/search.js +1 -1
  29. package/package.json +6 -5
@@ -60,7 +60,7 @@ export default function AuthRoutes(logger, router, securityContext) {
60
60
  };
61
61
 
62
62
  const params = new URLSearchParams();
63
- params.append('client_id', process.env.GITHUB_CLIENT_ID);
63
+ params.append('client_id', /** @type {string} */ (process.env.GITHUB_CLIENT_ID));
64
64
  params.append('state', state);
65
65
  params.append('redirect_uri', appUrl('/wuffle/login/callback'));
66
66
 
@@ -81,10 +81,9 @@ export default function AuthRoutes(logger, router, securityContext) {
81
81
 
82
82
  log.debug({ session_id }, 'logging out');
83
83
 
84
- return req.session.destroy(function(err) {
85
- return res.redirect(redirectTo);
86
- }) && null;
87
-
84
+ req.session.destroy(function(err) {
85
+ res.redirect(redirectTo);
86
+ });
88
87
  });
89
88
 
90
89
 
@@ -128,8 +127,8 @@ export default function AuthRoutes(logger, router, securityContext) {
128
127
  const params = new URLSearchParams();
129
128
  params.append('code', /** @type {string} */ (code));
130
129
  params.append('state', /** @type {string} */ (state));
131
- params.append('client_id', process.env.GITHUB_CLIENT_ID);
132
- params.append('client_secret', process.env.GITHUB_CLIENT_SECRET);
130
+ params.append('client_id', /** @type {string} */ (process.env.GITHUB_CLIENT_ID));
131
+ params.append('client_secret', /** @type {string} */ (process.env.GITHUB_CLIENT_SECRET));
133
132
  params.append('redirect_uri', appUrl('/wuffle/login/callback'));
134
133
 
135
134
  const {
@@ -185,7 +184,9 @@ export default function AuthRoutes(logger, router, securityContext) {
185
184
  } = session;
186
185
 
187
186
  if (!githubUser) {
188
- return res.type('json').json(null) && null;
187
+ res.type('json').json(null);
188
+
189
+ return;
189
190
  }
190
191
 
191
192
  const {
@@ -216,17 +217,18 @@ export default function AuthRoutes(logger, router, securityContext) {
216
217
  }, 'failed to check GitHub authentication');
217
218
 
218
219
  // access is not granted anymore, clear current session
219
- return req.session.destroy(function(err) {
220
- return res.type('json').json(null);
221
- }) && null;
220
+ req.session.destroy(function(err) {
221
+ res.type('json').json(null);
222
+ });
223
+
224
+ return;
222
225
  }
223
226
  }
224
227
 
225
- return res.type('json').json({
228
+ res.type('json').json({
226
229
  login,
227
230
  avatar_url
228
- }) && null;
229
-
231
+ });
230
232
  });
231
233
 
232
234
 
@@ -12,11 +12,11 @@ const CHANGES_REQUESTED = 'changes_requested';
12
12
  *
13
13
  * @constructor
14
14
  *
15
- * @param {import('./webhook-events/WebhookEvents.js').default} webhookEvents
16
- * @param {import('./github-issues/GithubIssues.js').default} githubIssues
17
- * @param {import('../columns.js').default} columns
18
- * @param {import('./issue-filter/IssueFilter.js').default} issueFilter
19
- * @param {import('../types.js').Logger } logger
15
+ * @param {import('../webhook-events/WebhookEvents.js').default} webhookEvents
16
+ * @param {import('../github-issues/GithubIssues.js').default} githubIssues
17
+ * @param {import('../../columns.js').default} columns
18
+ * @param {import('../issue-filter/IssueFilter.js').default} issueFilter
19
+ * @param {import('../../types.js').Logger } logger
20
20
  */
21
21
  export default function(webhookEvents, githubIssues, columns, issueFilter, logger) {
22
22
 
@@ -31,14 +31,13 @@ export default function(webhookEvents, githubIssues, columns, issueFilter, logge
31
31
  'pull_request.closed'
32
32
  ], ifEnabled(async (context) => {
33
33
 
34
- const {
35
- pull_request,
36
- issue
37
- } = context.payload;
38
-
39
34
  const column = columns.getByState(DONE);
40
35
 
41
- await githubIssues.moveIssue(context, issue || pull_request, column);
36
+ const issueOrPull = 'issue' in context.payload
37
+ ? context.payload.issue
38
+ : context.payload.pull_request;
39
+
40
+ await githubIssues.moveIssue(context, issueOrPull, column);
42
41
  }));
43
42
 
44
43
  webhookEvents.on('pull_request.converted_to_draft', ifEnabled(async (context) => {
@@ -107,7 +106,7 @@ export default function(webhookEvents, githubIssues, columns, issueFilter, logge
107
106
  const newAssignee = (
108
107
  process.env.AUTO_ASSIGN_PULLS && !external &&
109
108
  author && author.type === 'User' && author.login
110
- );
109
+ ) || null;
111
110
 
112
111
  await Promise.all([
113
112
  githubIssues.moveIssue(context, pull_request, column, newAssignee),
@@ -172,7 +171,7 @@ export default function(webhookEvents, githubIssues, columns, issueFilter, logge
172
171
  return;
173
172
  }
174
173
 
175
- const issue_number = match[1];
174
+ const issue_number = parseInt(match[1], 10);
176
175
 
177
176
  const column = columns.getByState(IN_PROGRESS);
178
177
 
@@ -0,0 +1,6 @@
1
+ import AutomaticDevFlow from './AutomaticDevFlow.js';
2
+
3
+ export default {
4
+ __init__: [ 'automaticDevFlow' ],
5
+ automaticDevFlow: [ 'type', AutomaticDevFlow ]
6
+ };
@@ -9,38 +9,48 @@ function isInternalError(error) {
9
9
  /**
10
10
  * This component performs a periodic background sync of a project.
11
11
  *
12
+ * Unless disabled via `process.env.DISABLE_BACKGROUND_SYNC` it will
13
+ * register a recurring check.
14
+ *
15
+ * Background check performs various optimizations to ensure only relevant
16
+ * data is stored on the board:
17
+ *
18
+ * * Closed issues/PRs on the board will be thrashed
19
+ * * Closed issues/PRs wiil not be synchronized from GitHub
20
+ * * Open but stale issues/PRs will not be synchronized to the board
21
+ * * Open issue/PR details will only be synchronized for recent issues
22
+ *
12
23
  * @constructor
13
24
  *
14
- * @param {import('../../types.js').Logger} logger
15
25
  * @param {Object} config
26
+ * @param {import('../../types.js').Logger} logger
16
27
  * @param {import('../../store.js').default} store
17
- * @param {import('../github-client/GithubClient.js').default} githubClient
18
- * @param {import('../github-app/GithubApp.js').default} githubApp
19
28
  * @param {import('../../events.js').default} events
29
+ * @param {import('./BackgroundSyncBackend.js').default} backgroundSyncBackend
20
30
  */
21
- export default function BackgroundSync(logger, config, store, githubClient, githubApp, events) {
31
+ export default function BackgroundSync(config, logger, store, events, backgroundSyncBackend) {
22
32
 
23
33
  // 30 days
24
34
  const syncClosedLookback = (
25
- parseInt(process.env.BACKGROUND_SYNC_SYNC_CLOSED_LOOKBACK, 10) ||
35
+ parseInt(process.env.BACKGROUND_SYNC_SYNC_CLOSED_LOOKBACK || '', 10) ||
26
36
  1000 * 60 * 60 * 24 * 30
27
37
  );
28
38
 
29
39
  // 4 hours
30
40
  const syncClosedDetailsLookback = (
31
- parseInt(process.env.BACKGROUND_SYNC_SYNC_CLOSED_DETAILS_LOOKBACK, 10) ||
41
+ parseInt(process.env.BACKGROUND_SYNC_SYNC_CLOSED_DETAILS_LOOKBACK || '', 10) ||
32
42
  1000 * 60 * 60 * 4
33
43
  );
34
44
 
35
45
  // 1 day
36
46
  const syncOpenDetailsLookback = (
37
- parseInt(process.env.BACKGROUND_SYNC_SYNC_OPEN_DETAILS_LOOKBACK, 10) ||
47
+ parseInt(process.env.BACKGROUND_SYNC_SYNC_OPEN_DETAILS_LOOKBACK || '', 10) ||
38
48
  1000 * 60 * 60 * 24
39
49
  );
40
50
 
41
51
  // 60 days
42
52
  const removeClosedLookback = (
43
- parseInt(process.env.BACKGROUND_SYNC_REMOVE_CLOSED_LOOKBACK, 10) ||
53
+ parseInt(process.env.BACKGROUND_SYNC_REMOVE_CLOSED_LOOKBACK || '', 10) ||
44
54
  1000 * 60 * 60 * 24 * 60
45
55
  );
46
56
 
@@ -101,18 +111,14 @@ We automatically synchronize all repositories you granted us access to via the G
101
111
  log.debug({ installation: owner }, 'processing');
102
112
 
103
113
  try {
104
- const octokit = await githubClient.getOrgScoped(owner);
105
-
106
- const repositories = await octokit.paginate(
107
- octokit.rest.apps.listReposAccessibleToInstallation,
108
- {
109
- per_page: 100
110
- }
111
- );
114
+ const repositories = await backgroundSyncBackend.getInstallationRepositories(installation);
112
115
 
113
116
  for (const repository of repositories) {
114
117
 
115
- const owner = repository.owner.login;
118
+ if (repository.owner.login !== owner) {
119
+ throw new Error('repository.owner !== installation.owner');
120
+ }
121
+
116
122
  const repo = repository.name;
117
123
 
118
124
  // log found repository
@@ -135,74 +141,12 @@ We automatically synchronize all repositories you granted us access to via the G
135
141
  repo
136
142
  }, 'processing');
137
143
 
138
- const params = {
139
- sort: /** @type { 'updated' } */ ('updated'),
140
- direction: /** @type { 'desc' } */ ('desc'),
141
- per_page: 100,
142
- owner,
143
- repo
144
- };
145
-
146
- const [
144
+ const {
147
145
  open_issues,
148
146
  closed_issues,
149
147
  open_pull_requests,
150
148
  closed_pull_requests
151
- ] = await Promise.all([
152
-
153
- // open issues
154
- octokit.paginate(
155
- octokit.rest.issues.listForRepo,
156
- {
157
- ...params,
158
- state: 'open'
159
- },
160
- response => response.data.filter(issue => !('pull_request' in issue))
161
- ),
162
-
163
- // closed issues, updated last 30 days
164
- octokit.paginate(
165
- octokit.rest.issues.listForRepo,
166
- {
167
- ...params,
168
- state: 'closed',
169
- since: new Date(syncClosedSince).toISOString()
170
- },
171
- response => response.data.filter(issue => !('pull_request' in issue))
172
- ),
173
-
174
- // open pulls, all
175
- octokit.paginate(
176
- octokit.rest.pulls.list,
177
- {
178
- ...params,
179
- state: 'open'
180
- }
181
- ),
182
-
183
- // closed pulls, updated last 30 days
184
- octokit.paginate(
185
- octokit.rest.pulls.list,
186
- {
187
- ...params,
188
- state: 'closed'
189
- },
190
- (response, done) => {
191
-
192
- const pulls = response.data;
193
-
194
- const filtered = pulls.filter(
195
- pull => new Date(pull.updated_at).getTime() > syncClosedSince
196
- );
197
-
198
- if (filtered.length !== pulls.length) {
199
- done();
200
- }
201
-
202
- return filtered;
203
- }
204
- )
205
- ]);
149
+ } = await backgroundSyncBackend.getRepositoryIssuesAndPulls(repository, syncClosedSince);
206
150
 
207
151
  for (const issueOrPull of [
208
152
  ...open_issues,
@@ -460,12 +404,19 @@ We automatically synchronize all repositories you granted us access to via the G
460
404
  return Promise.all(jobs);
461
405
  }
462
406
 
407
+ /**
408
+ * Trigger background synchronization for all connected repositories.
409
+ *
410
+ * This ensures that data out-of-sync with the board is fetched from remote.
411
+ *
412
+ * @return {Promise<void>}
413
+ */
463
414
  async function backgroundSync() {
464
415
 
465
416
  log.info('start');
466
417
 
467
418
  try {
468
- const installations = await githubApp.getInstallations();
419
+ const installations = await backgroundSyncBackend.getInstallations();
469
420
 
470
421
  await doSync(installations);
471
422
 
@@ -477,7 +428,7 @@ We automatically synchronize all repositories you granted us access to via the G
477
428
  }
478
429
 
479
430
  const syncInterval = (
480
- parseInt(process.env.BACKGROUND_SYNC_SYNC_INTERVAL, 10) || (
431
+ parseInt(process.env.BACKGROUND_SYNC_SYNC_INTERVAL || '', 10) || (
481
432
  process.env.NODE_ENV !== 'production'
482
433
 
483
434
  // five minutes
@@ -509,6 +460,13 @@ We automatically synchronize all repositories you granted us access to via the G
509
460
 
510
461
  // api ///////////////////
511
462
 
463
+ /**
464
+ * Trigger background synchronization for all connected repositories.
465
+ *
466
+ * This ensures that data out-of-sync with the board is fetched from remote.
467
+ *
468
+ * @return {Promise<void>}
469
+ */
512
470
  this.backgroundSync = backgroundSync;
513
471
 
514
472
 
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @typedef { import('../github-app/types.js').Installation } Installation
3
+ */
4
+
5
+ /**
6
+ * This component fetches GitHub data required for background synchronization.
7
+ *
8
+ * @constructor
9
+ *
10
+ * @param {import('../github-client/GithubClient.js').default} githubClient
11
+ * @param {import('../github-app/GithubApp.js').default} githubApp
12
+ */
13
+ export default function BackgroundSyncBackend(githubClient, githubApp) {
14
+
15
+ /**
16
+ * Return available installations.
17
+ *
18
+ * @return {Promise<Installation[]>}
19
+ */
20
+ async function getInstallations(installation) {
21
+ return githubApp.getInstallations();
22
+ }
23
+
24
+ /**
25
+ * Return repositories accessible to a GitHub app installation.
26
+ *
27
+ * @param {Installation} installation
28
+ *
29
+ * @return {Promise<Array>}
30
+ */
31
+ async function getInstallationRepositories(installation) {
32
+ const owner = installation.account.login;
33
+ const octokit = await githubClient.getOrgScoped(owner);
34
+
35
+ return octokit.paginate(
36
+ octokit.rest.apps.listReposAccessibleToInstallation,
37
+ { per_page: 100 }
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Fetch issues and pull requests for a repository.
43
+ *
44
+ * @param {{ owner: { login: string }, name: string }} repository
45
+ * @param {number} syncClosedSince
46
+ *
47
+ * @return {Promise<{ open_issues: Array, closed_issues: Array, open_pull_requests: Array, closed_pull_requests: Array }>}
48
+ */
49
+ async function getRepositoryIssuesAndPulls(repository, syncClosedSince) {
50
+ const owner = repository.owner.login;
51
+ const octokit = await githubClient.getOrgScoped(owner);
52
+
53
+ const repo = repository.name;
54
+
55
+ const params = {
56
+ sort: /** @type { 'updated' } */ ('updated'),
57
+ direction: /** @type { 'desc' } */ ('desc'),
58
+ per_page: 100,
59
+ owner,
60
+ repo
61
+ };
62
+
63
+ const [
64
+ open_issues,
65
+ closed_issues,
66
+ open_pull_requests,
67
+ closed_pull_requests
68
+ ] = await Promise.all([
69
+
70
+ // open issues
71
+ octokit.paginate(
72
+ octokit.rest.issues.listForRepo,
73
+ {
74
+ ...params,
75
+ state: 'open'
76
+ },
77
+ response => response.data.filter(issue => !('pull_request' in issue))
78
+ ),
79
+
80
+ // closed issues, updated since syncClosedSince
81
+ octokit.paginate(
82
+ octokit.rest.issues.listForRepo,
83
+ {
84
+ ...params,
85
+ state: 'closed',
86
+ since: new Date(syncClosedSince).toISOString()
87
+ },
88
+ response => response.data.filter(issue => !('pull_request' in issue))
89
+ ),
90
+
91
+ // open pulls, all
92
+ octokit.paginate(
93
+ octokit.rest.pulls.list,
94
+ {
95
+ ...params,
96
+ state: 'open'
97
+ }
98
+ ),
99
+
100
+ // closed pulls, updated since syncClosedSince
101
+ octokit.paginate(
102
+ octokit.rest.pulls.list,
103
+ {
104
+ ...params,
105
+ state: 'closed'
106
+ },
107
+ (response, done) => {
108
+
109
+ const pulls = response.data;
110
+
111
+ const filtered = pulls.filter(
112
+ pull => new Date(pull.updated_at).getTime() > syncClosedSince
113
+ );
114
+
115
+ if (filtered.length !== pulls.length) {
116
+ done();
117
+ }
118
+
119
+ return filtered;
120
+ }
121
+ )
122
+ ]);
123
+
124
+ return {
125
+ open_issues,
126
+ closed_issues,
127
+ open_pull_requests,
128
+ closed_pull_requests
129
+ };
130
+ }
131
+
132
+
133
+ // api ///////////////////
134
+
135
+ this.getInstallations = getInstallations;
136
+
137
+ this.getInstallationRepositories = getInstallationRepositories;
138
+
139
+ this.getRepositoryIssuesAndPulls = getRepositoryIssuesAndPulls;
140
+ }
@@ -1,6 +1,8 @@
1
1
  import BackgroundSync from './BackgroundSync.js';
2
+ import BackgroundSyncBackend from './BackgroundSyncBackend.js';
2
3
 
3
4
  export default {
4
5
  __init__: [ 'backgroundSync' ],
5
- backgroundSync: [ 'type', BackgroundSync ]
6
+ backgroundSync: [ 'type', BackgroundSync ],
7
+ backgroundSyncBackend: [ 'type', BackgroundSyncBackend ]
6
8
  };
@@ -172,7 +172,7 @@ export default async function BoardApiRoutes(
172
172
  const items = store.getBoard();
173
173
  const cursor = store.getUpdateCursor();
174
174
 
175
- return filterBoardItems(req, items).then(filteredItems => {
175
+ filterBoardItems(req, items).then(filteredItems => {
176
176
 
177
177
  return res.type('json').json({
178
178
  items: filteredItems,
@@ -196,7 +196,7 @@ export default async function BoardApiRoutes(
196
196
  name
197
197
  } = config;
198
198
 
199
- return res.type('json').json({
199
+ res.type('json').json({
200
200
  columns: columns.map(c => {
201
201
  const { name, collapsed } = c;
202
202
 
@@ -236,7 +236,9 @@ export default async function BoardApiRoutes(
236
236
  const user = authRoutes.getGitHubUser(req);
237
237
 
238
238
  if (!user) {
239
- return res.status(401).json({}) && null;
239
+ res.status(401).json({});
240
+
241
+ return;
240
242
  }
241
243
 
242
244
  const body = JSON.parse(req.body);
@@ -251,13 +253,17 @@ export default async function BoardApiRoutes(
251
253
  const issue = await store.getIssueById(id);
252
254
 
253
255
  if (!issue) {
254
- return res.status(404).json({}) && null;
256
+ res.status(404).json({});
257
+
258
+ return;
255
259
  }
256
260
 
257
261
  const column = columns.getByName(columnName);
258
262
 
259
263
  if (!column) {
260
- return res.status(404).json({}) && null;
264
+ res.status(404).json({});
265
+
266
+ return;
261
267
  }
262
268
 
263
269
  const repo = repoAndOwner(issue);
@@ -265,7 +271,9 @@ export default async function BoardApiRoutes(
265
271
  const canWrite = await userAccess.canWrite(user, repo);
266
272
 
267
273
  if (!canWrite) {
268
- return res.status(403).json({}) && null;
274
+ res.status(403).json({});
275
+
276
+ return;
269
277
  }
270
278
 
271
279
  const octokit = await githubClient.getUserScoped(user);
@@ -280,15 +288,13 @@ export default async function BoardApiRoutes(
280
288
  }
281
289
  };
282
290
 
283
- return (
284
- moveIssue(context, issue, column, { before, after }).then(() => {
285
- res.type('json').json({});
286
- }).catch(err => {
287
- log.error(err, 'failed to move issue');
291
+ moveIssue(context, issue, column, { before, after }).then(() => {
292
+ res.type('json').json({});
293
+ }).catch(err => {
294
+ log.error(err, 'failed to move issue');
288
295
 
289
- res.status(500).json({ error : true });
290
- })
291
- ) && null;
296
+ res.status(500).json({ error : true });
297
+ });
292
298
 
293
299
  });
294
300
 
@@ -16,6 +16,18 @@ export default function S3() {
16
16
  S3_ENDPOINT: endpoint
17
17
  } = process.env;
18
18
 
19
+ if (!accessKeyId) {
20
+ throw new Error('process.env.AWS_ACCESS_KEY_ID required');
21
+ }
22
+
23
+ if (!secretAccessKey) {
24
+ throw new Error('process.env.AWS_SECRET_ACCESS_KEY required');
25
+ }
26
+
27
+ if (!bucket) {
28
+ throw new Error('process.env.S3_BUCKET required');
29
+ }
30
+
19
31
  const s3client = new S3Client({
20
32
  region,
21
33
  endpoint,
@@ -5,7 +5,7 @@ import {
5
5
  filterRepository,
6
6
  getIdentifier,
7
7
  getKey
8
- } from '../filters.js';
8
+ } from '../../filters.js';
9
9
 
10
10
 
11
11
  /**
@@ -13,14 +13,14 @@ import {
13
13
  *
14
14
  * @constructor
15
15
  *
16
- * @param {import('./webhook-events/WebhookEvents.js').default} webhookEvents
17
- * @param {import('../store.js').default} store
18
- * @param {import('../types.js').Logger} logger
16
+ * @param {import('../webhook-events/WebhookEvents.js').default} webhookEvents
17
+ * @param {import('../../store.js').default} store
18
+ * @param {import('../../types.js').Logger} logger
19
19
  */
20
20
  export default function EventsSync(webhookEvents, store, logger) {
21
21
 
22
22
  const log = logger.child({
23
- name: 'wuffle:user-access'
23
+ name: 'wuffle:events-sync'
24
24
  });
25
25
 
26
26
  // issues /////////////////////
@@ -104,6 +104,8 @@ export default function EventsSync(webhookEvents, store, logger) {
104
104
  'pull_request.converted_to_draft',
105
105
  'pull_request.assigned',
106
106
  'pull_request.unassigned',
107
+ 'pull_request.milestoned',
108
+ 'pull_request.demilestoned',
107
109
  'pull_request.synchronize',
108
110
  'pull_request.closed',
109
111
  'pull_request.review_requested',
@@ -0,0 +1,6 @@
1
+ import EventsSync from './EventsSync.js';
2
+
3
+ export default {
4
+ __init__: [ 'eventsSync' ],
5
+ eventsSync: [ 'type', EventsSync ]
6
+ };